web学习笔记编程技术学习与分享

密码存储:为什么必须用慢哈希

2026 年 07 月 11 日约 3 分钟阅读 网络与安全

数据库泄露时,密码以什么形式存储,直接决定用户的损失程度。这里的关键不是「有没有哈希」,而是哈希有多快。

快哈希的致命问题

MD5 单次计算在现代 GPU 上每秒数十亿次。泄露库里的密码哈希,攻击者跑一遍常见密码字典(千万级)只需几秒。加盐能防彩虹表(预计算的「明文→哈希」表),防不了字典爆破——盐只是让每个密码的计算互相独立,不改变「单次计算太便宜」的事实。

慢哈希:把成本调到「正常登录无感、爆破不划算」

bcrypt / scrypt / argon2id 的设计目标就是慢且可调:

// bcrypt:cost=12,单次约 100-300ms(随硬件)
$hash = password_hash($pwd, PASSWORD_BCRYPT, ['cost' => 12]);
// 校验(自动读取盐与参数)
password_verify($pwd, $hash);
  • bcrypt:cost 每加 1,耗时翻倍。选择标准:登录路径可接受的最大耗时(一般 100-300ms);
  • argon2id(PHP 7.3+ 支持):内存参数让 GPU/ASIC 优势失效,是当前的首选;
  • 校验函数自动处理盐,不要自己拼 MD5(盐+密码),那是重新发明不安全的轮子。

常见错误实现

  • md5($pwd)、sha1($pwd)、md5(md5($pwd)):快,等于没防;
  • 自己设计「加密解密」:可逆 = 泄露即全灭,密码永远不可逆存储;
  • 校验时先 MD5 再比对 bcrypt:迁移期的过渡可以,但要设定清理时间表。

参数迁移

用户登录成功时顺手重新哈希(升级 cost、或从旧算法迁移到 argon2):

if (password_verify($pwd, $hash) && password_needs_rehash($hash, PASSWORD_ARGON2ID)) {
    $hash = password_hash($pwd, PASSWORD_ARGON2ID);
    $db->update(...);
}

无需打扰用户,存量密码随登录逐步升级。

相关的两条纪律

  • 登录接口限流:慢哈希挡离线爆破,限流挡在线爆破,两者互补。失败计数按「IP+账号」维度,锁定期指数退避;
  • 密码策略别过度:强制 90 天换密、必须含特殊字符已被 NIST 否定。长度 ≥8、黑名单弱密码、查已泄露库,比复杂度规则更有效。
密码安全哈希