为什么密码哈希(例如php的password_hash)如此缓慢?

9

我正在使用password_hash进行密码加密。但是有一个奇怪的问题,password_hash需要很长时间。以下是一个示例代码,该代码将花费超过1秒钟的时间。这正常吗?

<?php
  $startTime = microtime(TRUE);
  $password='123456';
  $cost=13;
  $hash=password_hash($password, PASSWORD_DEFAULT, ['cost' => $cost]);
  password_verify($password,$hash);
  $endTime = microtime(TRUE);
  $time = $endTime - $startTime;
  echo $time;
?>

结果为:1.0858609676361。

你需要打印密码吗?还是想要存储它? - user4795756
@Fatemeh 我想这只是调试代码。 - ceejayoz
@Burimi 什么?因为什么是好的? - user4795756
@Fatemeh “使用带盐的MD5加密密码是一个不好的想法。这不是因为MD5的加密弱点,而是因为它的速度太快。” - Rosmarine Popcorn
@Burimi 啊哈,好的...! - user4795756
1
13是bcrypt的最大成本,这意味着消息“嘿,函数编写者,你可以做任何你想做的,但要尽可能地强大”,因此实际上你正在要求自己进行长时间调用 - 它需要尽可能合理的哈希量。这并不意味着它将始终哈希一秒钟 - 这取决于硬件 - 但使用最大成本有点像“忽略时间,只需为我生成最佳结果” - 在密码哈希中,通过多次重新哈希可以实现最佳结果。 - Etki
4个回答

13

3v4l上运行似乎完全正常。

密码哈希不是您想要优化的内容。用Leigh on the hash documentation的话来说:

如果您正在为安全性哈希密码等内容,速度不是您的朋友。您应该使用最慢的方法。

慢速哈希意味着难以破解,希望可以使生成彩虹表之类的东西变得不值得麻烦。


9

password_hash的默认算法bcrypt设计为慢速算法。

http://en.wikipedia.org/wiki/Key_stretching

在密码学中,键延展是一种技术,用于通过增加测试每个可能键所需的时间来更安全地保护可能弱的密钥(通常是密码或口令)免受暴力攻击。由人类创建的密码或口令通常太短或可预测,容易被破解。键延展使这样的攻击更加困难。

http://en.wikipedia.org/wiki/Rainbow_table#Defense_against_rainbow_tables

另一种有助于防止预计算攻击的技术是键延展。使用延展时,将盐、密码和若干中间哈希值通过底层哈希函数多次运行,以增加哈希每个密码所需的计算时间。例如,MD5-Crypt使用一个1000次迭代循环,反复将盐、密码和当前中间哈希值馈送回底层MD5哈希函数。用户的密码哈希是盐值(不是秘密)和最终哈希的串联。对用户而言,这些额外时间并不明显,因为他们每次登录只需要等待几分之一秒。另一方面,延展按比例降低暴力攻击的有效性,因为它减少了攻击者在给定时间内可以执行的计算次数。这个原则适用于MD5-Crypt和bcrypt。它还大大增加了构建预计算表所需的时间,但在没有盐的情况下,这只需要做一次。

一秒钟可能有点长 - 您可以通过将$cost降低一到两个级别来尝试将其降至约十分之一秒,这将保持有效的保护同时使延迟对您的用户不可感知。


2
@HuskyHuskie 一块GPU每秒可以做高达100亿个MD5哈希。对于bcrypt哈希来说,十分之一秒足以提供足够的保护,以防止彩虹表攻击。 - ceejayoz
我不是在反驳你,但我认为这不是非黑即白的事情。如果登录类似于Facebook或Twitter,只需登录一次就会记住我,那么我认为他们不会注意到1秒钟。如果应用程序需要更安全(例如政府),则您可能希望它也需要更长时间。如果安全性不是很重要,那么可以降低“$cost”。我只是想提供另一个观点,因为有合理的理由使成本高昂。 - CodyEngel
1
@HuskyHuskie 一个六位密码有190亿种组合。使用bcrypt每秒0.1个哈希值生成彩虹表需要60年时间,而如果是MD5哈希,则不到两秒钟。我对这种安全级别感到非常满意 - 如果我正在构建银行网站,我可能需要8个或更多字符,这将使我有大约400万亿的暴力破解可能性 - 我的PC可以在一天内完成MD5,但使用bcrypt则需要大约一百万年。 - ceejayoz
我并不反对你的观点,但这并不是非黑即白的情况。攻击者没有理由不将彩虹表分成更小的部分并并行生成。正如我之前所说,如果用户只是偶尔登录,他们可能不会注意到0.1秒和1秒之间的区别。你只需要决定哪个更重要。在学术层面上,较低的$cost确实使哈希比较不安全。今天可能无关紧要,但明天就可能很重要。 - CodyEngel
明天你可以提高成本因素。这就是bcrypt的美妙之处! - ceejayoz
显示剩余3条评论

5

是的,这是正常的。这就是cost参数的作用:它允许您调整迭代次数,根据需要使哈希更慢或更快。

您应该尽可能地使哈希变慢,并根据需要尽可能快。原因是密码哈希唯一可行的攻击方式是暴力破解。您希望将成本设置得足够大,以至于简单地暴力破解所有可能的值需要花费极长的时间。这是您对使用密码哈希的攻击者唯一真正的防御。

对于您自己的使用来说,一秒钟似乎太长了。您应该将成本降低一点,以最多保持几百毫秒的速度。根据需要调整目标系统。


0

首先,password_hash 不是加密。

password_hash() 使用强的单向散列算法创建一个新的密码哈希。 password_hash()crypt() 兼容。因此,由 crypt() 创建的密码哈希可以与 password_hash() 一起使用。

哈希是单向的,无论您传递什么值,它都将始终具有相同的结果。但是,您无法从哈希中获取原始字符串。这非常适用于密码,因为您希望存储用户密码的模糊版本,以便在登录时可以轻松比较而不实际存储密码本身。这意味着如果数据库受到攻击,只要密码被哈希处理,攻击者就不会获得密码,而是获得基本无用的哈希密码(您可以使用彩虹表和其他技术来获得生成的哈希,但需要相当大的努力)。

这就引出了你最初的问题。为什么密码哈希很慢?它们很慢是因为从哈希中获取原始字符串的唯一方法之一是重新生成该哈希。因此,如果每个哈希需要1秒钟来生成,那么它将成为一个比使用快速哈希(如md5sha版本)更大的时间开销。快速哈希在几乎所有方面都非常好,除了密码存储。

希望这回答了你的问题。另外,我强烈建议为每个用户生成一个唯一的盐,并将其作为选项之一传递到password_hash中。这个盐可以明文存储在数据库中,与哈希密码一起存储。对于每个密码使用不同的盐将其添加到密码中,这样攻击者就必须为数据库中的每个盐生成彩虹表。此时,攻击者可能会利用其他技术来获取密码,而不是通过数据库入侵。


当前最佳实践建议是不指定盐,而是让password_hash生成。看起来在过去的5.5年里这可能已经改变了,因此认为值得为任何未来的读者做出说明。 - acorncom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接