Cuando BCrypt se publicó por primera vez, en 1999, enumeraron los factores de costo predeterminados de su implementación:
- usuario normal: 6
- superusuario: 8
También tienen en cuenta:
Por supuesto, cualquier costo que la gente elija debe ser reevaluado
de vez en cuando
Un costo de cifrado de 6 significa 64 rondas (2 6 = 64).
Si usamos ese valor inicial de "usuario normal" , queremos intentar ajustar la inflación de la potencia de cálculo ( suponiendo que en promedio se duplica cada 18 meses ).
R = R 0 × 2 (meses / 18)
R = 64 × 2 (meses / 18)
Hoy (9 de marzo de 2015) han pasado 171 meses desde el 31 de diciembre de 1999 (o usar 1/1/2000 por simplicidad), el número de rondas se debería haber duplicado un poco más de 9 veces:
R = 64 × 2 (171/18)
R = 64 × 2 9.5
R = 64 × 724.1
R = 46,341.0
Al final, queremos convertir eso de nuevo a un factor de costo
costo = ln (R) / ln (2)
costo = ln (46,341.0) / ln (2)
costo = 15.5
La practicidad de un factor de costo o 15 depende de la potencia de cálculo de su servidor. Por ejemplo, mi PC de escritorio es una CPU Intel Core i7-2700K a 3.50 GHz. Originalmente hice una evaluación comparativa de una implementación de BCrypt el 23 de enero de 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 |
Pero eso fue 2014
Esos cronogramas se calcularon originalmente a principios de 2014. En mi cálculo solo deberían haberse utilizado 156 meses (en lugar de 171):
R = 64 × 2 (156/18)
R = 64 × 2 8.66
R = 64 × 406.8
R = 26,035.2
costo = ln (R) / ln (2)
costo = ln (26,035.2) / ln (2)
costo = 14.7
Pero el i7-2700K ya se ha suspendido
El i7-2700K ya se había suspendido (Q1 2013) cuando ejecuté mis puntos de referencia. Se lanzó, y fue de vanguardia, en el cuarto trimestre de 2011. Si ejecuto los números para el cuarto trimestre de 2011:
R = 64 × 2 (129/18)
R = 64 × 2 7.16
R = 64 × 143.7
R = 9,196.8
costo = ln (R) / ln (2)
costo = ln (9,196.8) / ln (2)
costo = 13.2
Un costo de 13 es, en mi escritorio, casi 2 segundos en un segundo.
¿Cuánto tiempo puede soportarlo?
Eso le da una idea del tipo de retrasos que los implementadores originales estaban considerando cuando lo escribieron: ~ 0.5-1 segundo.
Pero, por supuesto, cuanto más tiempo puedas soportar, mejor. Cada implementación de BCrypt que he visto utiliza 10
como el costo predeterminado. Y mi implementación usó eso. Creo que es hora de que aumente el costo predeterminado a 12.
Pruebas futuras
También podría cambiar la función hash:
hash = HashPassword("correct battery horse stapler");
es decir, en el que confía en el costo predeterminado, para utilizar en cambio un costo que se desliza automáticamente. De esta manera el costo se incrementa con el tiempo. Cambio:
String HashPassword(String password)
{
return BCrypt.HashPassword(password, BCRYPT_DEFAULT_COST);
}
a algo como:
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;
}
Editar 12/03/2015 : números de velocidad actualizados. El compilador de 32 bits de Delphi XE6 (c.2013) genera un código de un orden de magnitud más rápido que Delphi 5 (c.1999) para el mismo procesador. El compilador Delphi XE6 de 64 bits genera un código un 20% más lento que el compilador de 32 bits.