# 5分钟入门非对称加密用法
30 秒速记
- 非对称加密 = 一对钥匙:公钥公开,私钥自己藏好
- 保密:公钥加密 → 私钥解密;证明身份:私钥签名 → 公钥验签,两件事别混
RSA只能加密很短的数据(2048位OAEP最多214字节),大数据走混合加密Node.js里全在crypto模块:publicEncrypt/privateDecrypt,签名用crypto.sign/crypto.verify- 密钥至少
2048位,私钥别进代码仓库,加密填充用OAEP,签名用PSS
非对称加密就像一把只能锁不能开的挂锁:公钥是挂锁,谁都可以拿去锁箱子;私钥是唯一的钥匙,只有你能打开。 所以保密的用法是对方用你的公钥加密,你用私钥解密。反过来用私钥处理、公钥校验,就是数字签名,证明东西确实是你发的、没被改过,这两个目的不要混。RSA 很慢,能加密的长度也很短,实际项目里都是混合加密:随机生成一个 AES 密钥加密正文,再用 RSA 公钥加密这个 AES 密钥,HTTPS 的思路也是这样。在 Node.js 里这些都在内置的 crypto 模块里,不用装第三方库。
理解「5分钟入门非对称加密用法」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 security 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
非对称加密真正要记住的只有一张表:
| 目的 | 谁来操作 | 用哪把钥匙 | Node.js 方法 |
|---|---|---|---|
| 保密(只让接收方看到) | 发送方加密 | 接收方的公钥 | crypto.publicEncrypt |
| 接收方解密 | 接收方的私钥 | crypto.privateDecrypt |
|
| 签名(证明是我发的、没被改) | 发送方签名 | 发送方的私钥 | crypto.sign |
| 接收方验签 | 发送方的公钥 | crypto.verify |
下面这段代码不依赖任何外部文件,直接 node demo.js 就能跑,把加密和签名两条路都走了一遍:
const crypto = require('node:crypto');
// 1. 生成一对 2048 位 RSA 密钥
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
});
// 2. 保密:公钥加密,私钥解密(默认就是 OAEP 填充)
const encrypted = crypto.publicEncrypt(publicKey, Buffer.from('你好,世界'));
const decrypted = crypto.privateDecrypt(privateKey, encrypted);
console.log(decrypted.toString()); // 你好,世界
// 3. 签名:私钥签名,公钥验签
const data = Buffer.from('转账 100 元给张三');
const signature = crypto.sign('sha256', data, {
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
});
const ok = crypto.verify('sha256', data, {
key: publicKey,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
}, signature);
console.log(ok); // true
// 4. 内容被改一个字,验签失败
console.log(crypto.verify('sha256', Buffer.from('转账 900 元给张三'), {
key: publicKey,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
}, signature)); // false
实际项目里很少单独用 RSA 加密业务数据,原因有两个:一是慢,比 AES 慢几个数量级;二是长度有上限,2048 位密钥加 OAEP(SHA-1)一次最多加密 256 - 2×20 - 2 = 214 字节,一个中文字符在 UTF-8 里占 3 字节,也就是七十来个汉字。所以标准做法是混合加密:每次随机生成一个 AES 密钥加密正文,再用对方的 RSA 公钥加密这个 AES 密钥,把两者一起发过去。对方先用私钥解出 AES 密钥,再解正文。
面试官追问
追问 1公钥加密的消息私钥能解开,能证明消息是某个用户发的吗?
不能。公钥是公开的,谁都能用它加密,解开只说明消息是发给你的,说明不了是谁发的。要确认发送方,得让对方用他的私钥签名,你用他的公钥验签。
追问 2能不能用 RSA 直接加密用户上传的 10MB 文件?
不行。RSA 一次能加密的长度受密钥大小限制,2048 位配 OAEP 最多 214 字节,超了直接报错。正确做法是 AES-256-GCM 加密文件,RSA 只加密那个 32 字节的随机密钥。
追问 3示例里的私钥直接写在 keys.js 里提交到仓库,有什么问题?
私钥一旦进了 git 历史,删文件也没用,等于公开了。私钥要放环境变量、密钥管理服务或部署时挂载的文件,权限只给服务进程,泄露后能换钥匙。
追问 4怎么在 Node.js 里现场生成一对密钥?
crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } }),拿到的就是 PEM 格式的公钥和私钥字符串。1024 位已经不安全了,别再用。
# 加密、解密方法
30 秒速记
- 加密:
crypto.publicEncrypt(publicKey, buffer),解密:crypto.privateDecrypt(privateKey, buffer) - 入参和返回值都是
Buffer,跨JSON传输要转base64 publicEncrypt默认填充就是RSA_PKCS1_OAEP_PADDING,两端填充和哈希必须一致- 新版
Node.js的privateDecrypt已禁用PKCS#1 v1.5填充(Marvin攻击),老密文要迁移 privateEncrypt/publicDecrypt不是加密,要做签名请用crypto.sign/crypto.verify
Node.js 的非对称加解密就两个函数:crypto.publicEncrypt 用公钥加密,crypto.privateDecrypt 用私钥解密,参数和返回值都是 Buffer。 key 参数可以直接传 PEM 字符串,也可以传对象指定填充方式,比如 { key, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' }。默认填充已经是 OAEP,问题一般出在两端不一致:前端用了 PKCS#1 v1.5 或 SHA-256,后端用默认的 SHA-1,结果就是解密报错。密文要放进 JSON 或数据库,记得先 toString('base64'),取出来再 Buffer.from(str, 'base64')。
const crypto = require('node:crypto');
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 });
const opts = { padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' };
const cipher = crypto.publicEncrypt({ key: publicKey, ...opts }, Buffer.from('hello'));
const b64 = cipher.toString('base64'); // 存库 / 传 JSON 用这个
const plain = crypto.privateDecrypt({ key: privateKey, ...opts }, Buffer.from(b64, 'base64'));
console.log(plain.toString()); // hello
在Node.js中,负责安全的模块是crypto。非对称加密中,公钥加密,私钥解密,加解密对应的API分别如下。
加密函数:
crypto.publicEncrypt(key, buffer)
解密函数:
crypto.privateDecrypt(privateKey, buffer)
