bigywater.tech
Django · 74

密码哈希算法与密码校验成本

Django 存的密码是什么样?校验一次要多久?换算法要不要让用户重置?攻击者能不能靠响应时间猜用户名?这篇在本机实测四种哈希器的耗时,并验证自动升级和抗计时的行为。

1,500,000 次
Django 6.1.1 默认 PBKDF2 迭代次数,本机单次校验约 162 ms
自动升级
旧算法(如 md5)的密码在用户下一次成功登录时被重新哈希为当前默认算法
163.2 / 162.6 ms
存在用户错误密码 / 不存在用户的 authenticate 耗时,几乎相同

默认编码

实测 · 本机执行输出
pbkdf2_sha256$1500000$qUv3EAf21gBP4tVa739AAP$mgQc2bEKeqlCAK8P8ETZabHs9JYTzRBhaZiPxuyGMvo=
算法 = pbkdf2_sha256  迭代次数 = 1500000  盐长度 = 22  哈希长度 = 44
同一个密码两次 make_password 结果相同吗: False (盐每次随机)
未设置的密码 make_password(None) = !FEhaTIKAVrf… (以 ! 开头 = 不可用密码,任何输入都校验失败)

格式 算法$迭代$盐$哈希。盐随机,所以数据库里两个相同的密码看起来不同。

各算法的成本

实测 · 本机执行输出
pbkdf2_sha256  make    162.7 ms  check    162.5 ms   编码前缀:pbkdf2_sha256$1500000$K3PwDLK5NWzlxe1oQS
pbkdf2_sha1    make    174.8 ms  check    174.9 ms   编码前缀:pbkdf2_sha1$1500000$iV8TORdMn1rhXbtulEuZ
argon2         make     26.5 ms  check     26.4 ms   编码前缀:argon2$argon2id$v=19$m=102400,t=2,p=8$SV
bcrypt_sha256  make    217.7 ms  check    218.2 ms   编码前缀:bcrypt_sha256$$2b$12$eRRE0uJxsr9dTKYgUhG
scrypt         make    159.6 ms  check    160.1 ms   编码前缀:scrypt$16384$aq1CEOmciRV8NoanIoom92$8$5$
md5            make      0.0 ms  check      0.0 ms   编码前缀:md5$2hUuCMYCjxYS53tn7KMFB0$2718e61cfb8cf

耗时是本机 5 次取中位数。argon2 与 bcrypt 需要额外安装(argon2-cffi、bcrypt);argon2 的参数是 argon2-cffi 的默认值,不是调优过的结论。选哪个由安全要求决定,这里的数字只说明「成本」可以调。

自动升级

实测 · 本机执行输出
存的是: md5$U1KNIkUEI2VFqMyD
authenticate 成功: True
之后库里是: pbkdf2_sha256$1500000$T7Haeg…  ← 已升级为默认的 pbkdf2_sha256

实测:登录成功后库里的哈希被换成了当前默认算法。所以 PASSWORD_HASHERS 列表的第一个决定新密码用什么,后面的仅用于校验旧密码。

抗计时探测

实测 · 本机执行输出
存在的用户 + 错误密码: 163.2 ms
不存在的用户:            162.6 ms
存在的用户 + 正确密码:   162.8 ms

实测的结果是三种情形耗时几乎相同;Django 的做法(据源码,本文没有单独验证)是用户不存在时也做一次密码哈希来消耗相近的时间。

迭代次数

实测 · 本机执行输出
iterations=       1  verify 中位      0.0 ms
iterations=   10000  verify 中位      1.1 ms
iterations=  100000  verify 中位     11.1 ms
iterations= 1000000  verify 中位    109.4 ms
当前默认迭代次数: 1500000

迭代次数与耗时成正比;对登录接口本身,高成本也意味着容易被当成 CPU 消耗型攻击目标——需要限流(本文没有测)。

分步演示:密码哈希

密码哈希
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。argon2-cffi 25.1.0、bcrypt 5.0.0;耗时取本机 5 次(迭代对比 3 次)中位数
    • 没有验证:GPU / 专用硬件下的破解成本、不同 CPU 上的耗时、argon2 / scrypt 的参数调优建议、密码长度上限与截断(bcrypt 的 72 字节)、认证接口的限流
    京ICP备2026067502号-1京公网安备11010502063487号