前端使用
jsencryp
實現分段加密。
解決長文本RSA
加密報錯問題。
支持文本包含中文。
支持后端解密。
前端加密代碼:
// import { JSEncrypt } from 'jsencrypt'
const JSEncrypt = require('jsencrypt')
/*** 使用 JSEncrypt 實現分段 RSA 加密(正確處理中文字符)* @param {string} data - 要加密的數據* @param {string} publicKey - PEM格式的公鑰* @returns {string} 加密后的數據(Base64編碼)*/
function encryptWithJSEncrypt(data, publicKey) {const encrypt = new JSEncrypt();encrypt.setPublicKey(publicKey);// RSA 2048位密鑰時,最大加密明文長度為245字節const MAX_ENCRYPT_BLOCK = 245;const encryptedBytes = [];// 按字符安全地分段,避免切斷UTF-8字符let startIndex = 0;while (startIndex < data.length) {// 嘗試獲取一段文本,確保其UTF-8編碼后的字節長度不超過MAX_ENCRYPT_BLOCKlet endIndex = startIndex;let segment = '';// 逐個添加字符,直到接近最大長度限制while (endIndex < data.length) {const nextSegment = segment + data.charAt(endIndex);const nextSegmentBytes = new TextEncoder().encode(nextSegment);// 如果添加下一個字符會超出限制,則停止if (nextSegmentBytes.length > MAX_ENCRYPT_BLOCK) {break;}segment = nextSegment;endIndex++;}// 如果沒有成功獲取到任何字符(理論上不應該發生),則至少獲取一個字符if (segment.length === 0 && startIndex < data.length) {segment = data.charAt(startIndex);endIndex = startIndex + 1;}// 加密當前段const encryptedBlock = encrypt.encrypt(segment);if (!encryptedBlock) {throw new Error('加密失敗');}// 將加密后的Base64字符串轉換為字節數組const encryptedBlockBytes = atob(encryptedBlock);for (let j = 0; j < encryptedBlockBytes.length; j++) {encryptedBytes.push(encryptedBlockBytes.charCodeAt(j));}// 移動到下一段startIndex = endIndex;}// 將所有加密后的字節數據進行Base64編碼let result = '';for (let i = 0; i < encryptedBytes.length; i++) {result += String.fromCharCode(encryptedBytes[i]);}return btoa(result);
}// 帶不帶-----BEGIN PUBLIC KEY-----均可
let pubkey = '-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwCKy+dsq0bbBhdt2Z2UDirtemU/7lAb746JngP4KLDmH9IgLZfranlflVLjfDW6BMYEVP3mN0HPlW4RWMPORgaHUu1yECzWqQX2bpDlN+IAZZhytTlMEyA+3DqKy3tS3QlIheDpMj7T6rOrfVFUDu3A4Tg/I3xqkToAHWI3rW3O3kDcFkFWVKYLiHsBhCd6LtauYENsB36voQKMGDrd9KZd5ndcy7otAU/+ITEfIMyKoD0eCojuDVOtREVY9jaos6kMDAtm8+ppbC7/0Q4TBf1zWaC1xDI6sTp+L6sYD6fDE2ni1/ly718hvHdPYC6GanmrME51pGGp8FgIkYpWXiQIDAQAB-----END PUBLIC KEY-----'let longText = "這是一段需要加密的長文本示例。RSA加密算法對加密的數據長度有限制,使用分段加密可以解決這個問題。當文本長度超過密鑰所能處理的最大長度時,我們需要將文本分成多個部分分別進行加密。Java后端使用的RSA/ECB/PKCS1Padding模式與JSEncrypt默認模式兼容"
let encrypt= encryptWithJSEncrypt(longText, pubkey)
console.log(encrypt)
后端工具類:
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;public class RSAUtils {private static final String ALGORITHM = "RSA";private static final String TRANSFORMATION = "RSA/ECB/PKCS1Padding";private static final int KEY_SIZE = 2048;// RSA最大加密明文大小private static final int MAX_ENCRYPT_BLOCK = 245;// RSA最大解密密文大小private static final int MAX_DECRYPT_BLOCK = 256;/*** 生成RSA密鑰對** @return KeyPair對象包含公鑰和私鑰*/public static KeyPair generateKeyPair() throws Exception {KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);keyPairGenerator.initialize(KEY_SIZE);return keyPairGenerator.generateKeyPair();}/*** 使用公鑰加密數據(支持分段加密)** @param data 要加密的數據* @param publicKey 公鑰* @return 加密后的數據(Base64編碼)*/public static String encrypt(String data, PublicKey publicKey) throws Exception {Cipher cipher = Cipher.getInstance(TRANSFORMATION);cipher.init(Cipher.ENCRYPT_MODE, publicKey);byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);int inputLen = dataBytes.length;ByteArrayOutputStream out = new ByteArrayOutputStream();int offSet = 0;byte[] cache;int i = 0;// 對數據分段加密while (inputLen - offSet > 0) {if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {cache = cipher.doFinal(dataBytes, offSet, MAX_ENCRYPT_BLOCK);} else {cache = cipher.doFinal(dataBytes, offSet, inputLen - offSet);}out.write(cache, 0, cache.length);i++;offSet = i * MAX_ENCRYPT_BLOCK;}byte[] encryptedData = out.toByteArray();out.close();return Base64.getEncoder().encodeToString(encryptedData);}/*** 使用私鑰解密數據(支持分段解密)** @param encryptedData 加密的數據(Base64編碼)* @param privateKey 私鑰* @return 解密后的原始數據*/public static String decrypt(String encryptedData, PrivateKey privateKey) throws Exception {Cipher cipher = Cipher.getInstance(TRANSFORMATION);cipher.init(Cipher.DECRYPT_MODE, privateKey);byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);int inputLen = encryptedBytes.length;ByteArrayOutputStream out = new ByteArrayOutputStream();int offSet = 0;byte[] cache;int i = 0;// 對數據分段解密while (inputLen - offSet > 0) {if (inputLen - offSet > MAX_DECRYPT_BLOCK) {cache = cipher.doFinal(encryptedBytes, offSet, MAX_DECRYPT_BLOCK);} else {cache = cipher.doFinal(encryptedBytes, offSet, inputLen - offSet);}out.write(cache, 0, cache.length);i++;offSet = i * MAX_DECRYPT_BLOCK;}byte[] decryptedData = out.toByteArray();out.close();return new String(decryptedData, StandardCharsets.UTF_8);}/*** 從字符串加載公鑰** @param publicKey 公鑰字符串* @return PublicKey對象*/public static PublicKey loadPublicKey(String publicKey) throws Exception {byte[] keyBytes = Base64.getDecoder().decode(publicKey);X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);return keyFactory.generatePublic(spec);}/*** 從字符串加載私鑰** @param privateKey 私鑰字符串* @return PrivateKey對象*/public static PrivateKey loadPrivateKey(String privateKey) throws Exception {byte[] keyBytes = Base64.getDecoder().decode(privateKey);PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);return keyFactory.generatePrivate(spec);}/*** 將公鑰轉換為字符串** @param publicKey 公鑰* @return Base64編碼的公鑰字符串*/public static String publicKeyToString(PublicKey publicKey) {return Base64.getEncoder().encodeToString(publicKey.getEncoded());}/*** 將私鑰轉換為字符串** @param privateKey 私鑰* @return Base64編碼的私鑰字符串*/public static String privateKeyToString(PrivateKey privateKey) {return Base64.getEncoder().encodeToString(privateKey.getEncoded());}/*** 測試加密解密功能*/public static void main(String[] args) {try {// 生成密鑰對KeyPair keyPair = generateKeyPair();PublicKey publicKey = keyPair.getPublic();PrivateKey privateKey = keyPair.getPrivate();// 打印公鑰和私鑰System.out.println("公鑰: " + publicKeyToString(publicKey));System.out.println("私鑰: " + privateKeyToString(privateKey));// 測試數據String testData = "這是一段需要加密的長文本示例。RSA加密算法對加密的數據長度有限制,使用分段加密可以解決這個問題。當文本長度超過密鑰所能處理的最大長度時,我們需要將文本分成多個部分分別進行加密。Java后端使用的RSA/ECB/PKCS1Padding模式與JSEncrypt默認模式兼容";// 加密String encryptedData = encrypt(testData, publicKey);System.out.println("加密后數據: " + encryptedData);// 解密String decryptedData = decrypt(encryptedData, privateKey);System.out.println("解密后數據: " + decryptedData);// 驗證System.out.println("加密解密是否成功: " + testData.equals(decryptedData));} catch (Exception e) {e.printStackTrace();}}
}