什么是密码哈希的理想bcrypt工作因子?
如果我使用10个因子,在我的笔记本电脑上哈希一个密码需要大约0.1秒。如果我们最终拥有一个非常繁忙的网站,这将变成一个相当大的工作量,只是检查人们的密码。
也许更好的做法是使用工作因子7,将总密码哈希工作减少到每次登录大约0.01秒?
如何权衡暴力安全和操作成本?
什么是密码哈希的理想bcrypt工作因子?
如果我使用10个因子,在我的笔记本电脑上哈希一个密码需要大约0.1秒。如果我们最终拥有一个非常繁忙的网站,这将变成一个相当大的工作量,只是检查人们的密码。
也许更好的做法是使用工作因子7,将总密码哈希工作减少到每次登录大约0.01秒?
如何权衡暴力安全和操作成本?
请记住,密码被存储在以下格式中:$2a$(两个字符的工作因子)$(22个字符的盐值)(31个字符的哈希值)
。它不是一个固定值。
如果您发现负载过高,只需使下次登录时加密为更快计算的内容即可。同样,随着时间的推移和服务器的更新换代,如果负载不成问题,您可以升级他们登录时哈希的强度。
诀窍是让它在未来一直花费大致相同的时间,同时考虑到摩尔定律。该数字是以log2为单位的,因此每当计算机速度翻倍时,默认数字就会增加1。
决定需要多长时间才能破解用户的密码。例如,对于某些常见词典单词,您的帐户创建可能已经警告了其密码太弱了。如果是1000个常用词之一,并且攻击者需要0.1秒才能测试每个单词,那么就需要100秒(某些单词更常见...)。如果用户选择“常见词典单词”+ 2个数字,那将超过两个小时。如果您的密码数据库遭到破坏,攻击者每天只能获取几百个密码,则可以为大多数用户购买数小时或数天的时间来安全更改其密码。这是购买时间的问题。
http://www.postgresql.org/docs/8.3/static/pgcrypto.html 提供了一些破解密码所需的时间,供您参考。当然,他们列出的密码是随机字母。词典单词...实际上你并不能救回那些使用12345作为密码的人。
$2y$08$fJS4yx0i8kiOzIBIamZ51OWTMrzyE/4je34Oxhw.5xxp3Es7Ke32W
。我尝试编辑的原因是:对于“2 chars work”,我不确定它是数字还是十六进制或其他什么,所以必须测试一下。以下是测试结果,供其他人参考,这样你就不必自己尝试了。 - Luc至少需要迭代多少次才能使计算时间达到250毫秒
当BCrypt于1999年首次发布时,他们列出了实现的默认成本因素:
bcrypt成本为6表示64轮(26 = 64)。
他们还指出:
当然,人们选择的任何成本都应定期重新评估
这让您了解到原始实现者在编写时考虑的延迟类型:
但是,当然,你能忍受的时间越长越好。我见过的所有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库的一部分,因此不必依赖库的用户定期增加成本,成本会自动增加。
我一直在尝试确定我的最佳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 将能够更快地完成此操作。
这是单线程完成的,多线程将使其更快。
这个问题与确定bcrypt密码哈希的成本因素的最佳和实用方法有关。
在一个你预计用户数量会随着时间增长而增加的服务器上计算用户密码哈希值时,为什么不将用户使用你的服务的时间长度作为决定因素,或许包括他们的登录频率作为该决定的一部分。
Bcrypt成本因素=6 +(用户成员资格年数或某些因素),并且可以对总成本进行可选限制,也许通过该用户的登录频率以某种方式进行修改。
请记住,使用这样的系统或任何确定成本因素的系统必须有效地考虑到计算哈希的成本可能被用作DDOS攻击服务器本身的方法,通过将任何增加成本因素的方法纳入他们的攻击中。