加密与数据安全
What — 是什么
加密与数据安全是 Node.js 服务端保护敏感数据的核心能力,涵盖
crypto模块的使用、对称/非对称加密、哈希签名、HTTPS/TLS 配置和密钥管理。
核心概念:
- 对称加密:加解密使用同一密钥,速度快,适合大数据量加密(AES-256-GCM)
- 非对称加密:公钥加密、私钥解密,或私钥签名、公钥验证,速度慢,适合密钥交换和签名(RSA/ECDSA)
- 哈希:单向不可逆,用于密码存储和数据完整性校验(bcrypt/scrypt/SHA-256)
- 数字签名:私钥签名 + 公钥验证,确保数据未被篡改且来源可信
- HTTPS/TLS:传输层加密,防止中间人窃听和篡改
- 密钥管理:密钥的生成、存储、轮换和撤销策略
关键特性:
crypto.createCipheriv()对称加密,crypto.createDecipheriv()解密crypto.generateKeyPairSync()生成 RSA/ECDSA 密钥对crypto.createSign()签名,crypto.createVerify()验证bcrypt.hash()密码哈希(自动加盐),bcrypt.compare()验证https.createServer()创建 HTTPS 服务器
Why — 为什么
适用场景:
- 密码存储:哈希 + 盐,不可逆
- 数据加密:敏感字段(身份证/银行卡)入库前加密
- API 签名验证:确保请求未被篡改
- HTTPS 配置:传输层加密
- JWT 签名:确保 Token 不被伪造
对比加密方案:
| 维度 | 对称加密(AES) | 非对称加密(RSA) | 哈希(bcrypt) |
|---|---|---|---|
| 方向 | 双向(加解密) | 双向(公私钥) | 单向(不可逆) |
| 速度 | 快 | 慢 | 中 |
| 密钥 | 1个共享密钥 | 公钥+私钥 | 无密钥(用盐) |
| 适用 | 大数据加密 | 密钥交换/签名 | 密码存储 |
优缺点:
- ✅ 优点:
- AES-GCM 提供加密+完整性校验
- bcrypt 自适应加盐,抗暴力破解
- Node.js crypto 模块功能完善
- ❌ 缺点:
- 密钥管理复杂,泄露等于防线失守
- 非对称加密速度慢,不适合大数据
- 加密增加系统复杂度和性能开销
How — 怎么用
快速上手
const crypto = require('crypto');
const bcrypt = require('bcrypt');
// 密码哈希
const hash = await bcrypt.hash('password123', 12);
const match = await bcrypt.compare('password123', hash);
// 对称加密
const key = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const encrypted = Buffer.concat([cipher.update('secret data', 'utf8'), cipher.final()]);
const authTag = cipher.getAuthTag();
代码示例
对称加密 + 非对称加密 + 签名:
const crypto = require('crypto');
// 对称加密(AES-256-GCM)
function encrypt(text, key) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const encrypted = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
const authTag = cipher.getAuthTag();
return { iv: iv.toString('hex'), encrypted: encrypted.toString('hex'), authTag: authTag.toString('hex') };
}
function decrypt(encData, key) {
const decipher = crypto.createDecipheriv(
'aes-256-gcm', key,
Buffer.from(encData.iv, 'hex')
);
decipher.setAuthTag(Buffer.from(encData.authTag, 'hex'));
const decrypted = Buffer.concat([
decipher.update(Buffer.from(encData.encrypted, 'hex')),
decipher.final()
]);
return decrypted.toString('utf8');
}
// 非对称加密(RSA)
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});
// 公钥加密、私钥解密
const encrypted = crypto.publicEncrypt(publicKey, Buffer.from('secret message'));
const decrypted = crypto.privateDecrypt(privateKey, encrypted);
// 私钥签名、公钥验证
const sign = crypto.createSign('SHA256');
sign.update('data to sign');
const signature = sign.sign(privateKey, 'hex');
const verify = crypto.createVerify('SHA256');
verify.update('data to sign');
const isValid = verify.verify(publicKey, signature, 'hex');
HTTPS 服务器配置:
const https = require('https');
const fs = require('fs');
const options = {
key: fs.readFileSync('server-key.pem'),
cert: fs.readFileSync('server-cert.pem'),
ca: fs.readFileSync('ca-cert.pem'),
minVersion: 'TLSv1.2',
ciphers: 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256',
honorCipherOrder: true
};
const server = https.createServer(options, app);
server.listen(443);
// HTTP → HTTPS 重定向
const http = require('http');
http.createServer((req, res) => {
res.writeHead(301, { Location: `https://${req.headers.host}${req.url}` });
res.end();
}).listen(80);
常见问题与踩坑
| 问题 | 原因 | 解决方案 |
|---|---|---|
| IV 重复导致密文可预测 | 每次加密必须用随机 IV | crypto.randomBytes(16) 生成 |
| 密钥硬编码在代码中 | 泄露风险 | 环境变量或密钥管理服务 |
| GCM authTag 校验失败 | 数据被篡改或 IV/Key 不匹配 | 检查传输完整性 |
| bcrypt 哈希耗时 | 故意慢(抗暴力破解) | 调整 cost factor(10-12 合理) |
| TLS 1.0/1.1 不安全 | 已弃用的协议版本 | minVersion: 'TLSv1.2' |
最佳实践
- 密码存储用 bcrypt/scrypt,不用 MD5/SHA
- 对称加密用 AES-256-GCM(含完整性校验)
- 密钥从环境变量或密钥管理服务加载,不硬编码
- HTTPS 强制 TLS 1.2+,禁用弱密码套件
- IV 每次加密随机生成,与密文一起存储
- 密钥定期轮换,旧密钥安全销毁
面试题
Q1: 对称加密和非对称加密的区别?
对称加密:加解密用同一密钥,速度快,适合大数据。常用 AES-256-GCM。非对称加密:公钥加密私钥解密(或反向),速度慢(比 AES 慢 1000 倍+),适合密钥交换和签名。常用 RSA-2048/ECDSA。实际混合使用:非对称加密交换对称密钥,再用对称密钥加密数据(HTTPS/TLS 就是这个模式)。
Q2: 为什么密码存储用 bcrypt 而不是 SHA-256?
三个原因:① bcrypt 自适应加盐——每个密码自动生成独立盐值,无需额外存储;② bcrypt 可调节计算成本——
cost factor控制哈希迭代次数,硬件升级后提高 cost 保持抗暴力破解能力;③ bcrypt 设计目标就是慢——SHA-256 设计目标是快(用于签名校验),快意味着暴力破解也快。bcrypt 的慢是故意的设计,10 次迭代约 100ms,SHA-256 单次微秒级。
Q3: AES-GCM 中的 AuthTag 是什么?
AuthTag(认证标签)是 GCM 模式提供的完整性校验值(16 字节)。加密时 GCM 同时计算密文和认证标签;解密时重新计算标签并与附带的标签比对,不匹配则说明数据被篡改。这是 GCM 相比 CBC 模式的核心优势——CBC 只加密不认证,需要额外的 HMAC 做完整性校验(Encrypt-then-MAC),GCM 一步完成。没有 AuthTag 的密文无法验证完整性。
Q4: HTTPS/TLS 握手过程?
TLS 1.3 握手(简化):① ClientHello——客户端发送支持的 TLS 版本和密码套件;② ServerHello——服务端选择版本和套件,发送证书;③ 密钥交换——客户端验证证书,生成预主密钥,用服务端公钥加密发送(或用 ECDHE 计算共享密钥);④ 完成——双方用预主密钥派生会话密钥,后续通信用对称加密。TLS 1.3 将握手从 2-RTT 缩短到 1-RTT,0-RTT 模式用于重连。
相关链接:
- 认证与授权
- Web安全防护
- HTTP服务构建
- Node.js crypto 文档:https://nodejs.org/api/crypto.html