Django 存的密码是什么样?校验一次要多久?换算法要不要让用户重置?攻击者能不能靠响应时间猜用户名?这篇在本机实测四种哈希器的耗时,并验证自动升级和抗计时的行为。
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 消耗型攻击目标——需要限流(本文没有测)。