如何用河豚哈希长密码(> 72个字符)


91

上周,我读了很多有关密码哈希的文章,而Blowfish似乎是目前最好的哈希算法之一(但其中之一),但这不是这个问题的主题!

最多72个字符

河豚只考虑输入的密码中的前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”密码有很多不同的意见。有人说这是不必要的,因为您必须假定秘密胡椒字符串也已知道/已发布,因此它不会增强哈希值。我有一个单独的数据库服务器,因此很有可能只有数据库泄漏了,而不是不断泄漏。

在这种情况下(胡椒未泄漏),您将很难基于字典进行攻击(如果这不正确,请纠正我)。如果您的胡椒粉串也泄漏了:还算不错-您仍然有盐分,并且像没有胡椒粉的土豆泥一样受到了很好的保护。

因此,我认为加密码至少不是坏选择。

建议

我的建议是使用Blowfish哈希来获取长度超过72个字符(和胡椒)的密码:

<?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个字符?

优点

  • 用户无法通过仅输入前72个字符来登录
  • 您可以在不超过字符数限制的情况下添加胡椒
  • hash_hmac的输出可能比密码本身具有更多的熵。
  • 密码由两个不同的函数进行哈希处理

缺点

  • 仅使用64个字符来构建河豚哈希


编辑1:这个问题只解决了blowfish / bcrypt的PHP集成。感谢您的评论!


3
河豚不是唯一一个会截断密码的人,误导人们以为它比实际更安全。这是8个字符限制
DOK

2
是72个字符的截断是Blowfish算法的基础,还是只是PHP的实现?IIRC Blowfish还用于(至少某些)“ nix”上以加密用户密码。
道格拉斯·B·斯台

3
问题出在Bcrypt,而不是Blowfish。我可以单独使用Python和Bcrypt重现此问题。
Blender

@Blender:感谢您的评论和您的工作。我找不到在河豚和bcrypt的php中不同的函数,尽管它们是相同的。但这在php中对我来说没有任何区别吗?我宁愿使用标准的php函数。
Frederik Kammer

1
另请参见Openwall的PHP密码哈希框架(PHPass)。它具有可移植性,并且可以抵抗多种常见的用户密码攻击。编写框架的人(SolarDesigner)与编写John The Ripper的人相同,并担任密码哈希竞赛的评委。因此,他知道有关密码攻击的一两件事。
jww 2014年

Answers:


134

这里的问题基本上是熵的问题。因此,让我们开始寻找那里:

每个字符的熵

每个字节的熵位数为:

  • 十六进制字符
    • 位数:4
    • 价值:16
    • 72个字符的熵:288位
  • 字母数字
    • 位数:6
    • 价值:62
    • 熵(以72个字符为单位):432位
  • “通用”符号
    • 位数:6.5
    • 价值:94
    • 熵(以72个字符为单位):468位
  • 全字节
    • 位数:8
    • 价值:255
    • 72个字符的熵:576位

因此,我们的行为方式取决于我们期望的字符类型。

第一个问题

您的代码的第一个问题是您的“胡椒粉”哈希步骤正在输出十六进制字符(因为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个字符是g第二个字符neighbor's。如果您猜的太多,您可能不会在停下来nei,而是继续其余的经文(因为这就是使用密码的方式)。因此,您的“哈希”并没有增加多少。

顺便说一句:我绝对不是在提倡圣经报价。其实恰恰相反。

结论

您实际上并不会首先通过哈希来帮助很多使用长密码的人。某些团体您绝对可以提供帮助。有些你肯定会受伤。

但最后,这都不是太重要。我们正在处理的数字只是WAY太高。熵的差别不会太大。

最好还是保留bcrypt不变。比您试图阻止的攻击更容易搞乱哈希(从字面上讲,您已经完成了哈希,而且不是第一个或最后一个犯该错误的人)。

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

那至少是我的0.02美元(或者可能超过0.02美元)...

就使用“秘密”胡椒粉而言:

从字面上看,还没有研究将一个哈希函数提供给bcrypt。因此,目前尚不清楚充其量是否会导致未知漏洞(我们知道这样做hash1(hash2($value))会在抗碰撞性和原像攻击周围暴露出重大漏洞),这充其量不会充其量。

考虑到您已经在考虑存储密钥(“胡椒粉”),为什么不以一种经过充分研究和理解的方式使用它呢?为什么不在存储哈希之前对其进行加密?

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

现在,SQL注入攻击不会泄漏任何有用的东西,因为它们没有密码密钥。而且,如果密钥泄露了,攻击者的状况就不会比您使用普通哈希(这是可证明的,没有提供“ pre-hash”的东西)更好。

注意:如果选择执行此操作,请使用库。对于PHP,我强烈推荐Zend Framework 2Zend\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);
}

这是有益的,因为您正在以一种易于理解和研究(至少相对)的方式使用所有算法。记得:

从最笨拙的业余爱好者到最好的密码学家,任何人都可以创建自己无法破解的算法。


6
非常感谢您提供详细的答案。这真的对我有帮助!
弗雷德里克·卡默

1
我对此表示赞赏。不过,这是大多数用户的不二之选,他们使用非常弱的密码,单词和字典中包含的派生词来破解密码,而胡椒将保护他们独立于密码问题。为了避免松懈,您可以将密码和Pepper串联起来。但是,关于加密哈希值的建议可能是添加服务器端机密的最佳解决方案。
martinstoeckli

2
@martinstoeckli:我对胡椒概念的看法不在于它的价值。正是因为加密算法,“胡椒粉”的应用才进入了未知领域。那不是好事。相反,我认为加密原语应该以一种可以组合在一起的方式进行组合。基本上,胡椒的核心概念在我耳中听起来像有些对密码学一无所知的人说的:“散列越多越好!我们加盐,胡椒也好!” 。我只希望有一个更简单,更经过测试和更直接的
提示-ircmaxell,

@ircmaxell-是的,我知道您的观点,并且我同意,只要之后将对哈希值进行加密即可。如果不采取此额外步骤,即使使用良好的哈希算法,字典攻击也只会显示太多弱密码。
martinstoeckli

@martinstoeckli:我不同意。存储秘密不是一件容易的事。相反,如果您以较高的价格使用bcrypt(今天为12),则除最弱的密码类别以外的所有密码都是安全的(字典密码和琐碎的密码均为弱密码)。因此,我宁愿建议人们专注于使用强度仪来教育用户,并让他们首先使用更好的密码...
ircmaxell 2013年

5

加密码肯定是一件好事,但让我们看看为什么。

首先,我们应该回答什么时候胡椒粉才有帮助。Pepper仅在密码保持秘密的情况下才保护密码,因此,如果攻击者可以访问服务器本身,则毫无用处。SQL注入是一种更容易的攻击,它允许对数据库(对我们的哈希值)进行读取访问,我准备了一个SQL注入演示以展示它的简单性(单击下一个箭头以获取预备信息)。输入)。

那胡椒实际上有什么帮助?只要Pepper保持秘密,它就可以保护弱密码免受字典攻击。密码1234将变成类似1234-p*deDIUZeRweretWy+.O。该密码不仅更长,而且还包含特殊字符,并且永远不会成为任何词典的一部分。

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

另一点是暴力破解的范围。sha256哈希函数将返回256位输出或1.2E77组合,即使对于GPU而言,这对于暴力破解来说也是太多了(如果我计算正确,这大约需要2E61年到2013,在GPU上)。因此,使用胡椒粉不会带来真正的不利。由于哈希值不是系统性的,因此无法使用常见模式来加快暴力破解的速度。

PS据我所知,72个字符的限制特定于BCrypt本身的算法。我找到的最好的答案就是这个

PPS我认为您的示例存在缺陷,您无法生成具有完整密码长度的哈希,而无法使用截断的密码进行验证。您可能打算将胡椒以相同的方式应用于生成哈希和验证哈希。


关于您的PPS,我只能说:是的,他可以使用非截断密码的哈希值来验证截断密码,并且仍然可以得到true。这就是这个问题的全部内容。看看自己:viper-7.com/RLKFnB
SLIQ

@Panique-问题不是BCrypt哈希的计算,这是之前的HMAC。为了生成SHA哈希,OP使用全长密码,并将结果用作BCrypt的输入。为了进行验证,他在计算SHA哈希之前将密码截断了,然后将这个完全不同的结果用作BCrypt的输入。HMAC接受任何长度的输入。
martinstoeckli 2013年

2

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

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

如果您认为用户通常会选择长度超过55个字节的密码,请记住,您始终可以改为增加密码扩展次数,以在密码表被破坏的情况下提高安全性(尽管与添加额外的密码相比,这样做必须很多)字符)。如果用户的访问权限非常重要,以至于用户通常需要一个非常长的密码,那么密码的有效期也应该很短,例如2周。这意味着,在黑客将他们的资源投入到击败测试每个试用密码所涉及的工作因素以查看其是否会产生匹配的哈希值方面,密码很难保持有效。

当然,在不违反密码表的情况下,在锁定用户帐户之前,我们最多只允许黑客十次尝试猜测用户的55字节密码;)

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


1
实际上,“密码有效期也应该短一些,例如2周”,“为什么密码太长呢?”,为什么真要保存密码呢,每次都使用密码重置。严重的是,这是错误的解决方案,请使用令牌进行两因素身份验证。
zaph

谢谢@zaph。您能指出一个例子吗?听起来很有趣。
菲尔(Phil)

[DRAFT NIST特殊出版物800-63B数字认证指南](pages.nist.gov/800-63-3/sp800-63b.html),5.1.1.2。记忆秘密验证者:验证者不应要求随意(例如,定期)更改记忆秘密。另请参见Jim Fenton提出的“达到更好的密码要求”
zaph

1
事实是,要求用户更改密码的频率越高,密码选择就越糟,从而降低了安全性。用户只有数量有限的良好易记密码,然后用光了,要么选择真正错误的密码,要么将其写在粘贴在键盘底部的
便条纸上
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.