【问题标题】:Do I need a "random salt" once per password or only once per database?我需要每个密码一次“随机盐”还是每个数据库只需要一次?
【发布时间】:2011-03-01 06:18:33
【问题描述】:

除了my previous question about salted passwords in PHP/MySQL,我还有一个关于盐的问题。

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

  • 创建一个静态的 1 次随机生成的字符串
  • 创建一个随机变化的字符串,每次 密码已创建

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

【问题讨论】:

标签: security passwords hash salt


【解决方案1】:

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

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

然后,您将盐和密码的散列结果与每个用户的 salt 一起存储在数据库 hash(salt + password) 中。您可以将它们存储在单独的列中,或全部存储在一列中(由散列中未使用的某些字符分隔,例如 ;)。只要你能检索到你会没事的。

但是,如果您的数据库受到破坏,无论是由于某人获得本地访问权限还是通过 SQL 注入攻击,那么盐和最终哈希都将可用,这意味着对用户密码的暴力攻击将是微不足道的。为了解决这个问题,正如The Rook 所建议的那样,您还可以使用存储在本地文件中的站点级密钥作为散列方法的另一个输入,这样攻击者也需要知道这一点才能发起有效的攻击。这意味着您的数据库必须受到损害,并且攻击者需要访问本地文件。所以使用hash(hash(salt + secret) + password)

虽然在大多数算法中,您的目标是使事情尽可能快,但对于密码散列,您希望减慢它的速度,这称为Key Strengthening(或有时是密钥拉伸)。如果您的哈希函数需要 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)

【讨论】:

  • 有道理。因此,您将执行类似 SELECT salt 的操作,其中用户 = 用户,然后检查密码是否 = 哈希(提供的盐 + 密码)。对吗?
  • 是的,没有要求将它们存储在单独的列中,只要每个用户都可以通过某种方式使用 salt 和最终哈希值,那么一切都会正常工作。将它像“sha256:salt:finalhash”一样存储在一个列中似乎是一种流行的选择。
  • 我建议不要使用固定长度的盐。原因是为了未来的变化。如果您使用分隔符,则可以通过某些识别因素(例如您更改分隔符等)检测“旧哈希”与“新哈希”。因此,您可以存储$salt.':'.$hash.':'.$hashFunc 以获取abc:def:sha1。这样,如果以后您决定改用更长的盐或更改哈希函数,您可以检测旧密码并即时“升级”...
  • 呃,怎么样?用户输入永远不会打到未经哈希处理的数据库,所以我在这里看不到注入攻击。还是您的意思是,如果您发现其他一些假设的注入攻击,您可以读取密码哈希列然后解密密码?用那个 SQL 注入难道没有更好的事情可以做吗?你会建议什么密码存储方案?
  • @The Rook:您假设该站点容易受到 SQL 注入的影响,这是一个完全独立的问题。如果该站点对 SQL 注入开放,则存在比存储密码更大的问题。如果您在任何密码存储情况下都可以访问数据库,那么您就可以暴力获取明文。如果通过 SQL 注入完全访问数据库,您是否有一些不易受到攻击的方法?您究竟在这里提出什么替代方案?
【解决方案2】:

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

【讨论】:

  • 正如大卫 M 所说,您将盐存储在哈希旁边。
  • @The Rook - 加盐的重点是需要针对每个密码安装不同的字典攻击,并且不能使用预先计算的攻击。这是正确的建议。
  • @David M 不,我不能再不同意了。不向攻击者提供盐更加安全。在谈论它之前,您实际上应该尝试破解密码哈希。使用 John the Ripper,你可以给它 3 个参数,一个字典,salt 和密码哈希。如果攻击者没有盐,那么他就无法破解哈希。
  • @The Rook - 没有人建议向攻击者提供盐哈希。关键是,您也无法在不知道盐的情况下根据哈希验证密码,因此您必须同时存储哈希和盐。
  • 最终,如果您的后端系统受到威胁,任何存储密码哈希的系统都容易受到某种字典攻击。这就是为什么密码安全只是系统安全设计的一部分,这也是为什么如果不强制使用强密码就应该鼓励使用的原因。
【解决方案3】:

第二种选择是正确的。

传统上,盐与散列密码一起存储,但未加密(通常预先附加,for example in unix passwords

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

【讨论】:

  • 我认为这是 UNIX 密码存储的旧方法,我很确定不再使用 DES。
【解决方案4】:

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

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

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

【讨论】:

  • md5(password + salt) 作为字典攻击只有在攻击者知道盐时才有效。如果您的密码数据库(列出每个用户的所有 salt+hash 组合)遭到破坏,您可能需要担心更大的问题。换句话说,salt+password 或 password+salt 是完全可以接受的最佳实践。
  • '编织你自己的'是安全低谷默默无闻。安全性由散列算法提供,如果他们知道输入模式,他们仍然不知道任何有价值的东西。
  • 您的解决方案是安全的,尽管晦涩难懂。阅读 Rich 的回答,这是一个很好的回答。
猜你喜欢
  • 1970-01-01
  • 2016-11-15
  • 1970-01-01
  • 2018-09-28
  • 1970-01-01
  • 2013-08-05
  • 1970-01-01
  • 1970-01-01
  • 2021-11-17
相关资源
最近更新 更多