ChatGPT解决这个技术问题 Extra ChatGPT

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

上周我阅读了很多关于密码散列的文章,而 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 questionpassword_verify 返回 false

问题

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

优点

用户无法通过仅输入前 72 个字符来登录

您可以在不超过字符限制的情况下添加辣椒

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

密码由两个不同的函数散列

缺点

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

编辑 1:这个问题仅涉及河豚/bcrypt 的 PHP 集成。感谢您的评论!

Blowfish 并不是唯一一种截断密码的方法,它误导人们认为它比实际更安全。这是interesting history of the 8-character limit.
72 个字符的截断是 Blowfish 算法的基础,还是只是 PHP 实现? IIRC Blowfish 也用于(至少一些)'nixes 来加密用户密码。
问题在于 Bcrypt,而不是 Blowfish。我可以单独使用 Python 和 Bcrypt 重现这个问题。
@Blender:感谢您的评论和您的工作。我在 php 中找不到针对河豚和 bcrypt 的不同函数,尽管它们是相同的。但是在php中对我来说没有什么不同吗?我更喜欢使用标准的 php 函数。
另请参阅 Openwall 的 PHP password hashing framework (PHPPass)。它的便携性和强化了针对用户密码的一些常见攻击。编写框架 (SolarDesigner) 的人与编写 John The Ripper 并在 Password Hashing Competition 中担任评委的人是同一个人。所以他知道关于密码攻击的一两件事。

G
Gras Double

这里的问题基本上是熵问题。所以让我们开始看那里:

每个字符的熵

每个字节的熵位数为:

十六进制字符位: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);
}

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

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

布鲁斯·施奈尔


非常非常感谢您的详细回答。这真的对我有帮助!
我对这个答案的赞美。虽然有一点点挑剔,但绝大多数用户使用非常弱的密码、字典中包含的单词和派生词来破解密码,辣椒可以保护他们不受熵问题的影响。为避免丢失熵,您可以将密码和胡椒连接起来。但是,您关于加密哈希值的建议可能是添加服务器端机密的最佳解决方案。
@martinstoeckli:我对胡椒概念的问题不在于它的价值。就密码算法而言,“胡椒”的应用进入了未知领域。这不是一件好事。相反,我认为密码原语应该以它们被设计为一起使用的方式组合。基本上,胡椒的核心概念在我耳边听起来就像一些对密码学一无所知的人说的“更多的哈希更好!我们有盐,胡椒也很好!”。我宁愿有一个更简单、更多测试和更直接的 impl
@ircmaxell - 是的,我知道你的观点,我同意,只要哈希值之后会被加密。如果你不采取这个额外的步骤,字典攻击只会暴露太多的弱密码,即使是一个好的哈希算法。
@martinstoeckli:我不同意。存储秘密不是一件小事。相反,如果您以较高的成本使用 bcrypt(今天为 12 个),那么除了最弱的密码类别之外,所有密码都是安全的(字典密码和普通密码都是弱密码)。所以我宁愿建议人们专注于用 strength meters 教育用户并让他们首先使用更好的密码......
C
Community

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 我认为您的示例有缺陷,您无法生成具有完整密码长度的哈希,并使用截断的哈希进行验证。您可能打算以相同的方式应用辣椒来生成哈希和验证哈希。


关于您的 PPS,我只能说:是的,他可以使用未截断密码的哈希值验证截断密码,仍然得到 true。这就是这个问题的全部内容。自己看看:viper-7.com/RLKFnB
@Panique - 问题不是 BCrypt 哈希的计算,而是之前的 HMAC。为了生成 SHA 哈希,OP 使用全长密码并将结果用作 BCrypt 的输入。为了验证,他在计算 SHA 哈希之前截断了密码,然后使用这个完全不同的结果作为 BCrypt 的输入。 HMAC 接受任何长度的输入。
P
Phil

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

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

如果您认为您的用户通常会选择长度超过 55 个字节的密码,请记住,您始终可以增加密码拉伸的轮次,以在密码表泄露的情况下提高安全性(尽管与添加额外的密码相比,这必须很多人物)。如果用户的访问权限非常重要,以至于用户通常需要很长的密码,那么密码的有效期也应该很短,比如 2 周。这意味着当黑客投入资源来击败测试每个试用密码以查看它是否会产生匹配的哈希时,密码不太可能保持有效。

当然,在密码表没有被破坏的情况下,我们应该只允许黑客最多尝试十次猜测用户的 55 字节密码,然后才能锁定用户的帐户;)

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


“密码有效期也应该很短,比如 2 周”的“超长密码”,真的,为什么还要保存密码,每次都使用密码重置。说真的,这是错误的解决方案,使用令牌进行两因素身份验证。
谢谢@zaph。你能给我举个例子吗?听起来很有趣。
[草案 NIST 特别出版物 800-63B 数字认证指南](pages.nist.gov/800-63-3/sp800-63b.html),5.1.1.2。记忆秘密验证者:验证者不应要求任意更改记忆秘密(例如,定期)。另见 Jim Fenton 的Toward Better Password Requirements
问题是用户需要更改密码的次数越多,密码选择就越糟糕,从而降低了安全性。用户有数量有限的好记密码,他们用完了,要么选择了非常糟糕的密码,要么将它们写在贴在键盘底部的便利贴上,等等。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅