一、哈希是什么
哈希(Hash)是一种把任意长度的数据映射成固定长度摘要的算法。它有几个关键特征:
- 单向:只能从原文算出哈希值,不能从哈希值反推原文。
- 固定长度:无论输入 1 个字节还是 1GB,输出长度都一样。
- 确定性:同一算法、同一输入,每次算出的结果都一样。
- 雪崩效应:输入改一个字符,输出会面目全非。
- 抗碰撞:很难找到两个不同的输入产生相同的输出。
举个例子,字符串 "hello" 的几个常见哈希值:
MD5: 5d41402abc4b2a76b9719d911017c592
SHA256: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
只改一个字符,把 "hello" 改成 "Hello"(大写 H):
MD5: 8b1a9953c4611296a827abf8c47804d7
SHA256: 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969
可以看到输出完全变了,这就是雪崩效应。
二、MD5 的历史
MD5 由麻省理工学院的 Ronald Rivest 在 1992 年设计。Rivest 是 RSA 加密算法的"R"(另外两位是 Adi Shamir 和 Leonard Adleman,三人因此获得 2002 年图灵奖)。
MD5 的全称是 Message Digest Algorithm 5,是 MD4 的改进版本。它的输出长度固定为 128 位(16 字节),通常用 32 个十六进制字符表示。
设计之初,MD5 的目标是:
- 作为数据完整性校验工具(文件下载后算一次 MD5,和官方对比)
- 作为密码存储的摘要(数据库里不存明文密码,存 MD5)
- 作为数字签名的一部分
在 1990 年代到 2000 年代初,MD5 几乎是互联网上最流行的哈希算法。
三、SHA256 是什么
SHA256 属于 SHA-2 家族(Secure Hash Algorithm 2),由美国国家安全局(NSA)设计、美国国家标准与技术研究院(NIST)在 2001 年发布。它的输出长度是 256 位(32 字节),通常用 64 个十六进制字符表示。
SHA-2 家族还包括 SHA224、SHA384、SHA512 等变体,区别主要是输出长度和内部循环次数。目前 SHA256 是使用最广泛的一个,被用于:
- HTTPS 证书签名(和 SHA384 一起)
- 区块链(比特币的挖矿算法就是双重 SHA256)
- 文件完整性校验
- 现代密码存储(配合加盐和慢哈希)
四、MD5 vs SHA256 对比
| 维度 | MD5 | SHA256 |
| 输出长度 | 128 位 | 256 位 |
| 字符长度 | 32 位十六进制 | 64 位十六进制 |
| 计算速度 | 快 | 较慢(约 MD5 的 1/2 到 1/3) |
| 安全性 | 已被破解,不推荐 | 安全,目前无有效攻击 |
| 碰撞难度 | 已能人为构造碰撞 | 暂无实际碰撞攻击 |
| 标准化 | RFC 1321 | FIPS 180-4 |
| 典型场景 | 文件校验(非安全场景) | 证书、区块链、密码存储 |
| 设计者 | Ronald Rivest | NSA / NIST |
从速度上看 MD5 确实更快,但在现代 CPU 上 SHA256 的性能也已经足够好。大多数场景下,速度差异可以忽略。
五、MD5 为什么不再安全
MD5 的"破解"并不是说能从哈希值反推出原文——哈希算法本身是数学上不可逆的。MD5 的安全问题在于碰撞(Collision):找到了一种方法,能人为构造出两个不同的输入,使它们产生相同的 MD5 值。
时间线:
| 年份 | 事件 |
| 1996 | Rivest 自己发现 MD5 存在理论弱点 |
| 2004 | 中国密码学家王小云团队公布 MD5 碰撞的快速构造方法 |
| 2008 | 研究者用 MD5 碰撞伪造了 CA 证书,整个互联网震惊 |
| 2012 | Flame 病毒利用 MD5 碰撞伪造微软证书 |
| 至今 | 主流浏览器、CA 机构全面弃用 MD5 |
碰撞攻击的实际危害在于:攻击者可以构造一个看似合法、实际是恶意的文件,让它和合法文件有相同的 MD5。如果系统只校验 MD5,就会把恶意文件当成合法文件放行。
所以现在如果你在系统里用 MD5 做安全校验,等于没做。
六、彩虹表攻击
即使算法本身没被破解,简单的哈希也不适合存密码。因为哈希的"确定性"意味着:相同的密码永远产生相同的哈希值。
攻击者可以提前算好一张"密码 → 哈希"对照表,比如:
| 密码 | MD5 |
| 123456 | e10adc3949ba59abbe56e057f20f883e |
| password | 5f4dcc3b5aa765d61d8327deb882cf99 |
| admin | 21232f297a57a5a743894a0e4a801fc3 |
这就是彩虹表(Rainbow Table)。它体积可以做到几个 TB,覆盖几乎所有常见密码。一旦数据库泄露,攻击者拿着哈希值往表里一查,几秒就能还原出明文密码。
应对彩虹表的方法是加盐(Salt):在密码前后加一段随机字符串再哈希。
import hashlib, secrets
# 错误做法
hash = hashlib.md5("password123".encode()).hexdigest()
# 正确做法(概念示意,实际要用 PBKDF2/bcrypt/scrypt)
salt = secrets.token_hex(16)
hash = hashlib.pbkdf2_hmac("sha256", "password123".encode(), salt.encode(), 100000)
加盐之后,即使两个用户用了同样的密码 123456,因为盐不同,存储的哈希值也完全不同,彩虹表就失效了。
七、密码存储的正确姿势
存密码不能用 MD5 或 SHA256 直接哈希一次,原因有两个:
- 现代 GPU 每秒能算几亿次 SHA256,暴力破解弱密码依然很快
- 通用哈希算法设计目标是快,而不是抗暴力破解
正确的做法是用专门的密码哈希函数:
| 算法 | 特点 |
| bcrypt | 自带盐、可调工作因子,最广泛使用 |
| scrypt | 内存困难型,抗 GPU/ASIC 攻击 |
| Argon2 | 2015 年密码哈希竞赛冠军,目前最推荐 |
| PBKDF2 | 标准化、兼容性好,但安全性弱于前三个 |
这些算法的共同点是:可以配置计算开销,让单次哈希耗时 100ms 左右。正常用户登录时多等 100ms 没感觉,但攻击者暴力破解时要算几亿次,成本直接高到不可行。
八、什么时候还能用 MD5
虽然 MD5 不再适合安全场景,但在一些非安全场景下仍然有用:
| 场景 | 说明 |
| 文件完整性校验 | 下载完文件算一次 MD5 对比,防止下载损坏 |
| 缓存键 | 用 MD5 把长 URL 变短,作为缓存 key |
| 去重 | 对内容做 MD5,相同内容会产生相同哈希 |
| 分库分表 | 用 MD5 取模把数据均匀分到不同表 |
这些场景不依赖抗碰撞性,只要哈希分布均匀、计算够快就行。
九、实践推荐
日常开发中经常需要算一段文本或一个文件的哈希值——校验文件完整性、对比数据、生成缓存键等等。在线工具最方便:
- MD5 / SHA256 在线计算工具:https://52tool.net/tools/code/md5
支持 MD5、SHA1、SHA256、SHA512 等多种算法,可以输入文本或上传文件,实时显示结果,是开发调试和文件校验时的常用工具。