上周我读了很多关于密码散列的文章,Blowfish现在似乎是(最好的)散列算法之一 - 但这不是这个问题的主题!
Blowfish只考虑输入密码中的前72个字符:
<?php
$password = "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)";
$hash = password_hash($password, PASSWORD_BCRYPT);
var_dump($password);
$input = substr($password, 0, 72);
var_dump($input);
var_dump(password_verify($input, $hash));
?>
输出结果为:
string(119) "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)"
string(72) "Wow. This is a super secret and super, super long password. Let's add so"
bool(true)
正如您所看到的,只有前72个字符很重要。 Twitter正在使用blowfish aka bcrypt存储他们的密码(https://shouldichangemypassword.com/twitter-hacked.php)并猜测:将您的Twitter密码更改为超过72个字符的长密码,您只需输入前72个字符即可登录您的帐户。 / p>
关于“胡椒”密码有很多不同的意见。有些人说这是不必要的,因为你必须假设秘密胡椒串也是已知/已发布的,因此它不会增强散列。我有一个单独的数据库服务器,因此很可能只有数据库被泄露而不是常数胡椒。
在这种情况下(辣椒没有泄漏)你根据字典进行攻击更加困难(如果这不对,请纠正我)。如果你的辣椒串也泄漏了:没那么糟糕 - 你仍然有盐,它就像没有辣椒的哈希一样受到保护。
所以我认为加密密码至少是不错的选择。
我建议为超过72个字符(和辣椒)的密码获取Blowfish哈希:
<?php
$pepper = "foIwUVmkKGrGucNJMOkxkvcQ79iPNzP5OKlbIdGPCMTjJcDYnR";
// Generate Hash
$password = "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)";
$password_peppered = hash_hmac('sha256', $password, $pepper);
$hash = password_hash($password_peppered, PASSWORD_BCRYPT);
// Check
$input = substr($password, 0, 72);
$input_peppered = hash_hmac('sha256', $input, $pepper);
var_dump(password_verify($input_peppered, $hash));
?>
这基于this question:password_verify
返回false
。
更安全的方式是什么?首先获取SHA-256哈希(返回64个字符)或仅考虑密码的前72个字符?
编辑1:此问题仅针对blowfish / bcrypt的PHP集成。感谢您的评论!
答案 0 :(得分:131)
这里的问题基本上是熵问题。所以让我们开始寻找:
每个字节的熵位数为:
所以,我们的行为取决于我们期望的角色类型。
您的代码的第一个问题是您的&#34; pepper&#34; 哈希步骤正在输出十六进制字符(因为未设置第四个参数hash_hmac()
)。
因此,通过对胡椒进行散列,您可以有效地将密码可用的最大熵减少2倍(从576到288 可能的位)。
但是,sha256
首先只提供256
位熵。因此,您可以有效地将可能的576位减少到256位。您的哈希步骤*立即*,非常定义失败
至少密码中可能的熵的50%。
您可以通过切换到SHA512
来部分解决此问题,您只需将可用熵减少约12%。但这仍然是一个无关紧要的差异。 12%将排列数减少1.8e19
倍。这是一个很大的数字......那就是因素它减少了......
根本问题是有超过72个字符的三种类型的密码。这种风格系统对它们的影响将大不相同:
注意:从现在开始,我假设我们将胡椒系统与原始输出(非十六进制)一起使用SHA512
进行比较。
高熵随机密码
这些是您的用户使用密码生成器生成密码的大密钥。它们是随机的(生成的,而不是人类选择的),并且每个字符具有高熵。这些类型使用高字节(字符> 127)和一些控制字符。
对于此组,您的哈希函数将显着将其可用熵减少到bcrypt
。
让我再说一遍。对于使用高熵,长密码的用户,您的解决方案显着会以可衡量的数量降低密码强度。 (对于72个字符的密码,丢失了62位熵,对于更长的密码,则丢失了更多的熵)
中等熵随机密码
此组使用的密码包含常用符号,但没有高字节或控制字符。这些是你的典型密码。
对于这个组,你将稍微解锁更多的熵(不创建它,但允许更多的熵适合bcrypt密码)。当我稍微说一点时,我的意思是轻微的。当您最大化SHA512所具有的512位时,会发生收支平衡。因此,峰值为78个字符。
让我再说一遍。对于此类密码,在熵耗尽之前,您只能存储6个字符。
低熵非随机密码
这是使用可能不是随机生成的字母数字字符的组。像圣经引用或类似的东西。这些短语每个字符具有大约2.3位的熵。
对于此组,您可以通过散列显着解锁更多熵(不创建它,但允许更多内容适合bcrypt密码输入)。在你用完熵之前,盈亏平衡点大概是223个字符。
让我们再说一遍。对于这类密码,预先散列肯定会显着提高安全性。
这些熵计算在现实世界中并不重要。重要的是猜测熵。这直接影响了攻击者可以做的事情。这就是你想要最大化的东西。
虽然我们很少有研究猜测熵,但我想指出一些观点。
连续猜测连续72个正确字符的几率极低。你更有可能赢得强力球彩票21次,而不是发生这次碰撞...这是我们谈论的数字有多大。
但我们可能不会在统计上发现它。在短语的情况下,前72个字符相同的可能性比随机密码高很多。但它仍然很低(你更有可能赢得强力球彩票5次,基于每个字符2.3位)。
实际上,它并不重要。有人猜测前72个字符的可能性是正确的,后者会产生显着差异,因此不值得担心。为什么呢?
好吧,让我们说你正在说一句话。如果这个人能够获得前72个字符,那么他们要么真的幸运(不太可能),要么就是一个常见的短语。如果它是一个常用短语,那么唯一的变量是制作它的时间。
让我们举个例子。让我们从圣经中引用一句话(因为它是长文本的常见来源,不是出于任何其他原因):
你不应该贪图邻居的房子。你不应该贪图邻居的妻子,或他的男仆或女仆,他的牛或驴,或属于你邻居的任何东西。
这是180个字符。第73个字符是第二个g
中的neighbor's
。如果你猜到那么多,你很可能不会停留在nei
,而是继续阅读其余部分(因为那可能是密码的使用方式)。因此,你的&#34;哈希&#34;并没有增加多少。
你并不是真的会帮助那些通过哈希首先使用长密码的人。有些团体你肯定可以提供帮助。有些你肯定会受伤。
但最终,这些都不是太重要。我们正在处理的数字只是 WAY 太高。熵的差异不会很大。
你最好离开bcrypt。你比你正在尝试的攻击更可能搞砸哈希(字面意思是,你已经完成了它,并且你不是第一个,或者最后一个犯错误)防止将要发生。
专注于保护网站的其余部分。并在注册时在密码框中添加密码熵计,以指示密码强度(并指示密码是否过长,用户可能希望更改密码)...
至少0.02美元(或者可能超过0.02美元)...
实际上没有研究将一个哈希函数输入到bcrypt中。因此,如果喂食一个&#34;胡椒,最好还是不清楚。哈希进入bcrypt会导致未知的漏洞(我们知道做hash1(hash2($value))
可能会暴露围绕碰撞阻力和前映像攻击的重大漏洞)。
考虑到您已经在考虑存储密钥(&#34; pepper&#34;),为什么不以一种经过充分研究和理解的方式使用它?为什么不在存储之前加密哈希?
基本上,在对密码进行哈希处理后,将整个哈希输出提供给强加密算法。然后存储加密结果。
现在,SQL注入攻击不会泄漏任何有用的东西,因为它们没有密码密钥。如果密钥被泄露,攻击者并没有比使用普通哈希(这是可证明的,胡椒和#34; pre-hash&#34;无法提供的东西)更好。
注意:如果您选择这样做,请使用库。对于PHP,我强烈推荐Zend Framework 2的Zend\Crypt
包。它实际上是我目前唯一推荐的。它经过了强烈的审核,它为您做出了所有决定(这是一件非常好的事情)......
类似的东西:
use Zend\Crypt\BlockCipher;
public function createHash($password) {
$hash = password_hash($password, PASSWORD_BCRYPT, ["cost"=>$this->cost]);
$blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
$blockCipher->setKey($this->key);
return $blockCipher->encrypt($hash);
}
public function verifyHash($password, $hash) {
$blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
$blockCipher->setKey($this->key);
$hash = $blockCipher->decrypt($hash);
return password_verify($password, $hash);
}
这是有益的,因为您以很好理解和充分研究的方式(相对至少)使用所有算法。记住:
任何人,从最无能的业余爱好者到最好的密码学家,都可以创建一个他自己无法破解的算法。
答案 1 :(得分:5)
佩戴密码肯定是件好事,但让我们明白为什么。
首先,我们应该回答问题,确切的胡椒有助于。胡椒只保护密码,只要它保密,所以如果攻击者可以访问服务器本身,那就没用了。一个更容易的攻击是SQL注入,它允许对数据库的读访问(对我们的哈希值),我准备了demo of SQL-injection来显示它是多么容易(点击下一个箭头来准备输入)。
然后胡椒实际上有什么帮助?只要辣椒保密,它就可以保护弱密码免受字典攻击。密码1234
将变为类似1234-p*deDIUZeRweretWy+.O
的内容。这个密码不仅更长,它还包含特殊字符,永远不会成为任何字典的一部分。
现在我们可以估算出用户将使用哪些密码,可能会有更多用户输入弱密码,因为密码介于64-72个字符之间的用户(实际上这种情况非常罕见)。
另一点是暴力迫使的范围。 sha256散列函数将返回256位输出或1.2E77组合,这对于暴力强制来说太多了,即使对于GPU来说也是如此(如果我正确计算,2013年在GPU上需要大约2E61 years)。所以我们不应该使用辣椒真正的劣势。因为哈希值不是系统性的,所以无法加速使用常见模式的强制执行。
P.S。据我所知,72字符限制特定于BCrypt本身的算法。我找到的最佳答案是this。
P.P.S我认为您的示例存在缺陷,您无法使用完整密码长度生成哈希值,并使用截断的密码进行验证。您可能打算以相同的方式应用胡椒来生成散列和验证散列。
答案 2 :(得分:2)
Bcrypt使用基于昂贵的Blowfish密钥设置算法的算法。
bcrypt的建议56字节密码限制(包括空终止字节)与Blowfish密钥的448位限制有关。超出该限制的任何字节都不会完全混合到生成的哈希中。因此,当您考虑这些字节对生成的哈希的实际影响时,bcrypt密码的72字节绝对限制就不那么重要了。
如果您认为您的用户通常会选择长度超过55个字节的密码,请记住您可以随时增加密码延长次数,以便在密码表违规的情况下提高安全性(尽管这需要进行大量比较)添加额外的字符)。如果用户的访问权限非常严重以至于用户通常需要大量长密码,那么密码到期时间也应该很短,例如2周。这意味着当黑客投入资源来破坏测试每个试用密码所涉及的工作因素以查看它是否会产生匹配的哈希值时,密码不太可能保持有效。
当然,如果密码表没有被破坏,我们应该只允许黑客在锁定用户帐户之前最多尝试10次猜测用户的55字节密码;)
如果您决定预先散列长度超过55个字节的密码,那么您应该使用SHA-384,因为它具有最大输出而不会超出限制。