关于安全性:为什么PHP crypt()将salt添加到哈希中?

Why does PHP crypt() prepend the salt to the hash?

我正在构建一个登录系统,在阅读了PHP手册之后,当您向crypt()函数传递一个2位数的salt时,它返回一个哈希字符串,字符串的前2位数是您使用的salt。

例子:

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

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

我在维基百科上查到了salt,上面写着:

For best security, the salt value is kept secret.

所以我不明白为什么crypt函数会返回所有用salt值预先填充的哈希值?

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


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

salt的目的是阻止预先计算的查找表(如彩虹表)。salt防止攻击者将"space"转换为"time"。每一位salt都使表的存储需求翻倍;两字节salt的差异很大(65536倍),但8个字节需要不存在的"yottabyte"存储设备来查找表。

盐必须存放在某个地方。如果你有一个有效的方法来保持一个"秘密",为什么不使用它来存储密码并完全忘记散列呢?不需要;如果您想要真正的安全性,那么您需要设计系统以避免遭受暴力攻击,即使攻击者知道盐。文章认为盐可以保密的假设应该被视为是错误的。

通过加强密钥(应用哈希函数数千次)和密码选择规则(最小长度、数字、特殊字符),可以最好地防止暴力攻击。

假设salt不能被保密,这将鼓励更好的密钥增强和密码选择,从而导致更安全的系统。


我要说的是crypt并不像marc b说的那么糟糕,而且事实上,只要你不依赖于更弱的方案(如md5),它可能是最简单的哈希算法。

见:

如何使用bcrypt在PHP中散列密码?

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

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


是的,盐应该是保密的,但是密码哈希也是保密的。他们在同一个地方同样保密是完全可以接受的。要对照哈希检查密码,您必须将salt与密码结合起来,然后对照哈希检查密码。因此,任何有权查看密码散列的用户或进程也应该有权查看salt,因为密码散列本身对于检查密码是不有用的(除非您打算强行执行salt)。

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

如果你能找到一种方法让salt比hash值更安全,这当然不是一件坏事,但是当任何需要访问一个程序需要访问两者时,很难看出这是如何可行的。


php crypt从unix crypt()函数继承此行为,该函数用于在unix passwd文件中生成密码散列。必须将盐存储在某个地方,否则以后无法验证密码是否正确。对于passwd文件,简单的行为只是将salt(总是两个字符)预先附加到加密密码的开头,这使得将其存储在单个字段中变得简单。

盐值应该保密的说法容易被误解。为了获得最佳实践,您不应该以不发布密码散列的方式发布您的salt。给攻击者散列和盐可以让他们很容易地执行暴力攻击,而不会对您的系统产生可疑的流量。但是,即使攻击者可以看到salt和hash密码,系统也应该是安全的。

事实上,没有任何地方可以存储哈希,原则上,不能被黑客以与哈希密码完全相同的方式破坏。如果密码检查代码可以访问它,那么您必须假设泄露系统的人也可以访问它。


salt被附加到has,这样当您获得密码并想查看它是否与哈希匹配时,您就知道要使用哪个salt。这里的想法是对每个密码使用不同的salt,这样就不会有人预先计算哈希表。

您还可以在每个密码上附加第二个salt(对所有密码都一样),而不必告诉任何人它是什么。


crypt()函数已过时。在影子密码支持出现之前,它被用于散列旧式UNIX系统的密码。盐是为了增加强制密码的复杂性。但是,由于salt是由密码子系统随机生成的,因此必须将其存储在clear中,以便将来的任何密码操作都可以工作。如果SALT在加密之前已经嵌入到密码中,那么就没有实际的方法来验证密码——每次进行密码检查时,您都必须尝试所有可能的SALT——这是非常不切实际的。所以,salt是预先加密的密码,所以您可以再次将其提取出来以备将来使用。

1
2
3
4
5
6
7
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仍然是安全的。