Web 开发里的 5 种加密算法:原理与代码
做 Web 开发,迟早会碰到'加密'这件事。很多时候问题不在于要不要加密,而在于你到底是在做传输保护、数据保密、完整性校验,还是密码存储。把这几类需求混在一起,选型就容易出错。
下面这五种算法是 Web 场景里最常见的:AES、RSA、SHA-256、HMAC 和 PBKDF2。它们不是一类东西,解决的问题也完全不同。
1. AES:对称加密,适合处理大块数据
AES 用同一把密钥完成加密和解密,速度快,适合真正的数据加密。它的内部结构是替代-置换网络(SPN),核心流程是密钥扩展、若干轮变换,最后输出密文。
常见密钥长度是 128、192、256 位,对应的轮数分别是 10、12、14。实际项目里,AES-256 用得很多,但别把'位数更大'理解成'什么场景都更好',它只是更适合强度要求高的场景,性能也会稍慢一点。
适合的场景主要是:
- HTTPS 之外的业务数据加密
- 数据库敏感字段保护
- 文件加密存储
Node.js 里可以这样写:
const crypto = require('crypto');
// AES-256-CBC 加密
function encrypt(text, key, iv) {
const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv);
let encrypted = cipher.update(text);
encrypted = Buffer.concat([encrypted, cipher.final()]);
return encrypted.toString('hex');
}
// AES-256-CBC 解密
function decrypt(encryptedText, key, iv) {
const encryptedBuffer = Buffer.from(encryptedText, 'hex');
const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv);
let decrypted = decipher.update(encryptedBuffer);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}
// 使用示例
const key = crypto.randomBytes(32); // 256 位密钥
const iv = crypto.randomBytes(16); // 初始向量
const message = 'Secret Message';
const encrypted = encrypt(message, key, iv);
console.log('Encrypted:', encrypted);
const decrypted = decrypt(encrypted, key, iv);
console.log('Decrypted:', decrypted);
2. RSA:非对称加密,别拿它去加大文件

RSA 基于大整数分解难题。它的强项不是'快',而是'公钥加密、私钥解密'这套分工很适合密钥交换和签名。
它的流程大致是:先选两个大素数 p、q,算出 n 和 φ(n),再选出公钥指数 e,最后求出私钥指数 d。加密和解密分别是模幂运算:
- 加密:c ≡ mᵉ mod n
- 解密:m ≡ cᵈ mod n
这类算法在 TLS 握手、数字签名、小数据加密里很常见。真要拿来加密长文本或大文件,效率会很难看,通常只负责加密一个对称密钥,再把真正的数据交给 AES。
Node.js 示例:
const crypto = require('crypto');
// 生成 RSA 密钥对
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048, // 密钥长度
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});
// RSA 加密
function rsaEncrypt(data, publicKey) {
return crypto.publicEncrypt({
key: publicKey,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256'
}, Buffer.from(data)).toString('base64');
}
// RSA 解密
function rsaDecrypt(encryptedData, privateKey) {
return crypto.privateDecrypt({
key: privateKey,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256'
}, Buffer.from(encryptedData, 'base64')).toString();
}
// 使用示例
const message = 'Confidential Data';
const encrypted = rsaEncrypt(message, publicKey);
console.log('RSA Encrypted:', encrypted);
const decrypted = rsaDecrypt(encrypted, privateKey);
console.log('RSA Decrypted:', decrypted);
3. SHA-256:哈希,不是加密

SHA-256 生成的是固定长度摘要,输入相同,输出就相同;输入稍微变一点,结果就完全不同。它不能解密,也不负责保密,主要用来做完整性校验、签名摘要、密码派生链路中的中间步骤。
它属于 SHA-2 家族,输出长度是 256 位。处理过程会先对消息做填充,再切成 512 位块,经过 64 轮压缩函数,最后拼出 256 位结果。
常见用途包括:
- 数据完整性校验
- 区块链相关逻辑
- 数字签名的摘要阶段
- 配合其他算法做认证流程
浏览器里可以直接用 Web Crypto API:
// 浏览器中使用 Web Crypto API 进行 SHA-256 哈希
async function sha256Hash(message) {
// 将字符串编码为 Uint8Array
const encoder = new TextEncoder();
const data = encoder.encode(message);
// 计算哈希
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
// 将 ArrayBuffer 转换为十六进制字符串
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashHex;
}
// 使用示例
sha256Hash('Hello World').then(hash => console.log('SHA-256 Hash:', hash));
4. HMAC:带密钥的哈希认证

HMAC 不是单纯的哈希,它把密钥和哈希函数绑在一起,用来验证消息有没有被改过,以及消息是不是来自持有密钥的一方。
公式是:
HMAC(K, m) = H((K ⊕ opad) || H((K ⊕ ipad) || m))
这里的 H 可以是 SHA-256,K 是密钥,m 是消息。它经常出现在 API 签名、JWT 签名、Webhook 校验里。比起'自己拼字符串再 hash 一下',HMAC 更稳,规避了很多容易踩坑的自制签名方案。
Node.js 示例:
const crypto = require('crypto');
// 生成 HMAC
function generateHMAC(message, secret) {
return crypto.createHmac('sha256', secret).update(message).digest('hex');
}
// 验证 HMAC
function verifyHMAC(message, secret, hmac) {
const expectedHmac = generateHMAC(message, secret);
return crypto.timingSafeEqual(
Buffer.from(expectedHmac),
Buffer.from(hmac)
);
}
// 使用示例
const secretKey = 'mySecretKey123';
const message = 'Important Data';
const hmac = generateHMAC(message, secretKey);
console.log('HMAC:', hmac);
const isValid = verifyHMAC(message, secretKey, hmac);
console.log('Verification:', isValid ? 'Valid' : 'Invalid');
5. PBKDF2:密码别直接存,先拉长它

PBKDF2 的作用很明确:把用户密码变成更难被暴力猜解的派生密钥。它不是为了'加密密码',而是为了让弱口令在存储和验证时更难被撞库。
它会引入盐值和迭代次数,通过重复 HMAC 运算把计算成本抬高。这个思路很朴素,但很实用。参数选得太保守,防护力度不够;选得太激进,登录验证又会拖慢系统,所以迭代次数通常要按业务负载来定。
适合的场景:
- 用户密码存储
- 从密码生成加密密钥
Node.js 代码如下:
const crypto = require('crypto');
// 使用 PBKDF2 派生密钥
function deriveKey(password, salt, iterations, keyLength, digest) {
return crypto.pbkdf2Sync(
password,
salt,
iterations,
keyLength,
digest
).toString('hex');
}
// 使用示例
const password = 'userPassword123';
const salt = crypto.randomBytes(16).toString('hex'); // 生成随机盐
const iterations = 10000; // 迭代次数
const keyLength = 32; // 密钥长度(字节)
const digest = 'sha256'; // 哈希算法
const derivedKey = deriveKey(password, salt, iterations, keyLength, digest);
console.log('Derived Key:', derivedKey);
console.log('Salt:', salt);
// 验证密码示例
function verifyPassword(password, storedHash, storedSalt, iterations, keyLength, digest) {
const newHash = deriveKey(password, storedSalt, iterations, keyLength, digest);
return newHash === storedHash;
}
const isMatch = verifyPassword('userPassword123', derivedKey, salt, iterations, keyLength, digest);
console.log('Password Match:', isMatch);
一张表先把角色分清
| 算法 | 类型 | 密钥长度 | 安全性 | 性能 | 适用场景 |
|---|---|---|---|---|---|
| AES | 对称加密 | 128/192/256 位 | 高 | 快 | 大数据量加密 |
| RSA | 非对称加密 | 2048 位 + | 高 | 慢 | 密钥交换、数字签名 |
| SHA-256 | 哈希算法 | 256 位输出 | 高 | 快 | 数据完整性验证 |
| HMAC | 消息认证码 | 可变 | 高 | 中等 | 消息认证 |
| PBKDF2 | 密钥派生 | 可变 | 高 | 可调 | 密码存储 |
实际开发里,我会这么选
- 密钥别硬编码。放配置文件里也不算安全,最好交给 KMS、Vault 之类的系统管理。
- 对称加密优先 AES。数据量大时它比 RSA 现实得多。
- 非对称加密主要用 RSA 做交换和签名,不要指望它承担大量数据加密。
- 密码存储不要直接 hash 一次就完事,PBKDF2、bcrypt、Argon2 才是更正常的做法。
- 传输层别省 HTTPS。很多'我已经自己加密了'的方案,最后还是栽在传输链路上。
- 校验场景尽量用恒定时间比较,别把时序信息白送出去。
还会继续碰到的方向
- ECC:同样是非对称加密,通常比 RSA 更省资源
- bcrypt / Argon2:密码哈希里更常见的选择
- 量子安全算法:现在更多是提前了解,不是普通 Web 项目的默认选项
- 同态加密:能在密文上直接计算,但工程成本不低
- 零知识证明:适合对'证明'本身有要求的场景
真正做 Web 安全时,算法不是越多越好,组合对了才有意义。AES 负责数据本体,RSA 负责交换和签名,SHA-256 和 HMAC 负责校验,PBKDF2 负责把密码这件事做得没那么脆弱。把这几个角色分清,很多安全设计就不会乱。


