Node.js 中的 RSA 加密、解密、签名与验证详解
Node.js 中的 RSA 加密、解密、签名与验证详解
一、背景与问题
在分布式系统、API 接口安全、数据完整性校验等场景中,RSA 加密算法是保障通信安全的重要技术。其核心价值在于通过非对称加密机制实现以下功能:
- 加密解密:用公钥加密数据,私钥解密(适合小数据量)
- 数字签名:用私钥生成签名,公钥验证签名(确保数据完整性)
- 身份认证:通过公钥/私钥对建立信任关系
但实际开发中常遇到以下问题:
- 密钥生成不规范导致安全漏洞
- 加密/解密过程中的 padding 方式选择错误
- 签名验证时忽略哈希算法配置
- 密钥管理不当引发私钥泄露风险
- 性能瓶颈:RSA 加密速度远低于 AES
二、基本原理
1. 数学基础
RSA 基于大整数分解的难度,其核心步骤如下:
- 选择两个大素数 p、q
- 计算模数 n = p × q
- 计算欧拉函数 φ(n) = (p-1)(q-1)
- 选择公钥指数 e(1 < e < φ(n),且与 φ(n) 互质)
- 计算私钥指数 d(满足 d × e ≡ 1 mod φ(n))
公钥为 (n, e),私钥为 (n, d)。加密过程为:C = M^e mod n,解密过程为:M = C^d mod n。
2. 填充机制
RSA 加密必须配合填充方案,常见的有:
- PKCS1 v1.5(旧标准)
- OAEP(推荐标准,支持随机性)
不同填充方式对安全性影响显著,错误配置会导致加密数据不可逆。
三、环境准备
# 安装依赖(若使用第三方库)
npm install node-rsa四、核心实现
1. 密钥生成(Node.js 内置模块)
const { createPrivateKey, createPublicKey } = require('crypto');
// 生成 2048 位 RSA 密钥对
const keyPair = {
publicKey: createPublicKey({
key: '-----BEGIN PUBLIC KEY-----\n' +
'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n' +
'-----END PUBLIC KEY-----\n'
}),
privateKey: createPrivateKey({
key: '-----BEGIN PRIVATE KEY-----\n' +
'MIICXQIBAAKBgQC...\n' +
'-----END PRIVATE KEY-----\n'
})
};关键点说明:
- 使用 PEM 格式的密钥文件时,必须包含头部和尾部标记
- 私钥文件需要使用
-----BEGIN PRIVATE KEY-----前缀 - 密钥生成需使用安全随机数生成器(Node.js 内置的
crypto模块)
2. 加密解密实现
const { encrypt, decrypt } = require('node-rsa');
// 加密
const encrypted = keyPair.publicKey.encrypt('Hello World!', 'base64');
// 解密
const decrypted = keyPair.privateKey.decrypt(encrypted, 'utf8');
console.log(decrypted); // 输出 Hello World性能对比:
- RSA 加密速度:约 10ms/KB(2048 位)
- AES 加密速度:约 1ms/KB(AES-256)
3. 签名验证实现
const { sign, verify } = require('node-rsa');
// 签名
const signature = keyPair.privateKey.sign('Hello World!', 'base64');
// 验证
const isValid = keyPair.publicKey.verify(signature, 'Hello World!', 'utf8');
console.log(isValid); // 输出 true关键参数说明:
sign方法需要指定哈希算法(默认 SHA-1)verify方法需严格匹配原始数据
五、完整案例:安全API接口
1. 项目结构
./
├── index.js
├── config/
│ └── keys.js
└── utils/
└── security.js2. 密钥管理(keys.js)
// config/keys.js
const fs = require('fs');
const path = require('path');
const privateKeyPath = path.join(__dirname, '..', 'keys', 'private.pem');
const publicKeyPath = path.join(__dirname, '..', 'keys', 'public.pem');
const privateKey = fs.readFileSync(privateKeyPath, 'utf8');
const publicKey = fs.readFileSync(publicKeyPath, 'utf8');
module.exports = {
privateKey,
publicKey
};3. 安全验证工具(security.js)
const { createPublicKey } = require('crypto');
const { verify } = require('node-rsa');
function verifySignature(data, signature, publicKey) {
const publicKeyObj = createPublicKey({
key: publicKey
});
return publicKeyObj.verify(signature, data, 'utf8');
}4. 主程序(index.js)
const express = require('express');
const { privateKey, publicKey } = require('./config/keys');
const app = express();
app.post('/secure', (req, res) => {
const data = req.body.data;
const signature = req.headers['x-signature'];
if (!verifySignature(data, signature, publicKey)) {
return res.status(401).json({ error: 'Invalid signature' });
}
res.json({ message: 'Valid request' });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});安全增强建议:
- 使用 HTTPS 传输加密数据
- 在签名时添加时间戳防止重放攻击
- 对敏感数据进行二次加密(如 AES 加密后使用 RSA 加密密钥)
六、源码解析
1. 密钥生成源码(node-rsa)
// node-rsa 的核心实现(简化版)
function generateKeyPair(bits) {
const p = generatePrime(bits / 2);
const q = generatePrime(bits / 2);
const n = p * q;
const phi = (p - 1) * (q - 1);
const e = 65537; // 常用公钥指数
const d = modularInverse(e, phi);
return {
n,
e,
d
};
}2. 加密过程(node-rsa)
function encrypt(data, publicKey) {
const n = publicKey.n;
const e = publicKey.e;
// 将明文转换为整数(需要处理大数)
const m = stringToInteger(data);
// RSA 加密算法
const c = modPow(m, e, n);
return integerToString(c);
}关键点:
- 需要处理大数运算(使用
bigint或第三方库) - 填充方案直接影响安全性(建议使用 OAEP)
七、进阶使用
1. 与对称加密结合使用
const crypto = require('crypto');
// 使用 AES 加密数据
const cipher = crypto.createCipher('AES-256-CBC', 'secret-key');
let encrypted = cipher.update('Hello World', 'utf8', 'base64');
encrypted += cipher.final('base64');
// 使用 RSA 加密 AES 密钥
const rsa = new nodeRSA(2048);
const encryptedKey = rsa.encrypt('secret-key', 'base64');2. 多密钥管理
const { KeyStore } = require('node-rsa');
// 创建密钥存储
const keyStore = new KeyStore();
keyStore.addPrivateKey('my-key', '-----BEGIN PRIVATE KEY-----\n...');
// 从存储中获取密钥
const key = keyStore.getPrivateKey('my-key');八、性能与工程实践
1. 性能优化策略
| 优化手段 | 说明 |
|---|---|
| 使用缓存 | 缓存常用公钥减少重复生成 |
| 异步处理 | 避免阻塞主线程 |
| 密钥分层 | 分离加密密钥和签名密钥 |
2. 异常处理规范
try {
const decrypted = keyPair.privateKey.decrypt(encrypted, 'utf8');
} catch (err) {
console.error('Decryption error:', err.message);
// 处理无效密文、密钥不匹配等异常
}3. 安全防护措施
- 使用
crypto模块的createHash实现可配置哈希算法 - 对签名数据进行完整性校验(如 SHA-256)
- 密钥存储时使用 AES-256 加密
九、常见问题与踩坑
1. 常见错误及解决办法
| 错误场景 | 错误信息 | 解决方案 |
|---|---|---|
| 密钥格式错误 | 'Invalid PEM format' | 确保 PEM 头尾标记完整 |
| 填充方式不匹配 | 'Padding scheme mismatch' | 确保加密/解密使用相同 padding |
| 私钥泄露 | 'Private key exposed' | 使用环境变量存储密钥 |
2. 常见陷阱
- 使用弱随机数生成密钥(如
Math.random()) - 忽略证书有效期检查
- 在签名时未处理特殊字符(如换行符)
十、最佳实践
1. 推荐方案
密钥管理:
- 使用环境变量存储密钥
- 密钥文件应存储在
.env目录并设置文件权限 - 避免硬编码在代码中
填充方案:
- 推荐使用 OAEP 填充(
node-rsa默认支持) - 避免使用 PKCS1 v1.5(易受选择性伪造攻击)
- 推荐使用 OAEP 填充(
性能优化:
- 对称加密处理大部分数据
- 使用 RSA 加密对称密钥
- 避免频繁生成密钥对
2. 安全建议
- 对敏感数据使用 AES-GCM 模式
- 使用 HSTS 强制 HTTPS
- 定期轮换密钥
- 使用证书链验证签名
十一、总结
RSA 加密在 Node.js 中提供了强大的安全保障,但其应用需要深入理解其原理和限制。在实际开发中应遵循以下原则:
- 合理使用:适合加密小数据、数字签名等场景
- 谨慎使用:避免加密大文本、直接使用默认配置
- 安全实践:重视密钥管理、填充方案选择、性能优化
- 持续学习:关注最新密码学进展,定期更新安全策略
通过合理结合对称加密算法,可以构建更安全的通信体系。在实际项目中,应根据具体需求选择合适的加密方案,同时遵循安全最佳实践,确保系统的完整性和保密性。
评论已关闭