918 字
3 分钟
为什么使用 MD5 存储密码非常危险
密码是用户认证的基础,而密码存储方式直接决定了系统的安全性。使用 MD5 存储密码曾经是常见做法,如今已被证明是非常危险的错误。
一、MD5 的特性
1.1 什么是 MD5?
MD5(Message-Digest Algorithm 5)是一种密码哈希函数,由 Ronald Rivest 在 1991 年设计:
# MD5 哈希示例$ echo "password123" | md5f9d8a6757c5b7a4a5e5c8b7d6e4f3a2b| MD5 特性 | 说明 |
|---|---|
| 输入 | 任意长度的数据 |
| 输出 | 128 位(16 字节)固定长度哈希 |
| 速度 | 极快(每秒数十亿次) |
| 单向性 | 无法从哈希反推原始输入 |
| 确定性 | 相同输入产生相同输出 |
1.2 MD5 的输出
输入: "password"MD5: 5f4dcc3b5aa765d61d8327deb882cf99
输入: "password123"MD5: e807f1fcf82d132f9bb018ca6738a19f二、为什么 MD5 不适合密码存储?
2.1 彩虹表攻击
MD5 的致命问题是速度。攻击者可以预先计算所有常见密码的哈希表:
flowchart LR
subgraph 彩虹表
P1[password] --> H1[5f4dcc3b...]
P2[123456] --> H2[e10adc39...]
P3[admin] --> H3[21232f29...]
end
H1 --> D[数据库泄露哈希]
H2 --> D
H3 --> D
**彩虹表(Rainbow Table)**是预先计算的大规模哈希对照表:
- 大小:通常数十 GB 到数 TB
- 覆盖: billions 常见密码组合
- 查找速度:毫秒级
2.2 实际攻击演示
# 模拟攻击者使用彩虹表破解 MD5 哈希import hashlib
# 假设这是数据库泄露的哈希stolen_hash = "5f4dcc3b5aa765d61d8327deb882cf99"
# 攻击者查询彩虹表rainbow_table = { "5f4dcc3b5aa765d61d8327deb882cf99": "password", "e10adc3949ba59abbe56e057f20f883e": "123456", "21232f297a57a5a743894a0e4a801fc3": "admin",}
# 瞬间破解!if stolen_hash in rainbow_table: password = rainbow_table[stolen_hash] print(f"密码是: {password}") # password2.3 暴力破解也很快
MD5 速度太快,使得暴力破解也变得容易:
# hashcat 是最快的密码破解工具# MD5 速度: 30+ GH/s (每秒 300 亿次哈希)
# 8 位字符的所有组合# 96^8 ≈ 7.2 万亿种可能# 在高速 GPU 集群上: 几小时到几天
hashcat -m 0 -a 3 hash.txt ?a?a?a?a?a?a?a?a| 哈希算法 | 速度(RTX 3090) | 破解 8 位密码时间 |
|---|---|---|
| MD5 | 30 GH/s | ~4 小时 |
| SHA-1 | 10 GH/s | ~12 小时 |
| SHA-256 | 3 GH/s | ~2 天 |
三、MD5 的其他安全问题
3.1 碰撞攻击
2004 年,王小云教授等人展示了 MD5 的碰撞攻击:
# 两个不同的输入可以产生相同的 MD5# 这在证书伪造等领域是严重问题现实攻击:
- 2008 年,Flame 恶意软件利用 MD5 碰撞伪造微软代码签名证书
- 2012 年,Flame 恶意软件使用 MD5 碰撞攻击 Windows Update
3.2 原像攻击
给定哈希值 h,找到原始输入 m 使得 MD5(m) = h。
四、密码存储的正确方式
4.1 使用慢哈希算法
正确的密码哈希应该故意变慢:
| 算法 | 设计用途 | 密码存储适用性 |
|---|---|---|
| bcrypt | 专为密码设计 | 最佳选择 |
| Argon2 | 密码哈希(2015 年获奖) | 最佳选择 |
| scrypt | 内存硬哈希 | 适用 |
| PBKDF2 | 密钥派生 | 可用 |
| MD5/SHA-1/SHA-256 | 数据完整性 | 不适合 |
4.2 bcrypt 的工作原理
import bcrypt
# 密码哈希password = b"user_password"salt = bcrypt.gensalt()hashed = bcrypt.hashpw(password, salt)
print(hashed)# $2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.VTtYaL.6Njnmes
# 密码验证bcrypt.checkpw(password, hashed) # Truebcrypt 的关键参数:
| 参数 | 说明 | 推荐值 |
|---|---|---|
| cost/rounds | 计算复杂度 | 10-12(2024 年) |
| salt | 随机盐 | 自动生成,22 字符 |
| 输出长度 | 哈希长度 | 60 字符(bcrypt) |
4.3 Argon2:更现代的选择
# Argon2 - 2015 年 PHC 获奖算法import argon2
ph = argon2.PasswordHasher( time_cost=2, # 迭代次数 memory_cost=102400, # 内存消耗 (KB) parallelism=4 # 并行度)
hash = ph.hash("password")print(hash)# $argon2id$v=19$m=102400,t=2,p=4$...
ph.verify(hash, "password")4.4 为什么要加盐?
盐(Salt) 是随机数据,与密码一起哈希:
# 无盐:相同密码产生相同哈希MD5("password") = 5f4dcc3b5aa765d61d8327deb882cf99MD5("password") = 5f4dcc3b5aa765d61d8327deb882cf99 # 相同!
# 有盐:相同密码产生不同哈希salt1 = random_bytes(16)salt2 = random_bytes(16)bcrypt("password" + salt1) = $2b$12$...bcrypt("password" + salt2) = $2b$12$... # 不同!加盐的目的:
- 防止彩虹表攻击
- 防止相同密码产生相同哈希
- 每个用户有独立的盐
五、密码哈希最佳实践
5.1 推荐的哈希方案
# Python bcrypt 示例import bcrypt
def hash_password(password: str) -> str: """安全地哈希密码""" salt = bcrypt.gensalt(rounds=12) # 2024 年推荐 return bcrypt.hashpw(password.encode(), salt).decode()
def verify_password(password: str, hashed: str) -> bool: """验证密码""" return bcrypt.checkpw(password.encode(), hashed.encode())5.2 验证流程
sequenceDiagram
participant U as 用户
participant S as 服务器
participant DB as 数据库
U->>S: 输入密码
S->>DB: 获取用户哈希 + 盐
DB-->>S: 返回存储的哈希
S->>S: 使用盐哈希输入密码
S->>S: 比较两个哈希
S-->>U: 验证结果
5.3 错误做法 vs 正确做法
| 错误做法 | 正确做法 |
|---|---|
| MD5(password) | bcrypt(password) 或 Argon2(password) |
| SHA-256(password) | bcrypt(password, salt) |
| 没有盐 | 每个用户独立随机盐 |
| 固定盐 | 随机生成的盐 |
| 低迭代次数 | 高迭代次数(随硬件提升) |
六、实际案例
6.1 2012 年 LinkedIn 密码泄露
事件:1.17 亿 LinkedIn 密码被泄露存储方式:SHA-1(无盐)结果:大量密码被彩虹表破解教训:必须加盐 + 慢哈希6.2 2013 年 Adobe 密码泄露
事件:1.5 亿 Adobe 密码被泄露存储方式:加密(而非哈希)结果:密码被解密教训:密码不能加密,只能哈希七、总结
使用 MD5 存储密码的危险性:
| 问题 | 后果 |
|---|---|
| 速度太快 | 暴力破解容易 |
| 无盐 | 彩虹表攻击 |
| 已知弱点 | 碰撞攻击 |
正确的密码存储:
- 使用专门设计的慢哈希算法(bcrypt, Argon2, scrypt)
- 每个用户使用独立的随机盐
- 选择合适的迭代次数(定期更新)
- 不要使用加密(加密可以被解密)
参考资料
- OWASP Password Storage Cheat Sheet — 密码存储最佳实践
- bcrypt 官方网站 — bcrypt 算法详解
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时
相关文章 智能推荐
1
为什么 HTTPS 需要证书
技术科普 深入理解 PKI 证书体系的设计原理,掌握 HTTPS 安全认证的核心机制。
2
为什么比特币可以防篡改
技术科普 深入解析比特币的区块链结构,为什么它能做到不可篡改,以及拜占庭将军问题的解决。
3
为什么 HTTPS 需要 7 次握手以及 9 倍时延
技术科普 深入解析 HTTPS 建立连接的完整握手过程,为什么需要多次往返,以及如何优化。
4
密码学攻击案例
密码学与安全工程 深入密码学攻击案例——BEAST/CRIME/Heartbleed/ROBOT、侧信道攻击、时序攻击、实现漏洞。
5
后量子密码学
密码学与安全工程 RSA-2048 在经典计算机上需要 300 万亿年才能破解,在量子计算机上只需要几小时。Shor 算法让大数分解从指数时间降到多项式时间,RSA 和 ECC 的安全基础直接崩塌。NIST 已标准化 Kyber/Dilithium/SPHINCS+,后量子迁移不是未来的事——本章详解格密码基础、三个 PQC 标准、混合方案与迁移路径。






