首页 文章

如何使用河豚散列长密码(> 72个字符)

提问于
浏览
83

上周我读了很多关于密码哈希和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正在使用blowfish aka bcrypt来存储他们的密码(https://shouldichangemypassword.com/twitter-hacked.php)并猜测:将您的Twitter密码更改为超过72个字符的长密码,您只需输入前72个字符即可登录您的帐户 .

河豚和胡椒

关于“胡椒”密码有很多不同的意见 . 有些人说这是不必要的,因为你必须假设秘密胡椒串也是已知/已发布的,因此它不会增强散列 . 我有一个单独的数据库服务器,所以很可能只有数据库被泄露而不是常数胡椒 .

在这种情况下(辣椒没有泄漏)你根据字典进行攻击更加困难(如果这不对,请纠正我) . 如果你的辣椒串也泄漏了:没那么糟糕 - 你仍然有盐,它就像没有辣椒的哈希一样受到保护 .

因此,我认为密码密码至少是不错的选择 .

建议

我建议为超过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 questionpassword_verify return false .

问题

什么是更安全的方式?首先获取SHA-256哈希(返回64个字符)或仅考虑密码的前72个字符?

优点

  • 用户只能输入前72个字符才能登录

  • 您可以添加胡椒而不超过字符限制

  • hash_hmac的输出可能比密码本身具有更多的熵

  • 密码由两个不同的函数进行哈希处理

缺点

  • 仅使用64个字符构建blowfish哈希

Edit 1: 这个问题仅针对blowfish / bcrypt的PHP集成 . 感谢您的评论!

3 回答

  • 5

    这里的问题基本上是熵问题 . 那么让我们开始寻找:

    每个字符

    每字节熵的位数是:

    • 十六进制字符

    • 位:4

    • Value 观:16

    • 熵在72个字符:288位

    • Alpha-Numeric

    • 位:6

    • Value 观:62

    • 熵在72个字符:432位

    • "Common"符号

    • 位:6.5

    • Value 观:94

    • 熵在72个字符:468位

    • 完整字节

    • 位:8

    • 值:255

    • 熵在72个字符:576位

    所以,我们如何行动取决于我们期望的角色类型 .

    第一个问题

    您的代码的第一个问题是您的"pepper"哈希步骤正在输出十六进制字符(因为未设置第四个参数 hash_hmac() ) .

    因此,通过对胡椒进行散列,您可以有效地将密码可用的最大熵减少2倍(从576到288个可能的位) .

    第二个问题

    但是, sha256 首先仅提供 256 位的熵 . 所以你有效地将可能的576位削减到256位 . 您的哈希步骤立即,通过非常定义丢失密码中可能的熵的 at least 50% .

    你可以通过切换到 SHA512 来部分地解决这个问题,在这里'd only reduce the available entropy by about 12%. But that'仍然是一个无关紧要的差异 . 12%将排列数量减少了 1.8e19 . 's a big number... And that'是 factor 它减少了...

    相关问题

    根本问题是有超过72个字符的三种类型的密码 . 这种风格系统对它们的影响将大不相同:

    注意:从这里开始我'm assuming we'比较胡椒系统使用 SHA512 与原始输出(不是十六进制) .

    • 高熵随机密码

    这些是您的用户使用密码生成器生成密码的大密钥 . 它们是随机的(生成的,而不是人类选择的),并且每个字符具有高熵 . 这些类型使用高字节(字符> 127)和一些控制字符 .

    对于此组,您的散列函数将 significantly 将其可用熵减少为 bcrypt .

    让我再说一遍 . 对于使用高熵,长密码的用户,您的解决方案以可衡量的数量显着降低其密码的强度 . (对于72个字符的密码,丢失了62位熵,对于更长的密码,则丢失了更多的熵)

    • 中等熵随机密码

    该组使用包含公共符号的密码,但没有高字节或控制字符 . 这些是你的典型密码 .

    对于这个组,你将略微解锁更多的熵(不创建它,但允许更多的熵适合bcrypt密码) . 当我略微说,我的意思是略 . 当您最大化SHA512所具有的512位时,会发生收支 balancer . 因此,峰值为78个字符 .

    让我再说一遍 . 对于此类密码,在熵耗尽之前,您只能存储6个字符 .

    • 低熵非随机密码

    这是使用可能不是随机生成的字母数字字符的组 . 像圣经引用或类似的东西 . 这些短语每个字符具有大约2.3位的熵 .

    对于此组,您可以通过散列显着解锁更多熵(不创建它,但允许更多适合bcrypt密码输入) . 在你用完熵之前,盈亏 balancer 点大约是223个字符 .

    让我们再说一遍 . 对于此类密码,预先散列肯定会显着提高安全性 .

    回到现实世界

    这些类型的熵计算在现实世界中并不重要 . 重要的是猜测熵 . 这就是攻击者可以做的直接影响 . 这就是你想要最大化的东西 .

    虽然很少有研究猜测熵,但我想指出一些观点 .

    连续猜测连续72个正确字符的几率为 extremely . 你're more likely to win the Powerball lottery 21 times, than to have this collision... That'我们谈论的数字有多大 .

    但我们可能不会在统计上发现它 . 在短语的情况下,前72个字符相同的可能性比随机密码高很多 . 但它仍然很低(你更有可能赢得强力球彩票5次,基于每个字符2.3位) .

    实际上

    实际上,它并不重要 . 有人猜测前72个字符的可能性是正确的,后者会产生显着差异,因此不值得担心 . 为什么?

    好吧,让's say you'重新开口 . 如果这个人能够得到正确的前72个字符,那么它们或者是幸运的(不太可能),或者它是一个常见的短语,唯一的变量就是它需要多长时间 .

    我们来举个例子吧 . 让我们从圣经中引用一句话(因为它是长文本的常见来源,不是出于任何其他原因):

    你不应该贪图邻居家 . 你不应该贪图你邻居的妻子,他的男仆或女仆,他的牛或驴,或属于你邻居的任何东西 .

    这是180个字符 . 第73个字符是第二个 neighbor's 中的 g . 如果你猜到那么多,你很可能不会停留在 nei ,而是继续阅读其余部分(因为's how the password is likely to be used). Therefore, your 1330242 didn' t增加了很多 .

    顺便说一句:我绝对不提倡使用圣经引用 . 事实上,恰恰相反 .

    结论

    你不是真的会帮助那些通过哈希首先使用长密码的人 . 有些团体你肯定可以提供帮助 . 有些你肯定会受伤 .

    但最终,这些都不是太重要 . 我们正在处理的数字只是 WAY 太高了 . 熵的差异不会太大 .

    你最好离开bcrypt . 你试图阻止的攻击将会发生,你更有可能搞砸哈希(字面意思是,你已经完成了,并且你不是第一个或最后一个犯错误) .

    专注于保护网站的其余部分 . 并在注册时在密码框中添加密码熵计,以指示密码强度(并指示密码是否过长,用户可能希望更改密码)...

    这至少是0.02美元(或者可能超过0.02美元)......

    至于使用“秘密”辣椒:

    实际上没有研究将一个哈希函数输入到bcrypt中 . 因此,最好不清楚如果向bcrypt输入一个"peppered"哈希将导致未知的漏洞(我们知道 hash1(hash2($value)) 可以暴露围绕碰撞阻力和原像攻击的重大漏洞) .

    考虑到你已经在考虑存储一个密钥(“胡椒”),为什么不在它中使用它那种经过深入研究和理解的方式?为什么不在存储之前加密哈希?

    基本上,在对密码进行哈希处理后,将整个哈希输出提供给强加密算法 . 然后存储加密结果 .

    现在,SQL注入攻击不会泄漏任何有用的东西,因为它们没有密码密钥 . 如果密钥被泄露,攻击者并没有比使用普通哈希(这是可证明的,胡椒“预哈希”不提供的东西)更好 .

    注意:如果您选择这样做,请使用库 . 对于PHP,我 strongly 推荐Zend Framework 2的 Zend\Crypt 包 . 它's actually the only one I' d建议在当前时间点 . 它经过了强烈的审核,它为您做出了所有决定(这是一件非常好的事情)......

    就像是:

    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);
    }
    

    这是有益的,因为你以很好理解和充分研究的方式使用所有算法(相对至少) . 记得:

    从最无能的业余爱好者到最好的密码学家,任何人都可以创建一个他自己无法破解的算法 .

  • 126

    佩戴密码肯定是件好事,但让我们明白为什么 .

    首先,我们应该回答问题,确切的胡椒有助于 . 胡椒只保护密码,只要它保密,所以如果攻击者可以访问服务器本身,那就没用了 . 更简单的攻击是SQL注入,它允许对数据库的读访问(对我们的哈希值),我准备了一个demo of SQL-injection来显示它是多么容易(点击下一个箭头来获得准备好的输入) .

    然后胡椒实际上有什么帮助?只要辣椒保密,它就可以保护弱密码免受字典攻击 . 密码 1234 将变成 1234-p*deDIUZeRweretWy+.O . 这个密码不仅更长,它还包含特殊字符,永远不会成为任何字典的一部分 .

    现在我们可以估计用户将使用哪些密码,可能会有更多用户输入弱密码,因为密码介于64-72个字符之间的用户(实际上这将是非常罕见的) .

    另一点是暴力迫使的范围 . sha256散列函数将返回256位输出或1.2E77组合,即's ways too much for brute-forcing, even for GPU' s(如果我正确计算,这将需要在2013年的GPU上大约2E61 years) . 所以我们不应该使用辣椒真正的劣势 . 因为哈希值不是系统的,所以无法加速使用常见模式的强制执行 .

    附:据我所知,72字符限制特定于BCrypt本身的算法 . 我找到的最佳答案是this .

    P.P.S我认为你的例子是有缺陷的,你不能用完整的密码长度生成哈希,并用截断的密码验证它 . 您可能打算以相同的方式应用胡椒来生成散列和验证散列 .

  • 2

    Bcrypt使用基于昂贵的Blowfish密钥设置算法的算法 .

    bcrypt的建议56字节密码限制(包括空终止字节)与Blowfish密钥的448位限制有关 . 超出该限制的任何字节都不会完全混合到生成的哈希中 . 因此,当您考虑这些字节对结果散列的实际影响时,bcrypt密码的72字节绝对限制不太相关 .

    如果您认为您的用户通常会选择长度超过55个字节的密码,请记住,您可以随时增加密码延长次数,以便在密码表违规的情况下提高安全性(尽管与增加额外的密码表相比,这需要很多)字符) . 如果用户的访问权限非常严重以至于用户通常需要大量长密码,那么密码到期时间也应该很短,例如2周 . 这意味着当黑客投入资源来破坏测试每个试用密码所涉及的工作因素以查看它是否会产生匹配的哈希值时,密码不太可能保持有效 .

    当然,如果密码表没有被破坏,我们应该只允许黑客在锁定用户帐户之前最多尝试10次猜测用户的55字节密码;)

    如果您决定预先散列长度超过55个字节的密码,那么您应该使用SHA-384,因为它具有最大输出而不超过限制 .

相关问题