一、MD5 到底做了什么
MD5 把任意长度的输入,算成一个固定 128 位(16 字节) 的摘要,通常写成 32 位十六进制:
MD5("hello") = 5d41402abc4b2a76b9719d911017c592
三个特性:同样的输入永远得到同样的输出;输出长度固定;不可逆(无法从摘要反推原文,只能靠穷举比对)。正因为不可逆,它叫哈希/摘要,不叫加密。
二、它已经不是「安全」的算法
- 碰撞已被实际构造:2004 年王小云团队给出碰撞方法,之后有公开工具能在秒级生成两个 MD5 相同但内容不同的文件。「碰撞」意味着攻击者可以篡改内容却保持摘要不变。
- 速度太快:现代 GPU 每秒能算上百亿次 MD5,8 位以内的密码基本瞬间被彩虹表/暴力破解还原。
所以下面这些用法都应该停止:
| 用法 | 风险 |
|---|---|
md5(password) 存数据库 | 拖库后大批密码直接被还原 |
| 用 MD5 做接口签名 | 可构造碰撞绕过校验 |
| 用 MD5 校验下载包 | 无法抵御主动篡改(只防传输损坏) |
三、密码到底该怎么存
不要用任何快速哈希。正确做法是专用密码哈希函数 + 每用户随机盐 + 可调成本:
- bcrypt:成本因子(cost)10–12,自带盐。
- scrypt:内存硬,抗 GPU/ASIC。
- Argon2id:目前推荐的首选,2015 年密码哈希竞赛冠军。
import bcrypt
h = bcrypt.hashpw(b'user_password', bcrypt.gensalt(rounds=12))
bcrypt.checkpw(b'user_password', h)
如果历史系统里存的是 MD5,迁移方案是「登录时验证通过后用新算法重算并写回」,不要一次性全量改(无法还原明文)。
四、MD5 仍然合理的地方
- 非对抗场景的完整性检查:确认文件传输有没有损坏、两个文件是否相同。这时没有攻击者,MD5 够快够用。
- 缓存键、去重标识:用内容摘要当缓存 key,碰撞概率在实际业务里可忽略。
- 老系统兼容:很多第三方接口仍要求 MD5 签名,只能照做,但密钥要保管好、并限制重放。
只要涉及「有人在故意伪造」,就换成 SHA-256 或更强。
五、和 SHA-256 怎么选
SHA-256 输出 256 位、目前没有实用碰撞,是现在做校验和签名的默认选择。代价是慢一些、摘要长一倍(64 位十六进制)。日常校验文件、校验下载包,直接用 SHA-256;只有对方系统只支持 MD5 时才用 MD5,并在文档里注明这是兼容而非安全选择。
要算一段文本的摘要,用 MD5 计算 或 SHA 计算;文件不落盘、直接在浏览器里算,用 文件哈希校验。
小结
MD5 = 128 位快速哈希,碰撞已被攻破;不能存密码、不能做安全签名;存密码用 bcrypt/Argon2id,校验文件优先 SHA-256。看到「MD5 加密」这四个字,先想到它其实是「摘要」。