重构大版本更新
一口气全面统一博客样式,移除部分冗余导航页面,更专注内容展示。
MD5 为什么不行、加盐解决什么问题、bcrypt/Argon2 强在哪,以及密码存储的完整清单。
每隔一段时间就有新闻:某网站数据库泄露,几百万条密码被挂到暗网售卖。更糟的说法是”密码为明文存储”——这意味着攻击者拿到就能直接登录。这篇文章讲清楚:密码存储的正确姿势为什么是”慢哈希 + 盐”,以及每一步演进背后的攻击逻辑。
普通哈希(MD5、SHA-1、SHA-256)为”快”而设计,一秒能算上亿次。这正好帮了攻击者两种忙:
彩虹表:预先算好海量”常见密码 → 哈希”对照表,拿到库里批量反查。123456 的 MD5 一搜就能反查出原文。
GPU 暴力破解:现代显卡对 MD5 每秒可算数百亿次。一个 8 位纯数字密码,穷举空间一亿,眨眼跑完。
问题不在算法被”破解”(MD5 的单向性尚在),而在它太快了。
注册时生成随机字符串(盐)拼进密码再哈希,盐和摘要一起存:
const crypto = require("node:crypto");function md5WithSalt(password, salt) { return crypto.createHash("md5").update(salt + password).digest("hex");}const salt = crypto.randomBytes(16).toString("hex");const hash = md5WithSalt("123456", salt);// 存库: salt | hash两个用户都用了 123456,库里摘要完全不同;彩虹表全部作废(得为每个盐重算)。但盐解决不了 GPU 暴力破解——盐是公开存的,攻击者拿到库后照样逐个用户穷举,MD5 还是太快。
真正的解决方案是专门为密码设计的慢哈希:故意慢、吃内存,让 GPU 并行优势失效。
| 算法 | 设计目标 | 特点 |
|---|---|---|
| bcrypt | 慢 | 内置盐,成本因子可调,30 年实战检验 |
| scrypt | 慢 + 吃内存 | 抵抗 GPU/ASIC 并行 |
| Argon2id | 慒 + 吃内存 + 抗侧信道 | 2015 密码哈希竞赛冠军,当前首选 |
bcrypt 在 Node.js 里的用法:
const bcrypt = require("bcrypt");
// 注册:成本因子 12,自动生成盐const hash = await bcrypt.hash(password, 12);
// 登录:bcrypt 内部从 hash 里取盐再比对const ok = await bcrypt.compare(input, hash);成本因子每 +1,耗时翻倍。服务器能承受的前提下调到最高,1214 是常见值——单次校验 200500ms,用户无感,攻击者每秒只能试两三次。
盐 + 慢哈希的组合拳:盐废掉彩虹表,慢哈希废掉暴力穷举。
算法$盐$摘要 格式的字符串;密码存储演进史就是一部攻防史:明文 → 加密 → 快哈希 → 加盐 → 慢哈希。每一步都是攻击者逼出来的。今天新建任何系统,答案只有一个:Argon2id 或 bcrypt,成本因子拉满到服务器能承受。如果你的项目里还能搜到 md5(password),现在就是改的时候。