本文目录导读:

这是一个非常经典的混合加密方案(Hybrid Encryption),在单次接口通信中,RSA(非对称加密)用于安全传输AES的密钥,AES(对称加密)用于高效加密实际数据。
下面我会提供一个标准的、高安全性的实现流程,包含 Node.js 和浏览器端的示例代码。
核心逻辑
- 客户端:生成一个随机的 AES 密钥。
- 客户端:用这个 AES 密钥加密请求体的
JSON数据(得到encryptedData)。 - 客户端:用服务端的 RSA 公钥 加密这个 AES 密钥(得到
encryptedKey)。 - 客户端:将
encryptedKey和encryptedData一起发送给服务端。 - 服务端:用自己的 RSA 私钥解密
encryptedKey,得到 AES 密钥。 - 服务端:用这个 AES 密钥解密
encryptedData,得到原始请求数据。
准备 RSA 密钥对 (服务端)
服务端需要生成 2048 位或更长的 RSA 密钥对。
# 生成私钥 openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048 # 从私钥导出公钥 openssl rsa -pubout -in private_key.pem -out public_key.pem
private_key.pem: 放在服务端,绝对保密。public_key.pem: 分发给客户端(前端/移动端)。
客户端加密 (以浏览器/Node.js为例)
我们使用 Web Crypto API (浏览器) 或 Node.js crypto 模块。
依赖库安装 (Node.js 环境)
npm install node-forge # 或者使用内置 crypto 模块,但 forje 在浏览器端更容易移植
客户端加密代码 (JavaScript)
// 假设你已经从服务器获取了 RSA 公钥字符串
const rsaPublicKeyPem = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr...
-----END PUBLIC KEY-----`;
// 1. 生成 AES 密钥 (随机 256位)
function generateAESKey() {
// 使用 Web Crypto API (浏览器环境)
if (window.crypto && window.crypto.subtle) {
return crypto.subtle.generateKey({
name: "AES-CBC",
length: 256
}, true, ["encrypt", "decrypt"]);
}
// 回退到 Node.js (这里不演示,因为浏览器更常见)
throw new Error("Crypto API not supported");
}
// 2. 使用 AES 加密数据
async function encryptDataWithAES(aesKey, plaintext) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(plaintext)); // 先转JSON
const iv = crypto.getRandomValues(new Uint8Array(16)); // 初始化向量,非常重要
const encrypted = await crypto.subtle.encrypt({
name: "AES-CBC",
iv: iv
}, aesKey, data);
// 返回 IV 和密文的 base64 拼接 (或者分开传)
return {
iv: arrayBufferToBase64(iv),
data: arrayBufferToBase64(encrypted)
};
}
// 3. 使用 RSA 公钥加密 AES 密钥
async function encryptAESKeyWithRSA(rsaPublicKeyPem, aesKey) {
// 1. 导入公钥
const publicKey = await importRSAPublicKey(rsaPublicKeyPem);
// 2. 导出 AES 密钥的原始字节
const rawAesKey = await crypto.subtle.exportKey("raw", aesKey);
// 3. 使用 RSA-OAEP 加密 (推荐)
const encryptedKey = await crypto.subtle.encrypt({
name: "RSA-OAEP"
}, publicKey, rawAesKey);
return arrayBufferToBase64(encryptedKey);
}
// 辅助函数: 导入 RSA 公钥
async function importRSAPublicKey(pem) {
const pemHeader = "-----BEGIN PUBLIC KEY-----";
const pemFooter = "-----END PUBLIC KEY-----";
const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length);
const binaryDer = base64ToArrayBuffer(pemContents);
return await crypto.subtle.importKey(
"spki",
binaryDer,
{
name: "RSA-OAEP",
hash: "SHA-256"
},
false,
["encrypt"]
);
}
// 辅助函数: ArrayBuffer 转 Base64
function arrayBufferToBase64(buffer) {
let binary = '';
const bytes = new Uint8Array(buffer);
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
}
function base64ToArrayBuffer(base64) {
const binaryString = atob(base64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
// 主流程: 加密请求
async function encryptRequest(payload) {
const aesKey = await generateAESKey();
const encryptedPayload = await encryptDataWithAES(aesKey, payload);
const encryptedKey = await encryptAESKeyWithRSA(rsaPublicKeyPem, aesKey);
// 发送给服务端的数据结构
return {
key: encryptedKey, // RSA 加密后的 AES 密钥
iv: encryptedPayload.iv,
data: encryptedPayload.data
};
}
// 使用示例
const requestData = { userId: 123, amount: 100.00, timestamp: Date.now() };
encryptRequest(requestData).then(encryptedBody => {
// 发送到 /api/secure-transfer
fetch('/api/secure-transfer', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(encryptedBody)
});
});
服务端解密 (以 Node.js 为例)
使用 Node.js 内置的 crypto 模块。
const crypto = require('crypto');
const fs = require('fs');
// 加载私钥
const privateKey = fs.readFileSync('./private_key.pem', 'utf8');
function decryptRequest(encryptedBody) {
const { key: encryptedKeyB64, iv: ivB64, data: encryptedDataB64 } = encryptedBody;
// 1. 用 RSA 私钥解密 AES 密钥
const decryptedKey = crypto.privateDecrypt(
{
key: privateKey,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256'
},
Buffer.from(encryptedKeyB64, 'base64')
);
// 2. 创建 AES 解密器
const decipher = crypto.createDecipheriv(
'aes-256-cbc',
decryptedKey,
Buffer.from(ivB64, 'base64')
);
// 3. 解密数据
let decrypted = decipher.update(Buffer.from(encryptedDataB64, 'base64'));
decrypted = Buffer.concat([decrypted, decipher.final()]);
// 4. 解析 JSON
return JSON.parse(decrypted.toString('utf8'));
}
// 示例 Express 路由
app.post('/api/secure-transfer', (req, res) => {
try {
const originalData = decryptRequest(req.body);
// 正常处理业务逻辑
console.log('解密后的数据:', originalData);
res.json({ status: 'success', data: originalData });
} catch (error) {
console.error('解密失败:', error.message);
res.status(400).json({ status: 'error', message: '解密失败' });
}
});
安全要点 (必须注意)
| 问题 | 解决方案 |
|---|---|
| 中间人攻击 (MITM) | 必须使用 HTTPS,RSA 只加密了 AES 密钥,但公钥分发过程若被篡改,攻击者可以替换自己的公钥。HTTPS 是基础。 |
| RSA 密钥长度 | 建议使用 2048位 或 4096位,4096更安全但加密速度慢。 |
| AES 加密模式 | 推荐 AES-256-GCM (比 CBC 更安全,自带认证标签防止篡改),示例使用了 CBC,如果换成 GCM 需额外处理 authTag。 |
| 初始化向量 (IV) | 必须 随机生成,且每次请求不同。 |
| 填充方式 (Padding) | RSA 使用 OAEP (推荐) 而非 PKCS#1 v1.5,AES 使用标准填充 (PKCS7)。 |
| 重放攻击 (Replay) | 在明文数据中加入 timestamp 和 nonce (一次性随机数),服务端校验时间差和是否已使用过 nonce。 |
| 密钥存储 | 私钥使用环境变量或密钥管理服务 (KMS),不要硬编码在代码里。 |
这套混合加密方案是目前最实用的接口加密方式之一:
- RSA 解决密钥传输的安全问题(无法被窃听)。
- AES 解决大数据的加密效率问题。
- HTTPS 解决身份验证和公钥完整性验证问题。
这三个共同构成了一个相对可靠的加密传输通道。