哈希函数就是一台机器:你喂给它任何东西——一个密码、一段 4 GB 的视频、或者一个字节——它吐出来一串长度固定的字符。同样的输入永远得到同样的输出,不同的输入几乎总是得到不同的输出。这个输出叫摘要、指纹,或者直接叫哈希值。它有一个最本质的特点:不可逆。这个"单向"正是哈希的全部意义所在。
哈希函数到底在做什么
你调用哈希函数,不是为了像加密那样"打乱"数据,而是为了把数据浓缩成又小又稳定的东西。不管输入多大,机制都是一样的:
输入:"The quick brown fox" -> 摘要:37ab…(SHA-256 是 64 个十六进制字符)
输入:一部两小时的电影 -> 摘要:9f2c…(还是 64 个十六进制字符)
摘要的长度不会随着输入变大而变大。这个固定长度,正是哈希便于存储和比较的原因。
你可以信赖的四个性质
- 确定性。同一个字节序列哈希两次,结果逐字节相同。这正是校验和能被信任的前提。
- 定长输出。SHA-256 永远输出 256 位,不管你给了它什么。
- 单向。给了摘要,你无法还原出输入。没有"反哈希"按钮,谁说有,谁就是在卖东西。
- 雪崩效应。输入只要改一位,输出大约有一半的位会翻转,两段摘要看起来完全不相关。
你可以用两个几乎一样的字符串亲眼看看雪崩效应:
$ echo -n "hello" | sha256sum
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
$ echo -n "hellp" | sha256sum
1dd9f635fc9d209cbfa643af332d5b9f6d4b66cb3c8c1756a65d9d19c8a6833c
一个五个字母的单词,只改了一个字母,整个 64 位的输出就全变了。这就是雪崩效应在工作。
哈希 vs 加密 vs 编码
这三样东西经常被混为一谈,通常是因为它们都能把可读的数据变成一堆像乱码一样的字符。但它们解决的是三个完全不同的问题。
| 特性 | 哈希 | 加密 | 编码 |
|---|---|---|---|
| 可逆吗 | 否(单向) | 有密钥就能还原 | 能,无需密钥 |
| 需要密钥吗 | 不需要 | 需要 | 不需要 |
| 输出长度 | 固定 | 约等于输入 | 约等于输入 |
| 能证明什么 | 完整性 / 身份 | 机密性 | 传输兼容性 |
| 典型场景 | 密码、校验和、签名 | 存密钥、TLS | JSON 里的 Base64、URL |
最快的区分办法:编码只是换了一种格式、不带任何保密性(谁都能解码);加密靠密钥把数据藏起来;哈希是故意把原文丢掉。
算法阵容:谁还站得住
不是每个哈希算法都安全可用,而且这个差距在过去二十年里越拉越大。
| 算法 | 摘要长度 | 2026 年的状态 |
|---|---|---|
| MD5 | 128 位 | 碰撞已破解,只能做校验和,不能用于安全 |
| SHA-1 | 160 位 | 碰撞已破解(SHAttered,2017),该退役了 |
| SHA-256 | 256 位 | 稳妥,校验和与签名的默认选择 |
| SHA-512 | 512 位 | 稳妥,在 64 位硬件上比 SHA-256 还快 |
| BLAKE3 | 可配置 | 新锐,极快且可并行,新代码的好选择 |
MD5 和 SHA-1 是那两个你必须停止在安全场景里使用的算法。它们用来检测"下载文件是不是意外损坏了"还凑合,因为那时没有攻击者在刻意伪造一个碰撞文件。可一旦有人有动机去构造第二个相同摘要的文件,这两个算法就直接倒下。
碰撞与长度扩展攻击
碰撞指的是两个不同的输入产生了相同的摘要。256 位的输出意味着可能的输入远多于可能的摘要,所以碰撞在数学上必然存在——关键在于攻击者能不能主动找到一个。对 MD5 和 SHA-1 来说,他们能,而且在一台笔记本上几秒钟就行。
另一个坑叫长度扩展攻击。SHA-256 和 SHA-1 都建立在 Merkle–Damgård 构造之上,这意味着如果你知道 H(message) 却不知道 message 本身,往往还能往后追加数据、算出 H(message || extra),而根本不需要看到原始消息。这也就是为什么你绝不能自己用 hash(密钥 || 数据) 来当"MAC"用——应该用 HMAC(下一篇会讲)。
加盐与加 pepper:这是两回事
这两个词经常被不懂的人混着用。它们不是一回事。
- 盐(salt)是每个用户单独生成的随机值,存在哈希旁边。它阻止攻击者用预先算好的彩虹表,也防止两个密码相同的用户拿到相同的哈希。它不保密——就明晃晃躺在数据库里。
- pepper是整套应用共享的一个 secret 值,存在数据库之外(环境变量、KMS)。它保护的是"数据库泄露了但 pepper 没泄露"这种情况。如果攻击者同时拿到了数据库和 pepper,它就毫无作用。
如果你在存密码,你应该用 bcrypt、scrypt、Argon2id 这类本来就内置加盐的密码哈希算法,而不是自己手搓盐和 pepper。做消息认证用 HMAC 时,那个密钥就扮演了 pepper 的角色。
到底该用哪个
| 场景 | 用这个 | 为什么 |
|---|---|---|
| 校验下载的文件 | SHA-256 | 标准、抗碰撞的校验和 |
| 存储用户密码 | Argon2id 或 bcrypt | 故意慢且加盐 |
| 给消息做认证 | HMAC-SHA256 | 带密钥,免疫长度扩展 |
| 只是让二进制塞进文本 | Base64(那是编码!) | 根本不是哈希 |
| 把数据藏起来不给人看 | AES-GCM(那是加密!) | 哈希做不到这个 |
最常见的错误,就是图 MD5"快",拿它去做需要抵御攻击者的活儿。对校验和来说快是优点,对密码存储来说快是致命伤。选算法要看对手,不要看跑分。
想自己算几个摘要、亲眼看看雪崩效应?哈希生成工具 能把同一段文本用 SHA-256、SHA-512 和 BLAKE3 并排算出来。