密码怎么存?

Table of Contents

本文介绍怎么安全地把密码存储在数据库里。简短地说:去看 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.png

你也可以找一份常用密码表,先求出所有常用密码的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

这样的好处是:

  1. 几乎不可能从网络上或者采用表上搜索获得结果。
  2. 即使两个用户使用相同的密码,看起来也是不同的,而且一次哈希只能作用对比一个账号,不可能获得其它信息。

但是 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个部分:

  1. 版本:也就是bcrypt的版本号:\(2\), \(2a\), \(2x\), \(2y\) 或 \(2b\)
  2. 消耗:也就是迭代次数。这里11表示迭代次数是 \( 2^{11} \) 次。
  3. 盐:就是盐
  4. 哈希结果

hashcat 大概需要一个月来恢复消耗为11的哈希值。

8 总结

简短地说:Argon2id 或者老的系统用 bcrypt。如果你对上述密码都用了 bcrypt 的话,你会发现除了 billy 的密码,其它密码都能很快得出结果。


By .