Krótka wersja
Liczba iteracji, które dają co najmniej 250 ms do obliczenia
Długa wersja
Kiedy BCrypt został opublikowany po raz pierwszy, w 1999 roku, wymienili domyślne współczynniki kosztów ich implementacji:
- normalny użytkownik: 6
- superużytkownik: 8
Koszt bcrypt wynoszący 6 oznacza 64 rundy (2 6 = 64).
Zwracają również uwagę:
Oczywiście, niezależnie od tego, jaki koszt wybiorą ludzie, powinien być od czasu do czasu ponownie oceniany
- W momencie wdrożenia w 1976 r. Crypt mógł mieć mniej niż 4 hasła na sekundę. (250 ms na hasło)
- W 1977 roku na VAX-11/780 wartość krypty (MD5) mogła być oceniana około 3,6 razy na sekundę. (277 ms na hasło)
To daje wyobrażenie o rodzaju opóźnień, które pierwotni implementatorzy rozważali, kiedy to pisali:
- ~ 250 ms dla zwykłych użytkowników
- ~ 1 sekunda dla superużytkowników.
Ale oczywiście im dłużej możesz stać, tym lepiej. Każda implementacja BCrypt, którą widziałem, była używana 10
jako domyślny koszt. I moja implementacja to wykorzystała. Uważam, że nadszedł czas, aby zwiększyć domyślny koszt do 12.
Zdecydowaliśmy, że chcemy osiągnąć cel nie mniej niż 250 ms na hash.
Mój komputer stacjonarny to procesor Intel Core i7-2700K @ 3,50 GHz. Pierwotnie testowałem implementację BCrypt 23.01.2014:
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 |
Sprawdzanie przyszłości
Zamiast mieć stałą stałą, powinno to być ustalone minimum .
Zamiast mieć funkcję skrótu hasła:
String HashPassword(String password)
{
return BCrypt.HashPassword(password, BCRYPT_DEFAULT_COST);
}
powinno to być coś takiego:
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;
}
Idealnie byłoby, gdyby była to część biblioteki BCrypt każdego użytkownika, więc zamiast polegać na użytkownikach biblioteki w celu okresowego zwiększania kosztów, koszt okresowo sam się zwiększa.