【问题标题】:Why does PHP crypt() prepend the salt to the hash?为什么 PHP crypt() 将盐添加到哈希中?
【发布时间】:2023-05-05 05:42:02
【问题描述】:

我正在研究构建一个登录系统,在阅读了 php 手册后,当您将 2 位盐传递给 crypt() 函数时,它会返回一个哈希字符串,该字符串的前 2 位数字是您使用的盐.

示例:

$salt = "kr";
echo crypt("mysecret",$salt); //returns "kreOI.F7eOQMY"

我的第一个想法是,这对试图反转你的哈希的人有帮助吗?

我在*上查了salt,上面写着:

为了获得最佳安全性,盐值是保密的。

所以我不明白为什么 crypt 函数会返回所有带有所使用的盐值的哈希值?

这是有原因的吗?这应该是安全问题吗?

【问题讨论】:

  • 这就是我推荐两份盐的原因。一份在配置文件中,对所有用户保密但对所有用户都相同,另一部分与每个用户不同/随机生成的密码一起保存。
  • @meager,实际上我正在尝试升级我继承的一个。
  • 不应将*视为福音。盐不是秘密。

标签: php security encryption hash


【解决方案1】:

*文章的作者将盐与搜索空间的概念混为一谈,暗示盐是一种阻止暴力攻击的方法。混淆这些想法并不会提高安全性;无法识别和描述这两个问题的人不是可靠的指南。

盐的目的是阻止预先计算的查找表(如彩虹表)。 Salt 可以防止攻击者用“空间”换取“时间”。每一点盐都会使桌子的存储需求翻倍;两个字节的盐有很大的差异(65536 倍),但八个字节需要不存在的“yottabyte”存储设备来查找表。

假设盐不能保密,可以促进更好的密钥强化和密码选择,从而使系统更安全。

但是,NIST 最近的建议鼓励使用额外的、 秘密“盐”(我看到其他人称这个额外的秘密“胡椒”)。可以使用这个秘密作为盐来执行密钥推导的另一次迭代。这一轮不是增加对预先计算的查找攻击的强度,而是防止实时字典攻击。这样,它更像是一个好的密钥推导函数中的大量迭代。

如果与散列密码一起存储,此机密将毫无用处;它必须作为机密进行管理,这在大型用户数据库中可能很困难。

暴力攻击最好通过密钥强化(应用哈希函数数千次)和密码选择规则(需要更长的密码、拒绝列入黑名单的条目等)来防止,但是“胡椒”提供了额外的一层防御。

【讨论】:

  • 查看相关答案here.
【解决方案2】:

我应该评论说,Crypt 并不像 Marc B 所说的那么糟糕,而且实际上可能是获得良好哈希的最简单方法,只要您不依赖其较弱的方案(如 MD5)。

见:

How do you use bcrypt for hashing passwords in PHP?

http://uk.php.net/manual/en/function.crypt.php

http://www.openwall.com/phpass/

【讨论】:

    【解决方案3】:

    是的,盐应该保密,但密码哈希也是如此。他们在同一个地方同样保密是完全可以接受的。要根据哈希检查密码,您必须将盐与密码结合起来,然后根据哈希进行检查。因此,任何有权查看密码哈希的用户或进程也应该有权查看盐,因为密码哈希本身对于检查密码没有用(除非您要暴力破解盐)。

    盐的目的是,如果两个不同的用户有相同的密码,他们会散列到不同的东西。这也意味着字典攻击要复杂得多,因为您不能只是散列所有可能的密码,然后根据用户密码散列列表检查它们以找到多个用户的密码。相反,您必须尝试单个盐的密码才能找到一个用户的密码,或者尝试使用多个盐的可能密码的所有组合才能找到命中。但是了解盐本身并不意味着您可以反转密码哈希。这只是意味着您可以对密码哈希进行字典攻击。

    如果你能找到一种方法让 salt 比哈希值更安全,那肯定不是一件坏事,但是当任何需要访问的程序需要访问时,很难看出这是如何实现的两者都有。

    【讨论】:

      【解决方案4】:

      crypt() 函数已过时。在影子密码支持出现之前,它被用于对旧式 Unix 系统的密码进行哈希处理。加盐是为了增加暴力破解密码的复杂性。但是,由于盐是由密码子系统随机生成的,因此必须以明文形式存储,以便将来的任何密码操作都可以使用。如果在加密之前已经将盐嵌入到密码中,那么就没有实用的方法来验证密码——无论何时完成密码检查,你都必须尝试每一种可能的盐——非常不切实际。因此,盐被添加到加密密码之前,因此您可以再次将其取出以供将来使用。

      crypted password: xxabcdefghijklmn
                        ^^- salt
                          ^^^^^^^^^^^^^^-- crypted pw
      
      if ('xx' + crypt('xx' + password) == 'crypted string') then
           password is ok
      endif
      

      如今,crypt() 的安全性相当于谷物盒解码器环。出于历史目的和低安全性“谁在乎它是否被破解”存储。对于任何现代密码用法,最好使用更现代的哈希,例如 sha1/sha256/md5。而且现在连 md5 都被认为是坏了,sha1 的边缘有裂缝,而且(最后我检查过)sha256 仍然是安全的。

      【讨论】:

      • 这是有道理的,所以如果我已经拥有一个数据库,其中包含存储散列密码的用户。无论如何我可以将它们转换为 sha256 之类的其他东西,还是应该在用户下次登录时“转换”它们?因为显然那时我会有原始密码。谢谢。
      • 在那里增加蛮力复杂性。它的存在是为了防止彩虹表的时空权衡。
      • crypt() 并不是“过时的”——当然,旧的基于 DES 的哈希值在这一点上是可疑的,但 MD5、Blowfish 和(在较新的 PHP 中)基于 SHA 的哈希工作得很好用于现代密码哈希。
      • @John - 我处理转换问题的方式仅仅是散列哈希。它为密码验证过程增加了一个步骤,但这可以忽略不计。我读过的数学表明这样做可能会增加发生冲突的机会,但不会显着增加,即使如此,在身份验证机制中,冲突并不是我真正担心的问题。和@TML - 为什么要保护 crypt()?每个人都可以继续使用 bcrypt,我们都会变得更好。
      • SHA* 和盐是苹果和香蕉。没有盐的 SHA* 比任何有盐的旧算法都差。因此,这根本不能真正回答问题。
      【解决方案5】:

      salt 被附加到 has 中,以便您在获取密码并想查看它是否与哈希匹配时知道要使用哪个 salt。这里的想法是为每个密码使用不同的盐,以便有人无法预先计算哈希表。

      您还可以为每个密码附加第二个盐(对所有密码都相同),而不告诉任何人它是什么。

      【讨论】:

        【解决方案6】:

        PHP crypt 从 UNIX crypt() 函数继承此行为,该函数用于在 UNIX passwd 文件中生成密码哈希。有必要将盐存储在某个地方,否则您以后无法验证密码是否正确。对于passwd 文件,简单的行为只是在加密密码的开头添加盐(总是两个字符),这样可以很容易地将其存储在单个字段中。

        盐值应该保密的说法很容易被误解。作为最佳实践,您不应该发布您的盐,就像您不应该发布您的密码哈希一样。向攻击者提供哈希和盐可以让他们轻松进行暴力攻击,而不会对您的系统产生可疑流量。但是,即使攻击者可以看到盐和散列密码,系统仍然应该是安全的。

        事实上,您可以存储散列,原则上,黑客无法以与散列密码完全相同的方式破坏该散列。如果密码检查代码可以访问它,那么您必须假设入侵系统的人也可以访问它。

        【讨论】: