公司微服务用了那么久,终于到了启用加密请求的这一天,本身内部集成了加密变量,我以为打开开关,这事儿就妥了。但还是年轻了,前端和JAVA单纯RSA加密没问题,问题是切片和解决中文字符集搞了大半天。
ChatGPT给了示例没法用,还是Qoder最后帮我完善的方法,里面base64转来转去的,让我徒手写我还真是没招。我为国内AI打call!
补点基础
想直接要代码,直接往下拉,JAVA和vue都是封装的一个工具文件,直接用就好了。这里先了解下 AES 和 RSA 加密的概念。
🔑 1. AES 加密(Advanced Encryption Standard,高级加密标准)
1.1 定义
AES 是一种 对称加密算法(Symmetric Encryption),由美国国家标准与技术研究院(NIST)在 2001 年发布,替代了早期的 DES(Data Encryption Standard)。
对称加密的特点是:加密和解密使用同一把密钥。
1.2 工作原理
- 明文(Plaintext) + 密钥(Key) → 加密算法 → 密文(Ciphertext)
- 密文 + 密钥(Key) → 解密算法 → 明文
- AES 使用分组加密:把数据切分成固定长度的块(block),标准是 128 bit(16 字节)。
- 支持的密钥长度:128 位、192 位、256 位。
1.3 加密模式
AES 本身只是一个分组加密算法,需要配合 工作模式(Mode of Operation) 使用,常见有:
- ECB(Electronic Codebook):每个块单独加密,简单但容易泄露模式。
- CBC(Cipher Block Chaining):前一个块的密文会影响下一个块,需要初始向量 IV。
- CFB、OFB、CTR、GCM 等模式,增强安全性和应用灵活性。
1.4 特点
✅ 优点:
- 计算速度快(适合加密大数据)
- 安全性高,广泛用于工业界
❌ 缺点:
- 需要安全地保存密钥(对称密钥分发是难点)
1.5 应用场景
- HTTPS 中的会话数据加密(TLS 协议里常用 AES-128/256)
- 数据库、文件加密
- VPN、磁盘加密软件
🔑 2. RSA 加密(Rivest–Shamir–Adleman)
2.1 定义
RSA 是一种 非对称加密算法(Asymmetric Encryption),由 Ron Rivest、Adi Shamir 和 Leonard Adleman 于 1977 年提出。
非对称加密的特点是:加密和解密使用不同的密钥。
- 公钥(Public Key):可公开,用来加密数据
- 私钥(Private Key):必须保密,用来解密数据
2.2 工作原理
RSA 基于 大整数质因数分解的数学难题:
- 两个大素数相乘容易
- 反过来分解非常困难
核心流程:
- 生成一对密钥(公钥 + 私钥)
- 用公钥加密 → 只有私钥能解密
- 反过来,私钥签名 → 只有公钥能验证
2.3 特点
✅ 优点:
- 不需要提前共享密钥(解决了 AES 的密钥分发问题)
- 可用于 加密 和 数字签名
❌ 缺点:
- 运算速度慢(远远慢于 AES)
- 密文长度受密钥位数限制(例如 1024 位 RSA 最多只能加密 117 字节的明文)
2.4 应用场景
- HTTPS 中的握手阶段(RSA 用于加密 AES 会话密钥)
- 数字签名、身份认证
- 数字证书(CA 体系)
3. AES vs RSA 对比
| 特性 | AES(对称) | RSA(非对称) |
|---|---|---|
| 密钥数量 | 1 把(同一密钥加解密) | 2 把(公钥加密,私钥解密) |
| 速度 | 快(适合大数据) | 慢(主要加密小数据) |
| 安全性 | 高,但需安全传输密钥 | 高,基于数学难题 |
| 常见用途 | 数据传输/存储加密 | 密钥交换、数字签名 |
| 缺点 | 密钥分发难 | 性能低,不适合加密大文件 |
实际应用中的组合
在现实应用中,AES 和 RSA 经常结合使用:
- 通信双方用 RSA 交换 AES 密钥(解决对称密钥分发问题)。
- 后续通信使用 AES 加密大数据(保证性能和安全)。
说明
以上都是AI生成。
RSA的加密长度特性:
- 1024位 RSA 最多只能加密 117 字节的明文
- 2048位 RSA 最多只能加密 245 字节的明文
JAVA自己写的分段加密,VUE的也得自己写。
中文在RSA加密不受待见,我试过前端加密前端解没有问题,JAVA加密JAVA解密也没问题,但是他们俩一交互,全是问题。只能先base64,把中文解决了,这事儿才算解决了。
以下工具类没有提供base64处理,也就是json字符串进来你得单独处理base64。如下:
import { Base64 } from 'js-base64';
// 加密前 encode
let data = Base64.encode(JSON.stringify(jsonObject));
// 解密后 decode
let data = Base64.decode(encryptData);// 加密前 encode
String data = java.util.Base64.getEncoder().encodeToString(jsonString.getBytes(Charsets.UTF_8));
// 解密后 decode
String data = new String(java.util.Base64.getDecoder().decode(encrypt));6. 工具类
VUE3的工具类
/** rsa.js */
import JSEncrypt from 'jsencrypt';
/**
* 规范化 Base64(去空白,URL-safe -> 标准,补齐 =)
*/
function normalizeBase64(s) {
if (!s) return s;
s = s.replace(/\s+/g, ""); // 去掉换行/空白
s = s.replace(/-/g, "+").replace(/_/g, "/"); // url-safe -> standard
// 补齐 =
while (s.length % 4 !== 0) s += "=";
return s;
}
/** base64 -> Uint8Array */
function base64ToUint8Array(b64) {
const binStr = atob(b64);
const len = binStr.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) bytes[i] = binStr.charCodeAt(i);
return bytes;
}
/** Uint8Array -> base64 (安全小分块转换以避免 apply 限制) */
function uint8ArrayToBase64(u8arr) {
const CHUNK = 0x8000; // 32KB chunks (我们实际每块很小,所以只是保险)
let index = 0;
let binary = "";
while (index < u8arr.length) {
const slice = u8arr.subarray(index, Math.min(index + CHUNK, u8arr.length));
binary += String.fromCharCode.apply(null, slice);
index += CHUNK;
}
return btoa(binary);
}
const chunkStringByByteLimit=(str, limit)=> {
const encoder = new TextEncoder();
const chunks = [];
let cur = '';
let curLen = 0;
for (const ch of str) {
const chLen = encoder.encode(ch).length;
if (curLen + chLen > limit) {
// flush
chunks.push(cur);
cur = ch;
curLen = chLen;
} else {
cur += ch;
curLen += chLen;
}
}
if (cur.length > 0) chunks.push(cur);
return chunks;
}
// 加密
export const rsaEncrypt = (plainText, publicKeyPem) => {
// 默认 1024-bit key 的最大明文字节
const MAX_ENCRYPT_BLOCK = 117; // 如果你的密钥是 2048-bit,请改为 245
// chunk 明文(按 UTF-8 字节数)
const parts = chunkStringByByteLimit(plainText, MAX_ENCRYPT_BLOCK);
// 使用 JSEncrypt 逐块加密(每块返回 base64)
// eslint-disable-next-line no-undef
const jse = new JSEncrypt();
jse.setPublicKey(publicKeyPem);
const encryptedChunksBase64 = [];
for (const part of parts) {
const enc = jse.encrypt(part); // 返回 base64 或 null
if (!enc) {
throw new Error('jsencrypt 加密失败,可能公钥格式不对或分块过长');
}
encryptedChunksBase64.push(enc);
}
// 把每块 Base64 -> Uint8Array,然后拼接所有 bytes
let totalLen = 0;
const arrays = encryptedChunksBase64.map(b64 => {
const u8 = base64ToUint8Array(b64);
totalLen += u8.length;
return u8;
});
const result = new Uint8Array(totalLen);
let offset = 0;
for (const arr of arrays) {
result.set(arr, offset);
offset += arr.length;
}
// 最后把拼接后的 bytes 再 base64(与 Java 端行为一致)
return uint8ArrayToBase64(result);
}
/**
* 分片解密主函数
* @param {String} fullBase64Cipher 后端返回的整体 Base64 密文(多个密文块拼接后再Base64)
* @param {String} privateKeyPem 私钥(PEM 格式,包括 -----BEGIN PRIVATE KEY-----)
* @param {Number} keySizeBits RSA 密钥位数,默认 1024(如果你是 2048,传 2048)
* @returns {String} 明文(UTF-8 正确)
*/
export function rsaDecrypt(fullBase64Cipher, privateKeyPem, keySizeBits = 1024) {
if (!fullBase64Cipher) return "";
// 规范化 base64(去空白、url-safe、补齐)
const normalized = normalizeBase64(fullBase64Cipher);
// 把整体 base64 解成 bytes,再按密钥字节长度分片(例如 1024 -> 128 字节)
const keyByteLen = Math.floor(keySizeBits / 8);
const cipherBytes = base64ToUint8Array(normalized);
const decryptor = new JSEncrypt();
decryptor.setPrivateKey(privateKeyPem);
let offset = 0;
const parts = [];
while (offset < cipherBytes.length) {
const end = Math.min(offset + keyByteLen, cipherBytes.length);
const chunk = cipherBytes.subarray(offset, end);
// 单块重新 base64 编码,传给 jsencrypt.decrypt(它期望单块 base64 密文)
const chunkB64 = uint8ArrayToBase64(chunk);
const decrypted = decryptor.decrypt(chunkB64);
if (decrypted === false || decrypted === null) {
throw new Error(`RSA 解密失败(chunk offset=${offset})—— 请确认私钥与公钥匹配、分片长度设置是否正确。`);
}
parts.push(decrypted);
offset += keyByteLen;
}
// 拼接所有分片明文
return parts.join("");
}JAVA的工具类
public class RsaUtils {
/**
* 生成密钥对
*
* @return
*/
public static Map<String, String> generateRasKey() {
Map<String, String> rs = new HashMap<>();
try {
KeyPairGenerator keyPairGen = null;
keyPairGen = KeyPairGenerator.getInstance("RSA");
keyPairGen.initialize(1024, new SecureRandom());
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
rs.put("public_key", publicKeyString);
rs.put("private_key", privateKeyString);
} catch (Exception e) {
throw new RuntimeException("RsaUtils 生成公钥失败...");
}
return rs;
}
/**
* 公钥加密
*
* @param str
* @param publicKey
* @return 密文
*/
public static String publicKeyEncrypt(String str, String publicKey) throws Exception {
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
//当长度过长的时候,需要分割后加密 117个字节
byte[] resultBytes = getMaxResultEncrypt(str, cipher);
String outStr = Base64.encodeBase64String(resultBytes);
return outStr;
}
/**
* 分组加密
*
* @param str
* @param cipher
* @return
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
private static byte[] getMaxResultEncrypt(String str, Cipher cipher) throws IllegalBlockSizeException, BadPaddingException {
byte[] inputArray = str.getBytes(StandardCharsets.UTF_8);
int inputLength = inputArray.length;
int MAX_ENCRYPT_BLOCK = 117;
int offSet = 0;
byte[] resultBytes = {};
byte[] cache = {};
while (inputLength - offSet > 0) {
if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
offSet += MAX_ENCRYPT_BLOCK;
} else {
cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
offSet = inputLength;
}
resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
}
return resultBytes;
}
/**
* 私钥解密
*/
public static String privateKeyDecrypt(String str, String privateKey) {
try {
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
String outStr = new String(getMaxResultDecrypt(str, cipher));
return outStr;
} catch (Exception e) {
return "";
}
}
/**
* 分组解密
*
* @param str
* @param cipher
* @return
* @throws IllegalBlockSizeException
* @throws BadPaddingException
* @throws UnsupportedEncodingException
*/
private static byte[] getMaxResultDecrypt(String str, Cipher cipher) throws IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
byte[] inputArray = Base64.decodeBase64(str.getBytes("UTF-8"));
int inputLength = inputArray.length;
int MAX_ENCRYPT_BLOCK = 128;
int offSet = 0;
byte[] resultBytes = {};
byte[] cache = {};
while (inputLength - offSet > 0) {
if (inputLength - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(inputArray, offSet, MAX_ENCRYPT_BLOCK);
offSet += MAX_ENCRYPT_BLOCK;
} else {
cache = cipher.doFinal(inputArray, offSet, inputLength - offSet);
offSet = inputLength;
}
resultBytes = Arrays.copyOf(resultBytes, resultBytes.length + cache.length);
System.arraycopy(cache, 0, resultBytes, resultBytes.length - cache.length, cache.length);
}
return resultBytes;
}
}