bcrypt vs MD5:密码存储的正确选择
核心问题:MD5 为什么不能存储密码
MD5 存储密码的根本问题是速度。MD5 每秒可以计算数十亿次,这对于数据处理是优点,但对于密码安全是致命缺陷。当数据库被泄露,攻击者拿到 MD5 哈希后,可以使用以下方法快速破解:彩虹表(预计算的哈希→密码对照表)、暴力破解(在现代 GPU 上每秒尝试数十亿个密码组合)、字典攻击(使用常见密码词典)。大多数弱密码的 MD5 可以在秒级甚至毫秒级内被破解。
bcrypt 的设计哲学:故意慢
bcrypt 由 Niels Provos 和 David Mazières 于 1999 年设计,核心理念是"故意慢"——通过 cost 参数控制计算复杂度。cost=10 意味着 2 的 10 次方(1024)轮 key schedule 迭代。在现代计算机上,bcrypt(cost=12) 大约需要 100–300 毫秒,而 MD5 只需要微秒。这个差异让暴力破解的代价增加了几个数量级:MD5 GPU 破解速度 ~100 亿/秒,bcrypt cost=12 GPU 破解速度 ~10万/秒,相差约 10 万倍。
bcrypt 的内置安全特性
- **自动生成盐值:**bcrypt 在每次哈希时自动生成随机盐值并嵌入输出,无需手动管理盐值,防止彩虹表攻击
- **可调节成本因子:**随着硬件升级,可以增加 cost 值来维持安全性,而不需要重写代码
- **输出包含所有验证信息:**bcrypt 输出字符串同时包含算法版本、成本因子、盐值和哈希值,验证时只需要原始密码和这个字符串即可
/* bcrypt output format */
$2b$12$[22-char salt][31-char hash]
^ ^ ^
| | |-- 128-bit salt (22 Base64 chars)
| |-- cost factor (2^12 = 4096 iterations)
|-- bcrypt version (2b = current standard)
在常见框架中使用 bcrypt
// Node.js
const bcrypt = require('bcryptjs');
// Hash (async)
const hash = await bcrypt.hash(password, 12); // cost=12
// Store hash in database
// Verify
const isValid = await bcrypt.compare(password, storedHash);
// Python (Django)
from django.contrib.auth.hashers import make_password, check_password
hash = make_password(password) # Uses PBKDF2 by default, can configure bcrypt
is_valid = check_password(password, hash)
# PHP
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
$isValid = password_verify($password, $hash);
# Go (golang.org/x/crypto/bcrypt)
hash, _ := bcrypt.GenerateFromPassword([]byte(password), 12)
err := bcrypt.CompareHashAndPassword(hash, []byte(password))
bcrypt 的局限性
bcrypt 有一个实际局限:密码长度限制为 72 字节。超过 72 字节的字符(约 72 个 ASCII 字符)不会影响哈希结果。对于支持超长密码的系统,可以在 bcrypt 之前先用 SHA256 哈希密码(将任意长度压缩为 32 字节),再将 SHA256 哈希值(十六进制字符串或 Base64)作为 bcrypt 的输入。
2025 年的建议:Argon2id 优先
虽然 bcrypt 仍然被认为安全,但 2025 年新项目的首选是 Argon2id,这是 2015 年密码哈希竞赛的获胜者。与 bcrypt 相比,Argon2id 增加了内存复杂度参数,使其对 GPU 并行攻击更具抵抗力,且没有 72 字节的长度限制。OWASP 在其密码存储指南中将 Argon2id 列为首选,bcrypt 作为备选。
迁移指南:从 MD5 密码迁移到 bcrypt
如果你的系统使用了 MD5 存储密码,以下是迁移到 bcrypt 的策略:在用户成功登录时(此时你有明文密码),将 MD5 哈希替换为 bcrypt 哈希;对于长期未登录的用户,可以先对 MD5 哈希值本身进行 bcrypt 处理("哈希的哈希"),在用户下次登录时再完成完整迁移;在数据库中添加标识字段,记录每个用户的密码哈希类型。
立即免费使用相关工具
免费使用 →