Kurzfassung
Die Anzahl der Iterationen, für die mindestens 250 ms berechnet werden müssen
Lange Version
Bei der Erstveröffentlichung von BCrypt im Jahr 1999 wurden die Standardkostenfaktoren ihrer Implementierung aufgelistet:
- normaler Benutzer: 6
- Superuser: 8
Ein Verschlüsselungspreis von 6 bedeutet 64 Runden (2 6 = 64).
Sie bemerken auch:
Natürlich sollten die Kosten, die die Menschen wählen, von Zeit zu Zeit neu bewertet werden
- Zum Zeitpunkt der Bereitstellung im Jahr 1976 konnte die Krypta weniger als 4 Kennwörter pro Sekunde hashen. (250 ms pro Passwort)
- Im Jahr 1977 konnte auf einem VAX-11/780 die Krypta (MD5) etwa 3,6 Mal pro Sekunde ausgewertet werden. (277 ms pro Passwort)
Das gibt Ihnen einen Eindruck von den Verzögerungen, die die ursprünglichen Implementierer beim Schreiben in Betracht gezogen haben:
- ~ 250 ms für normale Benutzer
- ~ 1 Sekunde für Superuser.
Aber je länger Sie stehen können, desto besser. Jede BCrypt-Implementierung, die ich gesehen habe, wird 10
als Standardkosten verwendet. Und meine Implementierung hat das genutzt. Ich glaube, es ist Zeit für mich, die Standardkosten auf 12 zu erhöhen.
Wir haben beschlossen, nicht weniger als 250 ms pro Hash anzuvisieren.
Mein Desktop-PC ist eine Intel Core i7-2700K-CPU mit 3,50 GHz. Ich habe ursprünglich eine BCrypt-Implementierung am 23.01.2014 bewertet:
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 |
Zukunftssicherheit
Anstatt eine feste Konstante zu haben, sollte es ein festes Minimum sein .
Anstatt Ihre Passwort-Hash-Funktion zu haben, seien Sie:
String HashPassword(String password)
{
return BCrypt.HashPassword(password, BCRYPT_DEFAULT_COST);
}
es sollte so etwas sein wie:
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;
}
Und im Idealfall ist dies Teil der BCrypt-Bibliothek aller Benutzer. Anstatt sich darauf zu verlassen, dass Benutzer der Bibliothek die Kosten regelmäßig erhöhen, erhöhen sich die Kosten regelmäßig von selbst.