mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
918 字
3 分钟
为什么使用 MD5 存储密码非常危险
2023-02-20

密码是用户认证的基础,而密码存储方式直接决定了系统的安全性。使用 MD5 存储密码曾经是常见做法,如今已被证明是非常危险的错误。

一、MD5 的特性#

1.1 什么是 MD5?#

MD5(Message-Digest Algorithm 5)是一种密码哈希函数,由 Ronald Rivest 在 1991 年设计:

# MD5 哈希示例
$ echo "password123" | md5
f9d8a6757c5b7a4a5e5c8b7d6e4f3a2b
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}") # password

2.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 位密码时间
MD530 GH/s~4 小时
SHA-110 GH/s~12 小时
SHA-2563 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) # True

bcrypt 的关键参数:

参数说明推荐值
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") = 5f4dcc3b5aa765d61d8327deb882cf99
MD5("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 存储密码的危险性:

问题后果
速度太快暴力破解容易
无盐彩虹表攻击
已知弱点碰撞攻击

正确的密码存储

  1. 使用专门设计的慢哈希算法(bcrypt, Argon2, scrypt)
  2. 每个用户使用独立的随机盐
  3. 选择合适的迭代次数(定期更新)
  4. 不要使用加密(加密可以被解密)

参考资料#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

为什么使用 MD5 存储密码非常危险
https://blog.souloss.com/posts/why-the-design/why-md5-password-storage-is-dangerous/
作者
Souloss
发布于
2023-02-20
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时