如何使用RSA+AES进行混合加密。
在Node.js 中使用RSA+AES混合加密通讯
加密方法
首先简单介绍一下什么是RSA和AES
RSA 和 AES 是两种常用的加密算法,它们在密码学中扮演着重要的角色,用于保护数据的安全性和完整性。它们的主要区别在于加密方式:RSA 是非对称加密算法,而 AES 是对称加密算法。
1. AES (Advanced Encryption Standard) 高级加密标准
- 类型: 对称加密
- 密钥: 使用相同的密钥进行加密和解密。
- 速度: 加密和解密速度快,效率高。
- 安全性: 在密钥长度足够长的情况下(128位、192位或256位),安全性高。
- 应用场景: 适用于大量数据加密,例如文件加密、数据库加密、网络传输加密(如 VPN、TLS/SSL 中的数据加密部分)。
2. RSA (Rivest–Shamir–Adleman) 算法
- 类型: 非对称加密
- 密钥: 使用一对密钥:公钥和私钥。公钥用于加密,私钥用于解密。
- 速度: 加密和解密速度相对较慢,计算复杂度较高。
- 安全性: 基于大数分解的数学难题,在密钥长度足够长的情况下(通常为 2048 位、3072 位或 4096 位),安全性高。
- 应用场景: 适用于少量数据加密,例如数字签名、密钥交换、身份验证。
使用场景
在日常项目中 如果只是服务端加密解密,那么两个的安全性都是没问题的,但是如果涉及到客户端的加密,那么单独使用AES就要考虑密钥的安全性,单独使用RSA就要考虑大文件的加密性能。那么使用混合加密就能解决各自的痛点。
如何进行混合加密
具体步骤如下:
- 服务端生成RSA公钥和私钥,并保存在本地
- 客户端加密时发起请求,服务端将公钥发送给客户端
- 客户端生成AES随机密钥
- 客户端根据RSA公钥加密AES随机密钥
- 客户端使用AES加密数据
- 发起请求 上传AES加密密钥和加密数据
- 服务端解密
下文将使用具体代码进行步骤的说明, 本文使用node.js express.js 进行服务端开发 客户端使用
crypto-js
库进行 AES加密node-forge
进行随机密钥的生成和RSA的加解密
1.生成RSA密钥
使用node.js中内置的crypto 模块就可生成RSA公钥和私钥
import crypto from "crypto";
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
});
2.服务端将公钥发送给客户端
将公钥通过接口发送给客户端,这里的公钥需要通过调用export({ type: "pkcs1", format: "pem" })
进行格式化导出
app.get("/rsaKey", async (req, res) => {
res.setHeader("Content-Type", "text/plain");
res.send(
successResponse(rsaPublicKey.export({ type: "pkcs1", format: "pem" })),
);
});
3.客户端生成AES随机密钥
使用node-forge生成随机的AES密钥 这里是生成32字节(256位)
的随机字符串作为AES密钥
import forge from "node-forge";
const aesKey = forge.random.getBytesSync(32);
4.RSA公钥加密AES随机密钥
serverPublicKey
为服务端发送过来的RSA公钥,这里使用forge.pki.publicKeyFromPem(serverPublicKey);
将其转换成公钥对象,使用encrypt
进行加密,这里的加密参数需要和服务端解密时设置匹配。
这里的 this.key
和this.iv
都是接下来AES加密所需要的参数 通过 CryptoJS
提供的方法转换成对象
import CryptoJS from "crypto-js";
import forge from "node-forge";
const { serverPublicKey } = opt;
const aesKey = forge.random.getBytesSync(32);
if (serverPublicKey) {
const publicKey = forge.pki.publicKeyFromPem(serverPublicKey);
this.rsaEncrypt = publicKey.encrypt(aesKey, "RSA-OAEP", {
md: forge.md.sha256.create(),
});
// 转换成AES密钥对象
this.key = CryptoJS.enc.Base64.parse(forge.util.encode64(aesKey));
// iv是AES加密时的偏移值 这里使用了固定值,也可以像密钥一样使用公钥加密后传至服务器增加安全性
this.iv = CryptoJS.enc.Utf8.parse("1234567890abcdef");
} else {
throw new Error("Unsupported encryption key");
}
5.AES加密数据
这里的加密参数和服务端AES解密时要对应
import { AES } from "crypto-js";
function getOptions() {
return {
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
iv: this.iv,
};
}
// AES 加密
encryptByAES(cipherText: string) {
return AES.encrypt(cipherText, this.key || "", this.getOptions()).toString();
}
6.上传加密数据和加密AES密钥
客户端发送加密数据和加密后的AES密钥到服务端
const encryption = new AesEncryption({
serverPublicKey: rsaKey,
});
const password = encryption.encryptByAES(data.password);
userRegister({
...data, // 其他无需加密的数据
key: encryption.getKey,
password,
})
服务端获取加密后的AES密钥
// 在class 中使用get直接获取加密后的AES密钥
get getKey() {
try {
return forge.util.encode64(this.rsaEncrypt);
} catch (e) {
console.log(e);
}
}
7.服务端 AES解密数据
调用aesDecrypt
传入加密数据和加密的AES密钥
使用decryptAESKey
RSA解密获取AES密钥
// 配置加密参数
const algorithm = "aes-256-cbc"; // 加密算法
const iv = Buffer.from("1234567890abcdef", "utf8"); // 16字节的初始化向量
// RSA解密 AES 密钥
function decryptAESKey(encryptedKey) {
const buffer = Buffer.from(encryptedKey, "base64");
return crypto.privateDecrypt(
{
key: privateKey,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: "sha256",
},
buffer,
);
}
// 解密函数
export const aesDecrypt = (encryptedText, key) => {
try {
console.log("RSA解密前", key);
const aesKey = decryptAESKey(key);
console.log("RSA解密后的AES Key", aesKey.toString("base64"));
const decipher = crypto.createDecipheriv(algorithm, aesKey, iv);
let decrypted = decipher.update(encryptedText, "base64", "utf8");
decrypted += decipher.final("utf8");
console.log("解密完成:", decrypted);
return decrypted;
} catch (e) {
console.log(e);
}
}
测试调用
客户端 打印
服务端 打印
可以看到解密出来的数据和加密前的一致,需要注意加密过程和解密过程中 crypto 库的各种格式需要匹配 注意使用base64编码加密的也需要转成base64后进行解密
文章中项目的源码地址 node-express-serve