我需要每个密码都使用“随机盐”还是整个数据库只需要用一次?

37

关于在PHP/MySQL中使用盐加密密码的问题,参见我的之前的问题,我还有一个关于盐的问题。

当有人说“使用随机盐”来预/附加到密码时,这是指:

  • 创建一个静态的、一次性的随机生成字符串,或者
  • 创建一个每次创建密码都会随机更改的字符字符串

如果盐对于每个用户都是随机的,并且存储在散列密码中,那么如何检索原始盐以进行验证?


参见:https://dev59.com/F3I-5IYBdhLWcg3w18Z3#1645190 - Jacco
4个回答

82
每个用户和每次更改密码时,至少应为其生成一个新的随机盐。例如,不要仅依赖于网站范围内的盐,因为这违背了使用盐的初衷。
为每个用户使用唯一的盐是为了防止如果两个用户有相同的密码,则它们不会得到相同的结果哈希值。这也意味着需要对每个用户单独进行暴力攻击,而不能预先计算出彩虹表
然后将盐和密码的哈希结果存储在数据库中hash(salt + password),以及每个用户的salt。您可以将它们存储在单独的列中,或者全部存储在一列中(用某些哈希中未使用的字符分隔,例如;)。只要能够检索到两者,就没问题。
但是,如果您的数据库被攻击,无论是由于某人获得本地访问权限还是通过SQL注入攻击,那么盐和最终哈希都将可用,这意味着对用户密码进行暴力攻击将变得轻而易举。为了解决这个问题,建议像The Rook一样,您还可以使用存储在本地文件中的站点密钥作为哈希方法的另一个输入,以便攻击者还需要知道这个密钥才能发起有效攻击。这意味着您的数据库必须被攻破,并且攻击者需要访问本地文件。因此使用hash(hash(salt + secret) + password)等方法。
在大多数算法中,你的目标是尽可能快地完成,但对于密码哈希,你希望减慢速度,这被称为密钥强化(有时也称为密钥拉伸)。如果你的哈希函数返回需要0.00001秒,那么有人可以尝试每秒暴力破解100,000个密码,直到找到匹配项。如果你的哈希函数需要1秒才能返回结果,对于登录应用程序的人来说并不是什么大问题,但对于破解密码来说就更麻烦了,因为每次尝试都需要1秒钟才能得到结果,这意味着使用原始哈希函数测试每个暴力破解密码所需的时间将增加100,000倍。
要使哈希函数变慢,只需运行多次即可。例如,你可以执行new_hash = salt + password + previous_hash 100,000次。如果速度太快,你可能需要将迭代次数调整为更高的值。如果想要稍后更改该值,请确保将迭代次数与用户记录一起存储,以便不影响以前存储的任何密码。
现在,你的用户记录应该有一个类似于此格式的字段 "$<algorithm>$<iterations>$<salt>$<hash>"(或者如果你愿意,可以作为单独的字段)。
当用户输入密码时,你可以从数据库中检索盐和迭代次数,并从本地文件中检索站点范围内的秘密,并验证当你使用盐和密码运行相同数量的迭代时,生成的哈希是否与存储的哈希匹配。
如果用户更改了密码,那么您应该生成一个新的盐。您使用的哈希方法并不重要(但哈希算法确实很重要)。上面我建议使用hash(hash(salt + secret) + password),但同样也可以使用hash(hash(salt) + hash(secret) + hash(password))。您使用的方法不会改变密码存储的有效性,其中一个并不比另一个更安全。依靠如何将密码和盐哈希在一起的设计来提供安全性被称为security through obscurity,应该避免使用。
* 您不应使用MD5或SHA-1,因为它们被认为是不安全的。改用SHA-2系列(SHA256、SHA512等)。(Ref

1
有道理。所以你会做类似于 SELECT salt where user = user 的操作,然后检查 password 是否等于 hash(salt+password supplied)。是这样吗? - barfoon
2
是的,没有必要将它们存储在单独的列中,只要每个用户的盐和最终哈希以某种方式对您可用,那么一切都可以正常工作。像“sha256:salt:finalhash”这样在一列中存储似乎是一个受欢迎的选择。 - Rich Adams
3
我建议不要使用固定长度的盐。原因是为了未来的更改而言。如果您使用分隔符,您可以通过某些识别因素(例如更改分隔符等)来检测“旧散列”与“新散列”。因此,您可以存储 $salt.':'.$hash.':'.$hashFunc 以获取 abc:def:sha1。这样,如果以后您决定更改为更长的盐或更改哈希函数,您可以检测到遗留密码并在运行时“升级它们”... - ircmaxell
2
@The Rook:你假设该网站容易受到SQL注入攻击,这是一个完全不同的问题。如果该网站容易受到SQL注入攻击,那么存储密码就是小问题了。如果在任何密码存储情况下都可以访问数据库,那么你就可以使用暴力破解来获取明文密码。你有没有一些方法可以在完全访问数据库的情况下不易受到攻击?你究竟提出了什么替代方案? - Rich Adams
1
@The Rook:bcrypt 不是 一个加密函数 - 我认为你需要做更多的研究。消息摘要函数的目的确实是为了快速,这也是为什么裸的消息摘要函数是一个不好的密码哈希函数(尽管它可以用作相同的构建块) 。密码哈希函数应该是慢的;您只需要执行一次就可以验证密码,而攻击者必须对每个测试的候选密码执行一次。 - caf
显示剩余33条评论

4

为了能够进行验证,盐必须与哈希值一起存储。最佳实践是每次创建或更改密码时使用不同的盐。


2
你可以像David M所说的那样,将盐值与哈希值一起存储。 - TRiG
2
@The Rook - 盐的作用在于需要对每个密码进行不同的字典攻击,而预先计算的攻击无法使用。这是正确的建议。 - David M
1
@David M 不,我完全不同意。不向攻击者提供盐值才是更安全的。在谈论它之前,你应该尝试破解密码哈希。使用John the Ripper,您可以给它3个参数:字典、盐值和密码哈希。如果攻击者没有盐值,那么他就无法破解哈希。 - rook
1
@The Rook - 没有人建议向攻击者提供盐或哈希。关键是,您无法验证密码是否与哈希匹配而不知道盐,因此必须同时存储哈希和盐。 - David M
1
最终,如果你的后端系统被攻破,任何存储密码哈希值的系统都容易受到某种形式的字典攻击。这就是为什么密码安全只是系统安全设计的一部分,也是为什么应该鼓励使用强密码,如果不是强制的话。 - David M
显示剩余11条评论

4
第二个选择是正确的。
传统上,盐值与哈希密码一起存储,但是未加密(通常是预先添加的,例如在Unix密码中)。
更新:大多数较新的Unix系统中使用的方法是这个。

我认为那是UNIX密码存储的旧方法,我很确定DES已经不再使用了。 - rook

2
动态更改盐比使用一次性静态盐要更安全。此外,使每个用户的盐唯一而不是一个全局盐。例如,每次用户登录时都更改它们。将盐连接到密码末尾再进行哈希处理是可以的,但这是一个容易被猜测的公式。最好自己想出一种公式,例如编织盐和密码以创建一个新字符串,然后进行哈希处理,因为md5(密码+盐)仍然容易受到字典攻击的影响。

1
md5(password + salt)作为字典攻击仅在攻击者知道盐的情况下才能生效。如果您的密码数据库(列出每个用户的所有盐+哈希组合)被攻击者入侵,那么您可能有更大的问题需要担心。换句话说,盐+密码或密码+盐是完全可接受的最佳实践。 - Dan McDougall
6
"Weaving your own"是安全性通过混淆实现的。安全性由哈希算法提供,即使他们知道输入模式,仍然不知道任何有价值的东西。 - Jacco
你的解决方案是通过模糊来保证安全性。阅读Rich的答案,他的回答很好。 - rook

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接