深色模式
关于散列函数
概述
散列函数常被叫作 hash 函数。它接收任意长度输入,输出一个固定长度结果。这个结果通常叫摘要、散列值,或者 digest。
很多人第一次接触散列函数,会把它和加密混在一起。两者都能把原文变成“看起来认不出来”的东西,但目的完全不同。加密是为了还能解回来,散列则默认不打算恢复原文。
散列函数在做什么
散列函数把任意输入映射为固定长度输出,例如:
text
"hello" -> 2cf24dba...
"hello!" -> ce06092f...它最直观的特点是:输入只改一点点,输出通常会变很多。这种现象叫雪崩效应。
一个合格的密码学散列函数,通常希望具备这些性质:
- 输入相同,输出一定相同
- 输入不同,输出应尽量不同
- 从输出反推输入应当非常困难
- 很难构造出两个不同输入却得到相同结果
为什么散列不是加密
加密强调可逆,散列强调单向。
| 类型 | 目标 | 是否可逆 | 常见场景 |
|---|---|---|---|
| 加密 | 保密性 | 可逆 | 传输和存储保护 |
| 散列 | 摘要与完整性校验 | 不可逆 | 文件校验、签名输入、索引 |
例如下载软件时看到一个 SHA-256 校验值,它的意义不是“把文件藏起来”,而是“确认下载内容是否和发布方提供的一致”。
碰撞是什么
散列函数输出长度固定,而输入空间几乎无限,所以理论上一定存在碰撞,也就是:
text
输入 A != 输入 B
但 hash(A) == hash(B)真正重要的不是“有没有碰撞”,而是“能不能被实际构造出来”。密码学散列函数追求的是抗碰撞能力,也就是让攻击者很难主动找到一对碰撞输入。
这也是为什么:
MD5和SHA-1已不适合安全敏感场景SHA-256、SHA-512仍然广泛使用
常见算法
MD5
MD5 输出 128 位,速度快,历史上用得非常广。但它已经不适合安全用途,尤其不适合签名、证书、完整性防伪这类场景。
今天仍能见到 MD5 的地方,通常只是:
- 非安全场景下的快速指纹
- 老系统兼容
- 简单去重或分桶
SHA-1
SHA-1 比 MD5 晚一些,但也已经不再适合安全用途。现代系统里应尽量避免新使用。
SHA-2
SHA-2 是当前非常常见的一组散列算法,包括:
SHA-224SHA-256SHA-384SHA-512
其中 SHA-256 最常见,很多接口签名、文件校验、区块链相关实现都会用到它。
SHA-3
SHA-3 是另一套设计路线,不是简单给 SHA-2 换了个版本号。实际工程里没有 SHA-256 那么普遍,但它是正式标准的一部分。
散列函数最常见的用途
文件完整性校验
发布软件或镜像时,通常会同时给出一个 SHA-256 值。下载后重新计算一次,如果结果一致,至少可以说明文件没有在传输过程中被意外改动。
数字签名的输入
数字签名通常不是直接对整份大文件做复杂运算,而是先对内容做散列,再对摘要签名。这样计算成本更低,也更符合算法设计方式。
哈希表和键值索引
编程语言里的 HashMap、dict 也会用到散列思想,不过它们用的未必是密码学散列函数。这里的重点通常是速度和分布均匀,而不是安全性。
HMAC 不是单纯再 hash 一次
HMAC 是“带密钥的消息认证码”,常见写法如 HMAC-SHA256。
它的目标不是隐藏内容,而是证明:
- 消息来自知道密钥的一方
- 消息在传输中没有被改
这和“直接对内容做一次 SHA-256”差别很大。后者谁都能算,前者只有知道共享密钥的人才能正确生成。
很多开放平台接口里的“签名”其实本质上就是 HMAC,只是文档喜欢把名字写得很简洁。
密码存储为什么不能直接用 SHA-256
这是散列函数最常见的误用之一。
用户密码如果直接做一遍 MD5 或 SHA-256 再存库,看起来像是“没存明文”,但仍然不够安全。原因包括:
- 算得太快,暴力破解成本低
- 同样密码会得到同样结果
- 容易被彩虹表和批量撞库利用
更合适的做法,是使用专门的口令散列算法:
bcryptscryptArgon2
这些方案故意把计算做慢,并引入随机盐值,目标就是让攻击者难以批量试密码。
开发里怎么判断该不该用散列
可以先问三个问题:
- 只是想验证内容有没有变?用散列。
- 想证明消息来自某个持有密钥的人?用
HMAC或数字签名。 - 想防止别人看到内容?用加密,不要误用散列。
把目标分清,很多安全设计会简单很多。
