密码加盐(Salting)技术详解
什么是密码加盐?
想象你在做一道秘制腌肉🥩。盐就是那味秘方调料,让每块肉都有独特风味。
密码加盐就是在用户密码中添加随机数据(称为"盐"),就像给密码穿上定制防护衣,即使两个用户密码相同,存储结果也完全不同。
为什么需要加盐?举个🌰
假设有3个用户都使用密码 123456
:
// 不加盐的哈希结果(危险!):
用户A: 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
用户B: 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92 // 完全相同!
用户C: 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
// 加盐后的哈希结果(安全✅):
用户A(盐=abc): 7f9d8e... // 盐+密码 = "abc123456"
用户B(盐=xyz): 2b4a6c... // 盐+密码 = "xyz123456"
用户C(盐=123): d8e2f7... // 盐+密码 = "123123456"
三大核心作用
- 防彩虹表攻击
🏳️🌈彩虹表是黑客的"密码字典",加盐后每个密码都变独特,字典直接失效 - 防相同密码暴露
如上例所示,避免"一个密码泄露,所有相同密码用户遭殃" - 增加破解成本
黑客必须为每个用户单独破解,不能批量操作
加盐四步走🚶♂️
graph TD
A[生成随机盐值] --> B[组合密码+盐]
B --> C[哈希计算]
C --> D[存储盐+哈希]
1. 生成随机盐值
// 就像抽盲盒,每个用户获得专属盐
const salt = randomBytes(16).toString('hex');
// 生成16字节随机值 → 类似“4f9c2a3d8b1e7f0a”
2. 组合密码和盐值
// 盐+密码 → 像调制特饮
const saltedPassword = salt + password;
// 得到“4f9c2a3d8b1e7f0amyPassword123”
3. 哈希计算
// 用SHA-256等算法“粉碎”混合体
const hash = createHash('sha256')
.update(saltedPassword) // 喂入混合体
.digest('hex'); // 输出64位哈希值
4. 存储
// 盐和哈希存一起,用冒号分隔
`${salt}:${hash}` → "4f9c2a3d8b1e7f0a:9c2f4d8a1b..."
TypeScript 实战:两种加盐方式
方法1:原生SHA-256加盐
import { randomBytes, createHash } from 'crypto';
// 🔐 密码加密函数
export function hashPasswordWithSHA(password: string): Promise<string> {
return new Promise((resolve, reject) => {
try {
// 步骤1:生成16字节随机盐(32位16进制字符串)
const salt = randomBytes(16).toString('hex');
// 步骤2+3:组合盐+密码 → 哈希计算
const hash = createHash('sha256')
.update(salt + password) // 盐放密码前
.digest('hex'); // 转16进制字符串
// 步骤4:返回“盐:哈希”格式
resolve(`${salt}:${hash}`);
} catch (err) {
reject(err);
}
});
}
// 🔍 密码验证函数
export function verifyPasswordWithSHA(
storedPassword: string, // 数据库存的“盐:哈希”
inputPassword: string // 用户输入密码
): Promise<boolean> {
return new Promise((resolve) => {
// 从存储值拆分盐和哈希
const [salt, storedHash] = storedPassword.split(':');
// 用相同算法计算输入密码
const hash = createHash('sha256')
.update(salt + inputPassword)
.digest('hex');
// 比较新哈希 vs 存储哈希
resolve(hash === storedHash);
});
}
方法2:专业选手 bcrypt(推荐✨)
# 先安装依赖
npm install bcrypt
npm install --save-dev @types/bcrypt
import bcrypt from 'bcrypt';
const SALT_ROUNDS = 12; // 加密强度,值越大越安全(但越慢)
// 🔐 加密密码(bcrypt自动处理盐!)
export async function hashPasswordWithBcrypt(
password: string
): Promise<string> {
// 一步完成:生成盐+哈希+组合存储
return await bcrypt.hash(password, SALT_ROUNDS);
}
// 🔍 验证密码
export async function verifyPasswordWithBcrypt(
inputPassword: string,
storedHash: string // bcrypt自动包含盐
): Promise<boolean> {
// 智能对比:自动提取盐值计算验证
return await bcrypt.compare(inputPassword, storedHash);
}
使用示例
async function main() {
const password = '我的密码123';
// SHA-256演示
const shaHash = await hashPasswordWithSHA(password);
console.log('SHA加盐结果:', shaHash); // 输出"盐:哈希"
console.log('验证结果:',
await verifyPasswordWithSHA(shaHash, password)); // true
// bcrypt演示
const bcryptHash = await hashPasswordWithBcrypt(password);
console.log('bcrypt结果:', bcryptHash); // 自动包含盐的哈希
console.log('验证结果:',
await verifyPasswordWithBcrypt(password, bcryptHash)); // true
}
main();
为什么推荐 bcrypt?🚀
特性 | SHA-256加盐 | bcrypt |
---|---|---|
专业度 | 通用哈希算法 | 专为密码设计 |
盐值处理 | 需手动管理 | 自动处理 |
抗暴力破解 | 中等 | 非常强 |
速度控制 | 固定速度 | 可调节成本 |
bcrypt 四大优势
- 自动加盐 - 不用操心盐的生成存储
- 可调成本 - 通过
salt_rounds
控制计算强度 - 故意变慢 - 拖慢黑客破解速度(1秒验1次 vs 10万次/秒)
- 全副武装 - 内置抵抗GPU/ASIC等高级攻击
安全红绿灯🚦
🟢 该做的
// ✅ 使用专业库
import bcrypt from 'bcrypt';
// ✅ 设置合适强度(12是2023年推荐值)
const SAFE_SALT_ROUNDS = 12;
// ✅ 定期更新安全策略
function updateSecurity() {
if (year > 2025) considerArgon2(); // 未来可升级
}
🔴 禁止做
// ❌ 自己造轮子(除非你是密码学专家)
function myUnsafeHash() { /* 危险操作 */ }
// ❌ 使用弱哈希(如MD5、SHA1)
const weakHash = createHash('md5');
// ❌ 盐值过短(小于16字节)
const toySalt = randomBytes(2); // 太短!
📌 黄金法则:就像不会自己配药治病一样,不要自研密码算法!使用经过实战检验的
bcrypt
/Argon2
等方案。
通过这个指南,你已经掌握了密码加盐的核心原理和实战技能!关键记住三点:
- 加盐=给密码穿定制防护衣
- 优先用
bcrypt
等专业工具 - 永远不要存储明文密码
评论区