最佳的bcrypt工作因子

92

什么是密码哈希的理想bcrypt工作因子?

如果我使用10个因子,在我的笔记本电脑上哈希一个密码需要大约0.1秒。如果我们最终拥有一个非常繁忙的网站,这将变成一个相当大的工作量,只是检查人们的密码。

也许更好的做法是使用工作因子7,将总密码哈希工作减少到每次登录大约0.01秒?

如何权衡暴力安全和操作成本?


7
成本阻止了离线攻击。当“在线”时,您可以使用最短的重试间隔(例如5秒)来防止拒绝服务攻击。 - Ian Boyd
3
信息安全重复:bcrypt的推荐轮数 - Christian Strempfer
1
对于任何感兴趣的人,我刚刚编写了一个小型Java CLI工具,用于测试服务器上bcrypt的性能(这显然对于平衡安全性、服务器负载和响应时间非常重要):https://github.com/cdraeger/hash-performance - Blacklight
4个回答

116

请记住,密码被存储在以下格式中:$2a$(两个字符的工作因子)$(22个字符的盐值)(31个字符的哈希值)。它不是一个固定值。

如果您发现负载过高,只需使下次登录时加密为更快计算的内容即可。同样,随着时间的推移和服务器的更新换代,如果负载不成问题,您可以升级他们登录时哈希的强度。

诀窍是让它在未来一直花费大致相同的时间,同时考虑到摩尔定律。该数字是以log2为单位的,因此每当计算机速度翻倍时,默认数字就会增加1。

决定需要多长时间才能破解用户的密码。例如,对于某些常见词典单词,您的帐户创建可能已经警告了其密码太弱了。如果是1000个常用词之一,并且攻击者需要0.1秒才能测试每个单词,那么就需要100秒(某些单词更常见...)。如果用户选择“常见词典单词”+ 2个数字,那将超过两个小时。如果您的密码数据库遭到破坏,攻击者每天只能获取几百个密码,则可以为大多数用户购买数小时或数天的时间来安全更改其密码。这是购买时间的问题。

http://www.postgresql.org/docs/8.3/static/pgcrypto.html 提供了一些破解密码所需的时间,供您参考。当然,他们列出的密码是随机字母。词典单词...实际上你并不能救回那些使用12345作为密码的人。


9
这真是一个优秀的回答。我甚至没有考虑过登录时重新加密的想法。非常感谢! - Chris
1
重加密将如何工作?您需要在某个地方存储旧的bcrypt工作成本,以便在登录时使用它,然后在验证其密码后,您将更新数据库中的哈希和成本。 - Jerry Saravia
6
bcrypt 的美妙之处在于该成本存储在哈希本身内,因此您不需要存储_任何_额外的信息。只需使用当前哈希进行验证,然后立即使用不同成本重新生成哈希即可。 简单! - Mark Locker
@MarkLocker,谢谢Mark!这是一个美丽的加密!但说真的,这使事情变得更容易,是一件美好的事情。 - Jerry Saravia
好的,既然我不被允许编辑它,因为这是一个“错误的回复尝试”(你们甚至读我的编辑吗?),那么请允许我评论这些信息。示例值:$2y$08$fJS4yx0i8kiOzIBIamZ51OWTMrzyE/4je34Oxhw.5xxp3Es7Ke32W。我尝试编辑的原因是:对于“2 chars work”,我不确定它是数字还是十六进制或其他什么,所以必须测试一下。以下是测试结果,供其他人参考,这样你就不必自己尝试了。 - Luc
@MarkLocker:“你不需要存储任何额外的东西。” 另一方面,你可以说无论你是否想要,你始终会存储一些额外的内容;) - Luc

15

简短版

至少需要迭代多少次才能使计算时间达到250毫秒

详细版

当BCrypt于1999年首次发布时,他们列出了实现的默认成本因素:

  • 普通用户:6
  • 超级用户:8

bcrypt成本为6表示64轮(26 = 64)。

他们还指出:

当然,人们选择的任何成本都应定期重新评估

  • 在1976年部署时,crypt每秒仅能散列不到4个密码(每个密码250毫秒)
  • 在1977年,VAX-11/780上可以大约每秒评估3.6次crypt(MD5)。 (每个密码277毫秒)

这让您了解到原始实现者在编写时考虑的延迟类型:

  • 普通用户约250毫秒
  • 超级用户约1秒

但是,当然,你能忍受的时间越长越好。我见过的所有BCrypt实现均使用10作为默认成本。 我的实现(参见链接)也使用了10。 我认为现在是时候将默认成本增加到12了。

我们决定目标至少每个哈希250毫秒。

我的桌面PC是Intel Core i7-2700K CPU @ 3.50 GHz。我最初在2014年1月23日对BCrypt实现进行了基准测试:

1/23/2014  Intel Core i7-2700K CPU @ 3.50 GHz

| Cost | Iterations        |    Duration |
|------|-------------------|-------------|
|  8   |    256 iterations |     38.2 ms | <-- minimum allowed by BCrypt
|  9   |    512 iterations |     74.8 ms |
| 10   |  1,024 iterations |    152.4 ms | <-- current default (BCRYPT_COST=10)
| 11   |  2,048 iterations |    296.6 ms |
| 12   |  4,096 iterations |    594.3 ms |
| 13   |  8,192 iterations |  1,169.5 ms |
| 14   | 16,384 iterations |  2,338.8 ms |
| 15   | 32,768 iterations |  4,656.0 ms |
| 16   | 65,536 iterations |  9,302.2 ms |

输入图像描述

未来证明

不应该是一个固定的常数,而应该是一个固定的最小值

与其使您的密码哈希函数成为:

String HashPassword(String password)
{
   return BCrypt.HashPassword(password, BCRYPT_DEFAULT_COST);
}

应该是这样的:

String HashPassword(String password)
{  
   /*
     Rather than using a fixed default cost, run a micro-benchmark
     to figure out how fast the CPU is.
     Use that to make sure that it takes **at least** 250ms to calculate
     the hash
   */
   Int32 costFactor = this.CalculateIdealCost();
   //Never use a cost lower than the default hard-coded cost
   if (costFactor < BCRYPT_DEFAULT_COST) 
      costFactor = BCRYPT_DEFAULT_COST;

   return BCrypt.HashPassword(password, costFactor);
}

Int32 CalculateIdealCost()
{
    //Benchmark using a cost of 5 (the second-lowest allowed)
    Int32 cost = 5;

    var sw = new Stopwatch();
    sw.Start();
    this.HashPassword("microbenchmark", cost);
    sw.Stop();

    Double durationMS = sw.Elapsed.TotalMilliseconds;

    //Increasing cost by 1 would double the run time.
    //Keep increasing cost until the estimated duration is over 250 ms
    while (durationMS < 250)
    {
       cost += 1;
       durationMS *= 2;
    }

    return cost;
}

理想情况下,这应该是每个人的BCrypt库的一部分,因此不必依赖库的用户定期增加成本,成本会自动增加。


5

我一直在尝试确定我的最佳bcrypt加盐轮数,并希望发布我的发现。正如其他人指出的那样,理想的轮数取决于您的硬件、用户的耐心和潜在攻击者的硬件。

在2023年,我在基本的Heroku Dyno上运行,然后再次在我的本地机器上运行,对一系列不同的盐轮数进行了哈希操作的时间进行了基准测试:

盐轮数 Heroku哈希时间 5600x哈希时间 破解弱密码(28位) 破解中等密码(37位)
7 13毫秒 8毫秒 12天 17年
8 24毫秒 15毫秒 23天 32年
9 47毫秒 29毫秒 1.5个月 63年
10 90毫秒 58毫秒 3个月 126年
11 181毫秒 118毫秒 6个月 256年
12 367毫秒 233毫秒 1年 很长时间
13 735毫秒 461毫秒 2年 "
14 1422毫秒 936毫秒 4年 "
15 2878毫秒 1863毫秒 8年 "
16 5969毫秒 3721毫秒 16年 "

基于著名的XKCD密码漫画的弱密码熵位

需要注意的因素:

  • 破解时间可以根据您可以访问的计算机数量进行划分。拥有僵尸网络或大量资金的黑客可以将这些破解时间缩短数倍。

  • 破解时间会随着时间的推移而减少。泄露的哈希密码在10年后可能会很容易地被破解。

  • 5600X远非现代处理的巅峰。7900X 能够更快地完成此操作。

  • 这是单线程完成的,多线程将使其更快。

基于这些数据,我建议至少进行10轮加密,但强烈建议使用11轮。100-200毫秒对于用户来说是非常短的等待时间,如果并发登录导致延迟,您应该考虑将后端资源增加。我最多建议使用13轮,虽然它会非常安全,但700毫秒对于用户来说是一个很长的等待时间。在10到13之间,你作为开发人员需要做出决策。
PS:我要感谢Ian Boyds提供的精美图表和自动扩展代码。这是每个阅读本文的人都应该考虑的事情,但我也想提供一些最新的数据,以显示bcrypt在当前和常见硬件上实际花费的时间。
请在评论中指出如果此答案已过时,我将尽力更新它。
注意:如何计算破解时间?
要讨论这个问题,我们首先必须讨论密码熵。简单地说:密码熵是基于您用于选择密码的系统计算出来的。因此,如果您的密码是一个4位数的PIN码,则只有10,000个可能的PIN码可供选择。因此,您的密码具有10,000的熵值。通常称为13位熵,因为10,000 ≈ 2¹³。
要确定攻击者破解您的密码需要多长时间,您只需要确定他们平均需要尝试多少次。对于那个4位数的PIN码,他们可以在第一次尝试中猜到它,也可能需要10,000次尝试。但是平均而言,攻击者需要5,000次尝试才能成功。因此,检查单个密码所需的时间乘以所需的猜测次数即为破解时间。

你从哪里获取了你表格中呈现的破解时间信息?我已经在网上搜索了一个小时,但没有找到好的结果:D - Bendeguz Gulyás
@BendeguzGulyás 添加了一条注释,说明了破解时间的计算方法。此外,第一次时我忘记了除以2,现在已经修复了。 - Sam Spade

0

这个问题与确定bcrypt密码哈希的成本因素的最佳和实用方法有关。

在一个你预计用户数量会随着时间增长而增加的服务器上计算用户密码哈希值时,为什么不将用户使用你的服务的时间长度作为决定因素,或许包括他们的登录频率作为该决定的一部分。

Bcrypt成本因素=6 +(用户成员资格年数或某些因素),并且可以对总成本进行可选限制,也许通过该用户的登录频率以某种方式进行修改。

请记住,使用这样的系统或任何确定成本因素的系统必须有效地考虑到计算哈希的成本可能被用作DDOS攻击服务器本身的方法,通过将任何增加成本因素的方法纳入他们的攻击中。


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