密码怎么存?
本文介绍怎么安全地把密码存储在数据库里。简短地说:去看 OWASP 规则。
1 存明文
如你所见,直接把密码存在数据库里面,这是最直接的,也是最致命的。
username | password |
---|---|
admin | azerty |
toto | matrix |
billy | yep59f$4txwrr |
tata | matrix |
titi | freepass |
attacker | tesPwndPassword |
我们不知道应用程序会不会有漏洞,也不知道运维是否规范,有没有内鬼出售你的数据库。如果攻击者拿到了数据库,显然他就有了所有账号的访问权限。即使 billy 用了强密码也没用。更可怕的是,许多用户在不同平台使用的密码是相同的,如果一个不安全的应用泄露了他们的密码,那么所有平台的账号都会收到威胁。这样存储密码,不光危及一个系统的安全,还会威胁到其它平台。
这么存密码是不安全的,甚至有规范禁止明文存储密码。实际上,没有人,即使是网站或数据库管理员,都不应有明文密码的访问权限。
2 加密
加密是可以还原的,攻击者可以猜测或者逆向解密过程,获得明文。下面只讨论单向转换函数。
3 哈希函数
许多古老的网站把密码用 md5、sha1 等函数做一遍转换就存在数据库里面。比如 LinkedIn 曾经就保存密码的 sha1 值,2021 年这些哈希值泄露之后, 3天内就被还原出 90% 的密码。
数据库表内容如下所示:
username | password_md5 |
---|---|
admin | ab4f63f9ac65152575886860dde480a1 |
toto | 21b72c0b7adc5c7b4a50ffcb90d92dd6 |
billy | 47ad898a379c3dad10b4812eba843601 |
tata | 21b72c0b7adc5c7b4a50ffcb90d92dd6 |
titi | 5b9a8069d33fe9812dc8310ebff0a315 |
这根本就没什么用,不信的话找个 md5 值上网搜搜,点进去第一个链接就能看到 admin 的明文。
你也可以找一份常用密码表,先求出所有常用密码的md5,再去和数据库对比。这叫彩虹表。网上有许多现成的彩虹表可供下载。
用 hashcat 可以很快还原明文:
cat <<EOF>target_md5.txt ab4f63f9ac65152575886860dde480a1 21b72c0b7adc5c7b4a50ffcb90d92dd6 47ad898a379c3dad10b4812eba843601 21b72c0b7adc5c7b4a50ffcb90d92dd6 5b9a8069d33fe9812dc8310ebff0a315 EOF hashcat -m 0 target_md5.txt ./rockyou.txt
密码字典 rockyou.txt 可以从 github 下载到。
也许有人觉得是因为 MD5 太垃圾了,改用更好的哈希方法,如sha256,sha512, sha3 等。但这些算法,并不是被设计来保存密码的,它们设计来计算某个文件或消息的摘要,而后用来做数字签名、优化搜索或者用于索引。这些算法的目的设计要求之一就是要快。因此上述方法都可以用来处理这些哈希计算的密码。
4 加盐或胡椒
尽管 sha512 不是被设计来保存密码的,但可以使用某些方式让它能用来保存密码。比如加盐。盐是每个用户都不通的随机序列,计算的哈希值是盐和密码拼接的值。
username | salt | password_salt_sha256 |
---|---|---|
admin | BGdd6d6^ZgvkMhKf@W3RqT | 7509d123bce1aa92331861cf8fd738a58205045123f0e25f0862477cb19d3ee0757cd99865c30b123ad1e7f1be1e31a6058090458cb9941031f5c36683c8446e |
toto | HZBD^@gL*wvoExo6yJ7hVB | 6b28830776de6ad7ef1dd8c221e0d53fec4532c623075d0216d937ab82ab284a56a461ce5d4ec77d1783665a262a6a1eb98627b1f6260da55dbb782d7cb75bc4 |
billy | wvVndjwcZJy!dwT4fBD@U^ | 2847b2605f6a1cd88399e6c9784c0e583799be9485cb128fe5f541f43636559067ec32de33e9b3fa2c15b15eec294cf262fd7aab2395dd64d6dbd9640b4fe6fd |
tata | QeNWm9NXqJ8m@m2^F7Kh9* | 165bc06b69fa2bfcd893bfde86358394406c87c7f7abba891cd10ed9fac887c54d52ed14310ad675078033e9bca80084d345fb2836933e55c60f734982430e2b |
titi | iQUemgw9M6Gw*&v6RG%MZ# | f8eded6c815c7522ab6197aa319d3ff4cddc2c7eeffa0f91c1291603f807a47f320324d2ce2fed1fb3cbfe19524fc5d9c105093f755d76a949efb212fb85c942 |
这样的好处是:
- 几乎不可能从网络上或者采用表上搜索获得结果。
- 即使两个用户使用相同的密码,看起来也是不同的,而且一次哈希只能作用对比一个账号,不可能获得其它信息。
但是 hashcat 大概用一分钟就能还原密码。
胡椒和加盐一样,都是添加一定的信息到密码上再哈希,只是胡椒没有写到数据库里,而是写到代码或者配置文件中,攻击者只能猜胡椒是什么。但胡椒是配置文件中的,没办法让每个账号都用不一样的胡椒,一旦泄漏就相当于没有了。
5 增加迭代次数
破解太快了,那就增加哈希次数。让每次哈希计算的更慢,那么破解所需的时间就会更长。我们存一个密码哈希100次之后的值,看他们怎么解。这是个不错的手段。
6 结合三者
结合加盐、加胡椒、增加迭代次数,就可以得到一个安全的保存密码的方式:
hash = sha512(salt+password+pepper) while (cnt > 0) { hash = sha512(hash) cnt-- } return hash
现成的函数已经有现成的了:Argon2, scrypt, PBKDF2, bcrypt …
这些函数有共同的特点:
- 计算更慢
- 需要内存 (GPU的缺点)
- 定义迭代次数
7 Bcrypt
bcrypt 是基于 Blowfish 加密算法的哈希函数,它就是结合了上述三种方法的函数,应该是目前最常用的。他的哈希结果如下:
$2y$11$SXAXZyioy60hbnymeoJ9.ulscXwUFMhbvLaTxAt729tGusw.5AG4C
共4个部分:
- 版本:也就是bcrypt的版本号:\(2\), \(2a\), \(2x\), \(2y\) 或 \(2b\)
- 消耗:也就是迭代次数。这里11表示迭代次数是 \( 2^{11} \) 次。
- 盐:就是盐
- 哈希结果
hashcat 大概需要一个月来恢复消耗为11的哈希值。
8 总结
简短地说:Argon2id 或者老的系统用 bcrypt。如果你对上述密码都用了 bcrypt 的话,你会发现除了 billy 的密码,其它密码都能很快得出结果。