36

除了我之前关于 PHP/MySQL 中的加盐密码的问题之外,我还有另一个关于盐的问题。

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

  • 创建一个静态的 1 次随机生成的字符串或者
  • 创建每次创建密码时随机更改的字符串

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

4

4 回答 4

75

应该为每个用户随机生成一个新的盐,并且每次他们至少更改密码时。例如,不要仅仅依赖站点范围的盐,因为这首先破坏了使用盐的意义。

为每个用户使用唯一的盐是这样如果两个用户具有相同的密码,他们将不会获得相同的结果哈希。这也意味着需要单独针对每个用户进行蛮力攻击,而不是能够为站点预先计算彩虹表

然后,您将哈希盐和密码的结果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_hash100,000 次。如果太快,您可能需要将迭代次数调整为更高的值。如果您希望以后能够更改该值,请确保将迭代次数与用户记录一起存储,这样您就不会影响之前存储的任何密码。

您的用户记录现在应该有一个格式类似于“ $<algorithm>$<iterations>$<salt>$<hash>”的字段(如果需要,也可以作为单独的字段)。

当用户输入他们的密码时,您可以从数据库中检索盐和迭代次数,并从本地文件中检索站点范围的秘密,并验证当您使用盐和密码运行相同数量的迭代时,生成的哈希匹配什么你已经存储了。

如果用户更改了他们的密码,那么您应该生成一个新的盐。

您使用的散列方法无关紧要(但散列算法确实*)。上面我建议hash(hash(salt + secret) + password)但同样可以hash(hash(salt) + hash(secret) + hash(password))。您使用的方法不会改变密码存储的有效性,其中一种方法实际上并不比另一种更安全。依靠如何将密码和盐一起散列来提供安全性的设计称为通过模糊性实现的安全性,应该避免。

*您不应使用 MD5 或 SHA-1,因为它们被认为是不安全的。请改用 SHA-2 系列(SHA256、SHA512 等)。(参考

于 2010-06-08T16:24:57.540 回答
4

盐必须与哈希一起存储,才能进行验证。最佳做法是每次创建或更改密码时使用不同的盐。

于 2010-06-08T16:21:10.747 回答
4

第二种选择是正确的。

传统上,盐与散列密码一起存储,但未加密(通常预先附加,例如在 unix 密码中

更新:大多数较新的 Unix 系统中使用的方法是this

于 2010-06-08T16:24:27.320 回答
2

动态更改盐比使用一次性静态盐更安全。

还要使每个用户的盐是唯一的,而不是一种全局盐。例如,每次用户登录时更改它们。

将盐连接到密码的末尾然后对其进行散列是可以的,但这是一个容易猜到的公式。最好自己想出一个,即编织盐和密码以创建一个新字符串然后散列,因为 md5(密码 + 盐)仍然容易受到字典攻击。

于 2010-06-08T16:23:15.873 回答