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

上周我读了很多关于密码哈希的文章,Blowfish似乎是现在最好的哈希algorithm之一 – 但这不是这个问题的主题!

72个字符的限制

河豚只考虑input密码中的前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个字符的密码,您可以通过login到您的帐户只input前72个字符。

河豚和胡椒

关于“密码”密码有很多不同的意见。 有些人说这是没有必要的,因为你必须假定秘密胡椒串​​也是已知的/发布的,所以它不会增强哈希。 我有一个单独的数据库服务器,所以很有可能只有数据库泄漏,而不是胡椒。

在这种情况下(辣椒没有泄漏),你基于字典的攻击更困难(纠正我,如果这是不正确的)。 如果你的胡椒串也泄漏了:没有那么糟糕 – 你仍然有盐,而且没有胡椒粉,它的保护作用也很好。

所以我认为密码至less是不错的select。

build议

我的build议是要得到超过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)); ?> 

这是基于这个问题 : password_verify返回false

问题

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

优点

  • 用户只能input前72个字符不能login
  • 你可以添加胡椒,不超过字符限制
  • hash_hmac的输出可能比密码本身具有更多的熵
  • 密码由两个不同的函数散列

缺点

  • 只有64个字符用于构buildblowfish哈希

编辑1:这个问题只涉及到blowfish / bcrypt的PHP集成。 感谢您的意见!

这里的问题基本上是一个熵的问题。 那么让我们开始看:

每个字符的熵

每个字节的熵位数是:

  • hex字符
    • 位:4
    • 值:16
    • 熵在72个字符:288位
  • 字母数字
    • 位:6
    • 价值观:62
    • 熵在72个字符:432位
  • “常用”符号
    • 位:6.5
    • 价值观:94
    • 熵在72个字符:468位
  • 全字节
    • 位:8
    • 值:255
    • 熵72个字符:576位

所以,我们如何行事取决于我们期望的angular色types。

第一个问题

你的代码的第一个问题是你的“胡椒”哈希步骤输出hex字符(因为没有设置hash_hmac()的第四个参数)。

因此,通过对胡椒进行哈希处理,可以有效地将可用于密码的最大熵减less2倍(从576到288个可能位)。

第二个问题

但是, sha256只提供了256位的熵。 所以你可以有效地把576位降到256位。 你的哈希步*立即*,通过非常定义失去至less 50%的密码可能的熵。

你可以通过切换到SHA512来部分解决这个问题,你只需要将可用熵减less12%左右。 但这仍然是一个不小的差别。 这12%将排列的数量减less了1.8e19 。 这是一个很大的数字…这是它减less的因素

相关问题

根本问题是有超过72个字符的三种types的密码。 这种风格系统对他们的影响将是非常不同的:

注意:从这里开始,我假设我们正在比较一个使用SHA512和原始输出(不是hex)的胡椒系统。

  • 高熵随机密码

    这些是您的用户使用密码生成器,这些生成器会生成大量密码的密码。 它们是随机的(生成的,不是人类select的),并且每个字符都有很高的熵。 这些types使用高字节(字符> 127)和一些控制字符。

    对于这个组,你的哈希函数将大大减less他们的可用熵到bcrypt

    让我再说一遍。 对于使用高熵密码的用户,您的解决scheme将显着降低密码的强度。 (对于72个字符的密码,熵丢失了62位,而对于更长的密码则更多)

  • 中等熵随机密码

    该组正在使用包含通用符号的密码,但没有高字节或控制字符。 这些是你的典型密码。

    对于这个组,你将稍微解开更多的熵(不创build它,但允许更多的熵适合bcrypt密码)。 当我稍微说一点,我的意思是稍微。 当您最大限度地利用SHA512所具有的512位时,就会发生收支平衡。 因此,这个峰值是78个字符。

    让我再说一遍。 对于这类密码,在用完熵之前,只能存储6个字符。

  • 低熵非随机密码

    这是使用可能不会随机生成的字母数字字符的组。 像圣经报价或类似的东西。 这些短语每个字符有大约2.3比特的熵。

    对于这个组,你可以通过哈希显着解锁更多的熵(不是创build它,但允许更多的适合bcrypt密码input)。 在你用完熵之前,保本是大约223个字符。

    我们再说一遍。 对于这类密码,预散列明显增加了安全性。

回到现实世界

这种熵计算在现实世界中并不重要。 重要的是猜测熵。 这直接影响了攻击者的行为。 这就是你想要最大化的。

虽然有很less的研究已经进入了熵的猜测,但有一点我想指出。

随机猜测连续72个正确字符的几率非常低。 你有可能赢得powershell球彩票21次,而不是有这个碰撞…这是我们正在谈论的数字有多大。

但是我们可能不会在统计上绊倒。 在短语的情况下,前72个字符的机会是相同的,比随机密码高很多。 但是它仍然很低(根据每个字符2.3比特,你更有可能赢得5次powershell球彩票)。

几乎

实际上,这并不重要。 有人猜测前72个字符的机会是正确的,后面的字符有很大的差别,所以不值得担心。 为什么?

那么,假设你正在说一个短语。 如果这个人能够得到正确的前72个字符,他们真的很幸运(不太可能),或者这是一个常用的短语。 如果这是一个常见的短语,唯一的variables是多久才能做到这一点。

我们来举个例子。 让我们从圣经引用(仅仅因为它是长文本的一个常见的来源,而不是其他原因):

你不能贪图你邻居的房子。 你不可贪恋你的邻居的妻子,男仆,女仆,牛驴,或属于你邻居的任何东西。

这是180个字符 第73个字符是neighbor's g 。 如果你猜到了这么多,你可能不会停下来,但继续其余的经文(因为密码很可能被使用)。 因此,你的“哈希”没有增加太多。

顺便说一句:我绝对不主张使用圣经的报价。 其实正好相反。

结论

你不会真正帮助那些使用长密码哈希的人。 一些团体,你一定可以帮忙。 有些你肯定会受伤。

但最终没有一个太过重要。 我们处理的数字太高了。 熵的差别不会太大。

你最好离开bcrypt。 你更可能搞砸哈希(字面上,你已经做到了,而你不是第一个也不是最后一个犯这个错误)比你试图防止的攻击要发生。

专注于确保网站的其他部分。 并在注册密码框中添加一个密码熵表来指示密码强度(并指示密码是否过长,用户可能希望改变它)…

那是我的$ 0.02至less(或可能超过$ 0.02)…

至于使用“秘密”辣椒:

把一个散列函数join到bcrypt中是没有任何研究的。 因此,如果将一个“peppered”哈希join到bcrypt中会导致未知的漏洞(我们知道hash1(hash2($value))可能会在碰撞抵抗和原像攻击的周围暴露重大漏洞hash1(hash2($value)) ,目前还不清楚。

考虑到你已经在考虑存储一个秘密密钥(“胡椒”),为什么不以一种很好的研究和理解的方式使用它? 为什么不在存储之前encryption哈希?

基本上,在散列密码之后,将整个散列输出提供给强encryptionalgorithm。 然后存储encryption的结果。

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

注意:如果您select这样做,请使用库。 对于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); } 

这是有益的,因为你正在使用所有的algorithm的方式,很好的理解和研究(至less相对)。 记得:

任何人,从最无知的业余人员到最好的密码学家,都可以创build一个他自己无法破解的algorithm。

  • 布鲁斯·施奈尔

Peppering密码确实是一件好事,但让我们看看为什么。

首先,我们应该回答胡椒何时有用的问题。 胡椒只保护密码,只要它保密,所以如果攻击者有权访问服务器本身,这是没有用的。 一个更容易的攻击,虽然是SQL注入,它允许读取数据库(我们的哈希值),我准备了一个SQL注入演示,以显示它是多么容易(点击下一个箭头得到一个准备input)。

那么胡椒究竟有什么帮助? 只要胡椒保持秘密,它可以保护字典攻击的弱密码。 密码1234然后会变成类似1234-p*deDIUZeRweretWy+.O 。 这个密码不仅长得多,而且还包含特殊字符,不会成为任何字典的一部分。

现在我们可以估算出我们的用户将使用什么密码,可能更多的用户会input弱密码,因为有64-72个字符(实际上这将是非常罕见的)密码的用户。

还有一点是蛮力的范围。 SHA256哈希函数将返回256位输出或1.2E77组合,对于蛮力来说,即使对于GPU(如果我正确计算,2013 年在GPU上需要大约2E61年 ),方式也是如此。 所以,我们不应用辣椒的真正劣势。 因为散列值不是系统的,所以你不能用普通模式加速蛮力。

PS据我所知,72字符限制是特定于BCrypt本身的algorithm。 我发现的最好的答案是这个 。

PPS我认为你的例子是有缺陷的,你不能用完整的密码长度生成哈希,并validation与截断的一个。 你可能打算用相同的方法来生成哈希和validation哈希。

Bcrypt使用基于昂贵的Blowfish密钥设置algorithm的algorithm。

对于bcrypt,build议的56字节密码限制(包括空终止字节)涉及Blowfish密钥的448位限制。 超出该限制的任何字节都不会被完全混合到结果散列中。 因此,当您考虑对这些字节产生的散列的实际影响时,bcrypt密码的72个字节的绝对限制就不那么重要了。

如果你认为你的用户通常会select超过55个字节的密码,记住你总是可以增encryption码扩展的轮次,以增encryption码表破坏的安全性(尽pipe这与增加额外字符)。 如果用户的访问权限如此重要以至于用户通常需要长时间密码,那么密码过期也应该是短暂的,如2周。 这意味着密码不太可能保持有效,而黑客会投入资源来打败testing每个试用密码所涉及的工作因素,以查看是否会生成匹配的哈希。

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

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