哈希(hash,也叫散列、摘要)是每个开发者每天都在用、但很少认真想过的工具。这篇文章讲清它的三个核心性质、主流算法的优劣、以及一个高频错误——用 MD5 存密码。
哈希函数把任意长度的输入,转换成**固定长度的输出**。这个输出叫哈希值、摘要、指纹。
"hello" → MD5 → 5d41402abc4b2a76b9719d911017c592
100MB 文件 → MD5 → d41d8cd98f00b204e9800998ecf8427e(也是 32 位)
↑ 无论输入多长,输出长度恒定
它有三个关键性质:
从输入算哈希很容易,从哈希反推输入在计算上不可行。注意是“不可行”而不是“不可能”——理论上你可以暴力枚举所有输入,只是数量级太大,实际做不到。
输入改动一个比特,输出会面目全非:
MD5("hello") = 5d41402abc4b2a76b9719d911017c592
MD5("hellp") = 8b1a9953c4611296a827abf8c47804d7
↑ 只差一个字母,结果完全没有相似性
找到两个不同的输入、使它们的哈希值相同,应该极其困难。**这条是安全性的核心,也是 MD5 和 SHA-1 已经失效的原因。**
| 算法 | 输出长度 | 安全性 | 现状 |
|---|---|---|---|
| MD5 | 128 位(32 hex) | ❌ 已破 | 碰撞可秒级构造,不可用于安全场景 |
| SHA-1 | 160 位(40 hex) | ❌ 已破 | 2017 年 Google 构造出实际碰撞 |
| SHA-256 | 256 位(64 hex) | ✅ 安全 | 当前主流选择 |
| SHA-512 | 512 位(128 hex) | ✅ 安全 | 64 位系统上更快 |
| SM3 | 256 位(64 hex) | ✅ 安全 | 国密标准,合规场景用 |
不是“理论上存在弱点”,而是**实际碰撞已经被构造出来了**:
这意味着攻击者可以准备一个正常文件和一份恶意文件,两者哈希相同。如果系统靠哈希值判断文件完整性,就完全失效了。
这是最需要纠正的做法。很多人以为“存 MD5 就安全了,反正不可逆”,实际上:
前人已经把常见密码的哈希值算好、做成表了。拿到你的哈希值,查表即可还原:
5f4dcc3b5aa765d61d8327deb882cf99 → 查表 → "password"
现代显卡每秒能算几十亿次 MD5。8 位以内的纯数字密码,几分钟就能穷举完。
没有盐的情况下,两个用户密码相同,哈希也相同。数据库泄露时一眼就能看出“这些人用的是同一个密码”。
| 方案 | 说明 |
|---|---|
| bcrypt | 自带盐,可调工作因子,最常用 |
| scrypt | 内存密集,抗 GPU/ASIC |
| argon2 | 密码哈希竞赛胜出者,当前最优选择 |
它们的共同点是**故意慢**:一次计算耗时 100ms 左右。正常登录时用户感觉不到,但攻击者想穷举就得把成本乘以几十万倍。
MD5/SHA 是为“快速”设计的,用它们存密码等于放弃了这层防护。