"用 MD5 加密密码"这句话里有两个错:MD5 不是加密、而且它不该用来存密码。哈希、加盐、口令哈希常被混为一谈,但它们解决的是完全不同的问题。理清这三层,才能判断什么场景该用哪种,以及为什么校验文件和存密码要用截然相反的算法。

哈希不是加密,先分清这点
哈希(hash)是单向的:把任意输入压成固定长度的指纹,无法从指纹反推原文。加密(encryption)是双向的:用密钥能加密也能解密。所以"MD5 加密"是个常见误称——MD5 是哈希,没有"解密"一说。
哈希的两个核心性质决定了它的用途:
- 确定性:同样的输入永远得到同样的输出。这让它能当"指纹"用于校验。
- 抗碰撞:很难找到两个不同输入产生相同哈希。这让指纹难以伪造。
通用哈希(MD5、SHA-1、SHA-256、SHA-512)还有第三个特点:为速度优化,算得飞快。这个"快"在文件校验里是优点,在存密码里却是致命伤——下面就分场景看。
为什么不能用通用哈希存密码?
因为通用哈希太快了。结论先说:MD5/SHA-256 这类哈希每秒能在普通硬件上算几十亿次,攻击者拿到哈希后可以高速暴力枚举或查表反推,尤其对常见弱密码几乎瞬破。
具体有两种攻击:
- 彩虹表(rainbow table):预先把海量常见密码的哈希算好存成表,拿到一个哈希直接查表命中。因为哈希是确定性的,
123456的 SHA-256 永远是同一个值,一查就中。 - 暴力 / 字典枚举:现代 GPU 每秒能算数十亿次 SHA-256,按字典和规则猛试,弱密码扛不住几秒。
哈希快,本意是好事,但用来存密码就等于给攻击者送了高速试错的靶子。所以存密码需要反其道而行——故意让哈希变慢。
加盐:让相同密码不再相同
加盐(salt)解决的是"相同密码产生相同哈希"这个结构性弱点。做法是给每个用户生成一段随机字符串(盐),把"盐 + 密码"一起哈希,并把盐和结果一起存。
它带来两个直接效果:
- 彩虹表失效:预先算好的表是针对"裸密码"的,加了随机盐后哈希结果完全不同,表里查不到。
- 无法一次破多个账户:每个用户盐不同,即使两人用同一密码,哈希也不同,攻击者只能逐个账户单独破,成本陡增。
但要注意:加盐只防"批量/查表"攻击,不解决"哈希太快"。攻击者拿到某个用户的盐和哈希后,仍可以针对这一个账户高速枚举。要堵这个口子,得换算法本身。
口令哈希:故意做得又慢又吃内存
针对"哈希太快",密码学界设计了专门的口令哈希(password hashing)算法——bcrypt、scrypt、Argon2。它们的核心思路是故意昂贵:
| 算法 | 关键设计 | 防御重点 |
|---|---|---|
| bcrypt | 可调"工作因子",迭代次数指数增长 | 拖慢 CPU 暴力 |
| scrypt | 高内存占用 | 拖垮 GPU/ASIC 并行 |
| Argon2 | 时间+内存+并行三参数可调 | 综合抗 GPU/ASIC,现代首选 |
它们内置加盐,并允许你调一个"代价参数":让单次哈希耗时几十到几百毫秒。对正常登录,几百毫秒无感;对攻击者,每秒只能试几千次而非几十亿次,暴力破解的成本被抬高几个数量级。而且随着硬件变快,只要调高代价参数就能持续保持难度。
那 SHA-256 到底什么时候用?
通用哈希没被淘汰,只是用对场景:它适合"要快、要稳定指纹"的任务,而不是存口令。
- 文件完整性校验:下载完算一遍 SHA-256,和官方公布的值比对,确认没被篡改或传输出错。这里要的就是快和确定性。
- 内容寻址 / 去重:用哈希当数据的唯一标识(如 Git 的对象、CDN 缓存键)。
- 数字签名的预处理:先哈希再签名,缩短待签数据。
这些场景里,对一段文本或文件算一遍 MD5、SHA-1、SHA-256、SHA-512 得到指纹做比对就够了——因为目标是生成稳定指纹,而非抵御针对口令的暴力枚举。这也正是通用哈希"快"的价值所在。
一张表收束:哪种需求用哪种
把三层和场景对齐,选择其实很清晰:
| 需求 | 该用什么 | 不该用什么 |
|---|---|---|
| 校验文件没被篡改 | SHA-256 | —— |
| 数据去重/唯一标识 | SHA-256 等 | MD5(碰撞已被攻破) |
| 存用户密码 | Argon2 / bcrypt / scrypt + 盐 | MD5 / SHA-256 裸哈希 |
| 防彩虹表/批量破解 | 每用户随机盐 | 全局固定盐或不加盐 |
判断标准很简单:要快、要指纹 → 通用哈希;要扛暴力枚举 → 口令哈希;要防批量/查表 → 必须每用户加随机盐。 用错方向(拿快哈希存密码,或拿慢哈希校验大文件)才是真正的问题。
小结
哈希不是加密;通用哈希(MD5/SHA-256)为速度而生,适合文件校验和去重,但正因为快,直接拿来存密码会被彩虹表和 GPU 暴力枚举轻易反推。加盐解决"相同密码同哈希"和批量破解,但不改变"太快";真正存口令要用 bcrypt/scrypt/Argon2 这类故意又慢又吃内存的口令哈希,并可随硬件升级调高代价。一句话:校验文件用 SHA-256,存密码用 Argon2 加盐,别把两者用反。