上周我阅读了很多关于密码散列的文章,而 Blowfish 似乎是目前最好的散列算法之一——但这不是这个问题的主题!
72 个字符的限制
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 正在使用 bcrypt 存储他们的密码 (https://shouldichangemypassword.com/twitter-hacked.php) 并猜测:将您的 twitter 密码更改为超过 72 个字符的长密码,您只需输入前 72 个字符即可登录您的帐户。
河豚和胡椒
关于“peppering”密码有很多不同的看法。有人说这是不必要的,因为您必须假设秘密胡椒串也是已知/已发布的,因此它不会增强散列。我有一个单独的数据库服务器,所以很可能只有数据库被泄露,而不是不断的胡椒。
在这种情况下(胡椒没有泄露),您根据字典进行攻击会更加困难(如果这不正确,请纠正我)。如果你的胡椒串也泄露了:还不错——你还有盐,而且它的保护和没有胡椒的哈希一样好。
所以我认为加密密码至少是不错的选择。
建议
我的建议是为超过 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 个字符?
优点
用户无法通过仅输入前 72 个字符来登录
您可以在不超过字符限制的情况下添加辣椒
hash_hmac 的输出可能比密码本身具有更多的熵
密码由两个不同的函数散列
缺点
仅使用 64 个字符来构建河豚哈希
编辑 1:这个问题仅涉及河豚/bcrypt 的 PHP 集成。感谢您的评论!
这里的问题基本上是熵问题。所以让我们开始看那里:
每个字符的熵
每个字节的熵位数为:
十六进制字符位:4 值:16 熵在 72 个字符中:288 位
位数:4
值:16
72 个字符的熵:288 位
字母数字位:6 个值:72 个字符中的 62 个熵:432 位
位数:6
值:62
72 个字符的熵:432 位
“通用”符号位:6.5 值:72 个字符中的 94 熵:468 位
位:6.5
值:94
72 个字符的熵:468 位
全字节位:8 值:255 熵在 72 个字符中:576 位
位数:8
值:255
72 个字符的熵:576 位
所以,我们如何行动取决于我们期望的角色类型。
第一个问题
您的代码的第一个问题是您的 "pepper" 哈希步骤正在输出十六进制字符(因为未设置 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 个字符相同的机会比随机密码要高得多。但它仍然很低(基于每个字符 2.3 位,您更有可能赢得 5 次强力球彩票)。
几乎
实际上,这并不重要。有人猜对前 72 个字符的机会,而后者产生显着差异的机会非常低,因此不值得担心。为什么?
好吧,假设您正在使用一个短语。如果这个人能够正确理解前 72 个字符,那么他们要么真的很幸运(不太可能),要么这是一个常用短语。如果这是一个常见的短语,唯一的变量是要多长时间。
让我们举个例子。让我们从圣经中引用一段话(只是因为它是长文本的常见来源,而不是出于任何其他原因):
不可贪恋邻舍的房屋。不可贪恋邻舍的妻子、仆婢、牛驴,以及邻舍的一切。
那是180个字符。第 73 个字符是第二个 neighbor's
中的 g
。如果您猜得这么多,您可能不会在 nei
处停下来,而是继续阅读其余部分(因为这就是密码可能使用的方式)。因此,您的“哈希”并没有增加太多。
顺便说一句:我绝对不提倡使用圣经引用。事实上,恰恰相反。
结论
首先通过散列,你不会真正帮助那些使用长密码的人。有些团体你绝对可以提供帮助。有些你肯定会受伤。
但归根结底,这些都不是太重要。我们正在处理的数字太高了。熵的差异不会太大。
你最好保持 bcrypt 原样。你更有可能搞砸散列(从字面上看,你已经这样做了,而且你不是第一个或最后一个犯这个错误的人)而不是你试图阻止的攻击将会发生。
专注于保护网站的其余部分。并在注册时的密码框中添加密码熵表以指示密码强度(并指示密码是否过长,用户可能希望更改它)...
那至少是我的 0.02 美元(或者可能超过 0.02 美元)......
至于使用“秘密”辣椒:
实际上没有研究将一个哈希函数输入 bcrypt。因此,充其量不清楚将“胡椒”散列输入 bcrypt 是否会导致未知漏洞(我们知道执行 hash1(hash2($value))
可能会暴露围绕抗碰撞和原像攻击的重大漏洞)。
考虑到您已经在考虑存储密钥(“胡椒”),为什么不以经过充分研究和理解的方式使用它呢?为什么不在存储之前加密哈希?
基本上,在您对密码进行哈希处理后,将整个哈希输出输入到一个强大的加密算法中。然后存储加密结果。
现在,SQL 注入攻击不会泄露任何有用的信息,因为它们没有密钥。如果密钥泄露,攻击者的处境并不比使用普通哈希(这是可以证明的,辣椒“预哈希”不提供)更好。
注意:如果您选择这样做,请使用库。对于 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);
}
这是有益的,因为您以易于理解和研究的方式使用所有算法(至少相对而言)。记住:
任何人,从最无知的业余爱好者到最好的密码学家,都可以创建一个他自己无法破解的算法。
布鲁斯·施奈尔
Peppering 密码肯定是一件好事,但让我们看看为什么。
首先,我们应该回答这个问题,胡椒究竟有没有帮助。辣椒只保护密码,只要它保持秘密,所以如果攻击者可以访问服务器本身,它是没有用的。一个更简单的攻击是 SQL 注入,它允许读取数据库(对我们的哈希值),我准备了一个 demo of SQL-injection 来显示它是多么容易(单击下一个箭头以获取准备好的输入) .
那么胡椒实际上有什么帮助呢?只要辣椒保持秘密,它就可以保护弱密码免受字典攻击。密码 1234
将变为类似于 1234-p*deDIUZeRweretWy+.O
。此密码不仅更长,而且还包含特殊字符,并且永远不会成为任何字典的一部分。
现在我们可以估计我们的用户会使用什么样的密码,可能会有更多的用户输入弱密码,因为有些用户的密码在 64-72 个字符之间(实际上这种情况非常罕见)。
另一点是暴力破解的范围。 sha256 哈希函数将返回 256 位输出或 1.2E77 组合,这对于暴力破解来说太过分了,即使对于 GPU 也是如此(如果我计算正确,这在 2013 年的 GPU 上需要大约 2E61 years)。所以我们在使用胡椒粉时并没有真正的劣势。因为哈希值不是系统的,所以你不能用常见的模式加速暴力破解。
PS 据我所知,72 个字符的限制是特定于 BCrypt 本身的算法的。我找到的最佳答案是this。
PPS 我认为您的示例有缺陷,您无法生成具有完整密码长度的哈希,并使用截断的哈希进行验证。您可能打算以相同的方式应用辣椒来生成哈希和验证哈希。
true
。这就是这个问题的全部内容。自己看看:viper-7.com/RLKFnB
Bcrypt 使用基于昂贵的 Blowfish 密钥设置算法的算法。
bcrypt 推荐的 56 字节密码限制(包括空终止字节)与 Blowfish 密钥的 448 位限制有关。超出该限制的任何字节都不会完全混合到生成的哈希中。因此,当您考虑这些字节对生成的哈希的实际影响时,bcrypt 密码的 72 字节绝对限制就不那么重要了。
如果您认为您的用户通常会选择长度超过 55 个字节的密码,请记住,您始终可以增加密码拉伸的轮次,以在密码表泄露的情况下提高安全性(尽管与添加额外的密码相比,这必须很多人物)。如果用户的访问权限非常重要,以至于用户通常需要很长的密码,那么密码的有效期也应该很短,比如 2 周。这意味着当黑客投入资源来击败测试每个试用密码以查看它是否会产生匹配的哈希时,密码不太可能保持有效。
当然,在密码表没有被破坏的情况下,我们应该只允许黑客最多尝试十次猜测用户的 55 字节密码,然后才能锁定用户的帐户;)
如果您决定对超过 55 个字节的密码进行预散列,那么您应该使用 SHA-384,因为它具有最大的输出而不会超出限制。