¿Es "doble hashing" una contraseña menos segura que simplemente hacerlo una vez?

293

¿El hashing de una contraseña dos veces antes del almacenamiento es más o menos seguro que simplemente hacerlo una vez?

De lo que estoy hablando es de hacer esto:

$hashed_password = hash(hash($plaintext_password));

en lugar de solo esto:

$hashed_password = hash($plaintext_password);

Si es menos seguro, ¿puede proporcionar una buena explicación (o un enlace a una)?

Además, ¿la función hash utilizada hace la diferencia? ¿Hay alguna diferencia si mezclas md5 y sha1 (por ejemplo) en lugar de repetir la misma función hash?

Nota 1: cuando digo "doble hashing", estoy hablando de cambiar una contraseña dos veces en un intento de ocultarla. No estoy hablando de la técnica para resolver colisiones .

Nota 2: Sé que necesito agregar una sal al azar para que sea realmente segura. La pregunta es si el hashing dos veces con el mismo algoritmo ayuda o perjudica al hash.

Bill el lagarto
fuente
2
Hash(password)y Hash(Hash(password))son igualmente inseguros. Ambos carecen de la noción de seguridad semántica . Es decir, la salida es distinguible de aleatoria. Por ejemplo, MD5("password")es 5f4dcc3b5aa765d61d8327deb882cf99. Sé que ese es el hash MD5 de password, y es distinguible de aleatorio. En cambio, debe usar un HMAC. Es demostrablemente seguro y es un PRF.
jww

Respuestas:

267

Hashing una contraseña una vez es inseguro

No, los hashes múltiples no son menos seguros; son una parte esencial del uso seguro de la contraseña.

La iteración del hash aumenta el tiempo que le toma a un atacante probar cada contraseña en su lista de candidatos. Puede aumentar fácilmente el tiempo que lleva atacar una contraseña de horas a años.

La iteración simple no es suficiente

El simple hecho de encadenar la salida hash a la entrada no es suficiente para la seguridad. La iteración debería tener lugar en el contexto de un algoritmo que preserva la entropía de la contraseña. Afortunadamente, hay varios algoritmos publicados que han tenido suficiente escrutinio para dar confianza en su diseño.

Un buen algoritmo de derivación de clave como PBKDF2 inyecta la contraseña en cada ronda de hash, mitigando las preocupaciones sobre colisiones en la salida de hash. PBKDF2 se puede utilizar para la autenticación de contraseña tal como está. Bcrypt sigue la derivación de clave con un paso de cifrado; de esa manera, si se descubre una forma rápida de revertir la derivación de la clave, un atacante aún tiene que completar un ataque de texto sin formato conocido.

Cómo romper una contraseña

Las contraseñas almacenadas necesitan protección contra un ataque fuera de línea. Si las contraseñas no son saladas, pueden romperse con un ataque de diccionario precalculado (por ejemplo, usando una Tabla Rainbow). De lo contrario, el atacante debe dedicar tiempo a calcular un hash para cada contraseña y ver si coincide con el hash almacenado.

Todas las contraseñas no son igualmente probables. Los atacantes pueden buscar exhaustivamente todas las contraseñas cortas, pero saben que sus posibilidades de éxito con la fuerza bruta disminuyen considerablemente con cada personaje adicional. En cambio, usan una lista ordenada de las contraseñas más probables. Comienzan con "contraseña123" y avanzan a contraseñas de uso menos frecuente.

Digamos que una lista de atacantes es larga, con 10 mil millones de candidatos; supongamos también que un sistema de escritorio puede calcular 1 millón de hashes por segundo. El atacante puede probar que su lista completa dura menos de tres horas si solo se usa una iteración. Pero si solo se usan 2000 iteraciones, ese tiempo se extiende a casi 8 meses. Para derrotar a un atacante más sofisticado, uno capaz de descargar un programa que puede aprovechar la potencia de su GPU, por ejemplo, necesita más iteraciones.

¿Cuánto es suficiente?

El número de iteraciones a utilizar es una compensación entre la seguridad y la experiencia del usuario. El hardware especializado que pueden usar los atacantes es barato, pero aún puede realizar cientos de millones de iteraciones por segundo. El rendimiento del sistema del atacante determina cuánto tiempo se tarda en descifrar una contraseña dada una cantidad de iteraciones. Pero no es probable que su aplicación use este hardware especializado. La cantidad de iteraciones que puede realizar sin agravar a los usuarios depende de su sistema.

Probablemente pueda permitir que los usuarios esperen un extra segundo extra más o menos durante la autenticación. Perfile su plataforma de destino y use tantas iteraciones como pueda. Las plataformas que he probado (un usuario en un dispositivo móvil o muchos usuarios en una plataforma de servidor) pueden admitir cómodamente PBKDF2 con entre 60,000 y 120,000 iteraciones, o bcrypt con un factor de costo de 12 o 13.

Más antecedentes

Lea PKCS # 5 para obtener información autorizada sobre el papel de la sal y las iteraciones en el hash. Aunque PBKDF2 estaba destinado a generar claves de cifrado a partir de contraseñas, funciona bien como un hash unidireccional para la autenticación de contraseña. Cada iteración de bcrypt es más costosa que un hash SHA-2, por lo que puede usar menos iteraciones, pero la idea es la misma. Bcrypt también va un paso más allá de la mayoría de las soluciones basadas en PBKDF2 al usar la clave derivada para cifrar un texto plano bien conocido. El texto de cifrado resultante se almacena como el "hash", junto con algunos metadatos. Sin embargo, nada le impide hacer lo mismo con PBKDF2.

Aquí hay otras respuestas que he escrito sobre este tema:

erickson
fuente
68
Hacer un algoritmo lento intencionalmente es una práctica aceptada cuando se intenta evitar ataques de diccionario contra almacenes de autenticación comprometidos. La técnica se llama "fortalecimiento clave" o "estiramiento clave". Ver en.wikipedia.org/wiki/Key_stretching
17
@RoBorg: no importa cuán lenta sea su implementación, sino cuán lenta será la implementación de un atacante: si el hash en sí es miles de veces más lento, le tomará al atacante miles de veces más tiempo forzar la contraseña por fuerza bruta.
orip
55
Podría decirse que querría colisiones dentro del espacio de 128 bits 0 a 2 ^ 128-1. Si el espacio de salida 2 ^ 128 del algoritmo hash es perfecto, entonces, teóricamente, solo tiene un cifrado de sustitución con un alfabeto de 2 ^ 128 glifos.
jmucchiello 05 de
13
@devin: no es "mi solución", es una práctica ampliamente aceptada, integrada en estándares de criptografía basados ​​en contraseña como PKCS # 5 y recomendada por expertos como Robert Morris. Es extremadamente escalable, ya que la fracción del tiempo dedicado a autenticar usuarios es pequeña en una aplicación legítima. Solo se vuelve difícil escalar cuando su aplicación está descifrando contraseñas, de ahí la recomendación. Ciertamente, el espacio de búsqueda de un hash es más pequeño que el de las posibles contraseñas, pero incluso un espacio de 128 bits es demasiado grande para la búsqueda de fuerza bruta. La amenaza contra la que defenderse es un ataque de diccionario fuera de línea.
erickson
66
Me refería no a los inconvenientes para el usuario individual, sino al estrés que se le aplicaría al servidor si tuviera una gran base de usuarios, ya que depende de la carga de la CPU para ralentizar el número de solicitudes. Significa que si agrega más potencia de CPU, está reduciendo la restricción sobre esos atacantes de fuerza bruta. - Sin embargo, tiene toda la razón sobre la escalabilidad y la práctica ampliamente aceptada. Me equivoqué sobre casi todas las cosas que dije en mis comentarios anteriores. Lo siento :)
DevinB
227

Para aquellos que dicen que es seguro, son correctos en general . El hashing "doble" (o la expansión lógica de eso, iterando una función hash) es absolutamente seguro si se hace correctamente , para una preocupación específica.

Para aquellos que dicen que es inseguro, tienen razón en este caso . El código que se publica en la pregunta es inseguro. Hablemos de por qué:

$hashed_password1 = md5( md5( plaintext_password ) );
$hashed_password2 = md5( plaintext_password );

Hay dos propiedades fundamentales de una función hash que nos preocupan:

  1. Resistencia previa a la imagen : dado un hash $h, debería ser difícil encontrar un mensaje $mtal que$h === hash($m)

  2. Segunda resistencia previa a la imagen : dado un mensaje $m1, debería ser difícil encontrar un mensaje diferente de $m2modo quehash($m1) === hash($m2)

  3. Resistencia de colisión : debería ser difícil encontrar un par de mensajes de ($m1, $m2)modo que hash($m1) === hash($m2)(tenga en cuenta que esto es similar a la resistencia de la segunda imagen previa, pero diferente en que aquí el atacante tiene control sobre ambos mensajes) ...

Para el almacenamiento de contraseñas , todo lo que realmente nos importa es la resistencia previa a la imagen . Los otros dos serían discutibles, porque $m1es la contraseña del usuario que estamos tratando de mantener a salvo. Entonces, si el atacante ya lo tiene, el hash no tiene nada que proteger ...

DESCARGO DE RESPONSABILIDAD

Todo lo que sigue se basa en la premisa de que todo lo que nos importa es la resistencia previa a la imagen . Es posible que las otras dos propiedades fundamentales de las funciones hash no se mantengan (y normalmente no) de la misma manera. Por lo tanto, las conclusiones de esta publicación solo son aplicables cuando se utilizan funciones hash para el almacenamiento de contraseñas. No son aplicables en general ...

Empecemos

En aras de esta discusión, inventemos nuestra propia función hash:

function ourHash($input) {
    $result = 0;
    for ($i = 0; $i < strlen($input); $i++) {
        $result += ord($input[$i]);
    }
    return (string) ($result % 256);
}

Ahora debería ser bastante obvio lo que hace esta función hash. Suma los valores ASCII de cada carácter de entrada y luego toma el módulo de ese resultado con 256.

Entonces probémoslo:

var_dump(
    ourHash('abc'), // string(2) "38"
    ourHash('def'), // string(2) "47"
    ourHash('hij'), // string(2) "59"
    ourHash('klm')  // string(2) "68"
);

Ahora, veamos qué sucede si lo ejecutamos varias veces alrededor de una función:

$tests = array(
    "abc",
    "def",
    "hij",
    "klm",
);

foreach ($tests as $test) {
    $hash = $test;
    for ($i = 0; $i < 100; $i++) {
        $hash = ourHash($hash);
    }
    echo "Hashing $test => $hash\n";
}

Eso da salida:

Hashing abc => 152
Hashing def => 152
Hashing hij => 155
Hashing klm => 155

Hrm, wow. ¡Hemos generado colisiones! Tratemos de ver por qué:

Aquí está la salida del hash de una cadena de cada salida hash posible:

Hashing 0 => 48
Hashing 1 => 49
Hashing 2 => 50
Hashing 3 => 51
Hashing 4 => 52
Hashing 5 => 53
Hashing 6 => 54
Hashing 7 => 55
Hashing 8 => 56
Hashing 9 => 57
Hashing 10 => 97
Hashing 11 => 98
Hashing 12 => 99
Hashing 13 => 100
Hashing 14 => 101
Hashing 15 => 102
Hashing 16 => 103
Hashing 17 => 104
Hashing 18 => 105
Hashing 19 => 106
Hashing 20 => 98
Hashing 21 => 99
Hashing 22 => 100
Hashing 23 => 101
Hashing 24 => 102
Hashing 25 => 103
Hashing 26 => 104
Hashing 27 => 105
Hashing 28 => 106
Hashing 29 => 107
Hashing 30 => 99
Hashing 31 => 100
Hashing 32 => 101
Hashing 33 => 102
Hashing 34 => 103
Hashing 35 => 104
Hashing 36 => 105
Hashing 37 => 106
Hashing 38 => 107
Hashing 39 => 108
Hashing 40 => 100
Hashing 41 => 101
Hashing 42 => 102
Hashing 43 => 103
Hashing 44 => 104
Hashing 45 => 105
Hashing 46 => 106
Hashing 47 => 107
Hashing 48 => 108
Hashing 49 => 109
Hashing 50 => 101
Hashing 51 => 102
Hashing 52 => 103
Hashing 53 => 104
Hashing 54 => 105
Hashing 55 => 106
Hashing 56 => 107
Hashing 57 => 108
Hashing 58 => 109
Hashing 59 => 110
Hashing 60 => 102
Hashing 61 => 103
Hashing 62 => 104
Hashing 63 => 105
Hashing 64 => 106
Hashing 65 => 107
Hashing 66 => 108
Hashing 67 => 109
Hashing 68 => 110
Hashing 69 => 111
Hashing 70 => 103
Hashing 71 => 104
Hashing 72 => 105
Hashing 73 => 106
Hashing 74 => 107
Hashing 75 => 108
Hashing 76 => 109
Hashing 77 => 110
Hashing 78 => 111
Hashing 79 => 112
Hashing 80 => 104
Hashing 81 => 105
Hashing 82 => 106
Hashing 83 => 107
Hashing 84 => 108
Hashing 85 => 109
Hashing 86 => 110
Hashing 87 => 111
Hashing 88 => 112
Hashing 89 => 113
Hashing 90 => 105
Hashing 91 => 106
Hashing 92 => 107
Hashing 93 => 108
Hashing 94 => 109
Hashing 95 => 110
Hashing 96 => 111
Hashing 97 => 112
Hashing 98 => 113
Hashing 99 => 114
Hashing 100 => 145
Hashing 101 => 146
Hashing 102 => 147
Hashing 103 => 148
Hashing 104 => 149
Hashing 105 => 150
Hashing 106 => 151
Hashing 107 => 152
Hashing 108 => 153
Hashing 109 => 154
Hashing 110 => 146
Hashing 111 => 147
Hashing 112 => 148
Hashing 113 => 149
Hashing 114 => 150
Hashing 115 => 151
Hashing 116 => 152
Hashing 117 => 153
Hashing 118 => 154
Hashing 119 => 155
Hashing 120 => 147
Hashing 121 => 148
Hashing 122 => 149
Hashing 123 => 150
Hashing 124 => 151
Hashing 125 => 152
Hashing 126 => 153
Hashing 127 => 154
Hashing 128 => 155
Hashing 129 => 156
Hashing 130 => 148
Hashing 131 => 149
Hashing 132 => 150
Hashing 133 => 151
Hashing 134 => 152
Hashing 135 => 153
Hashing 136 => 154
Hashing 137 => 155
Hashing 138 => 156
Hashing 139 => 157
Hashing 140 => 149
Hashing 141 => 150
Hashing 142 => 151
Hashing 143 => 152
Hashing 144 => 153
Hashing 145 => 154
Hashing 146 => 155
Hashing 147 => 156
Hashing 148 => 157
Hashing 149 => 158
Hashing 150 => 150
Hashing 151 => 151
Hashing 152 => 152
Hashing 153 => 153
Hashing 154 => 154
Hashing 155 => 155
Hashing 156 => 156
Hashing 157 => 157
Hashing 158 => 158
Hashing 159 => 159
Hashing 160 => 151
Hashing 161 => 152
Hashing 162 => 153
Hashing 163 => 154
Hashing 164 => 155
Hashing 165 => 156
Hashing 166 => 157
Hashing 167 => 158
Hashing 168 => 159
Hashing 169 => 160
Hashing 170 => 152
Hashing 171 => 153
Hashing 172 => 154
Hashing 173 => 155
Hashing 174 => 156
Hashing 175 => 157
Hashing 176 => 158
Hashing 177 => 159
Hashing 178 => 160
Hashing 179 => 161
Hashing 180 => 153
Hashing 181 => 154
Hashing 182 => 155
Hashing 183 => 156
Hashing 184 => 157
Hashing 185 => 158
Hashing 186 => 159
Hashing 187 => 160
Hashing 188 => 161
Hashing 189 => 162
Hashing 190 => 154
Hashing 191 => 155
Hashing 192 => 156
Hashing 193 => 157
Hashing 194 => 158
Hashing 195 => 159
Hashing 196 => 160
Hashing 197 => 161
Hashing 198 => 162
Hashing 199 => 163
Hashing 200 => 146
Hashing 201 => 147
Hashing 202 => 148
Hashing 203 => 149
Hashing 204 => 150
Hashing 205 => 151
Hashing 206 => 152
Hashing 207 => 153
Hashing 208 => 154
Hashing 209 => 155
Hashing 210 => 147
Hashing 211 => 148
Hashing 212 => 149
Hashing 213 => 150
Hashing 214 => 151
Hashing 215 => 152
Hashing 216 => 153
Hashing 217 => 154
Hashing 218 => 155
Hashing 219 => 156
Hashing 220 => 148
Hashing 221 => 149
Hashing 222 => 150
Hashing 223 => 151
Hashing 224 => 152
Hashing 225 => 153
Hashing 226 => 154
Hashing 227 => 155
Hashing 228 => 156
Hashing 229 => 157
Hashing 230 => 149
Hashing 231 => 150
Hashing 232 => 151
Hashing 233 => 152
Hashing 234 => 153
Hashing 235 => 154
Hashing 236 => 155
Hashing 237 => 156
Hashing 238 => 157
Hashing 239 => 158
Hashing 240 => 150
Hashing 241 => 151
Hashing 242 => 152
Hashing 243 => 153
Hashing 244 => 154
Hashing 245 => 155
Hashing 246 => 156
Hashing 247 => 157
Hashing 248 => 158
Hashing 249 => 159
Hashing 250 => 151
Hashing 251 => 152
Hashing 252 => 153
Hashing 253 => 154
Hashing 254 => 155
Hashing 255 => 156

Observe la tendencia hacia números más altos. Esa resulta ser nuestra muerte. Ejecutar el hash 4 veces ($ hash = ourHash ($ hash) `, para cada elemento) termina dándonos:

Hashing 0 => 153
Hashing 1 => 154
Hashing 2 => 155
Hashing 3 => 156
Hashing 4 => 157
Hashing 5 => 158
Hashing 6 => 150
Hashing 7 => 151
Hashing 8 => 152
Hashing 9 => 153
Hashing 10 => 157
Hashing 11 => 158
Hashing 12 => 150
Hashing 13 => 154
Hashing 14 => 155
Hashing 15 => 156
Hashing 16 => 157
Hashing 17 => 158
Hashing 18 => 150
Hashing 19 => 151
Hashing 20 => 158
Hashing 21 => 150
Hashing 22 => 154
Hashing 23 => 155
Hashing 24 => 156
Hashing 25 => 157
Hashing 26 => 158
Hashing 27 => 150
Hashing 28 => 151
Hashing 29 => 152
Hashing 30 => 150
Hashing 31 => 154
Hashing 32 => 155
Hashing 33 => 156
Hashing 34 => 157
Hashing 35 => 158
Hashing 36 => 150
Hashing 37 => 151
Hashing 38 => 152
Hashing 39 => 153
Hashing 40 => 154
Hashing 41 => 155
Hashing 42 => 156
Hashing 43 => 157
Hashing 44 => 158
Hashing 45 => 150
Hashing 46 => 151
Hashing 47 => 152
Hashing 48 => 153
Hashing 49 => 154
Hashing 50 => 155
Hashing 51 => 156
Hashing 52 => 157
Hashing 53 => 158
Hashing 54 => 150
Hashing 55 => 151
Hashing 56 => 152
Hashing 57 => 153
Hashing 58 => 154
Hashing 59 => 155
Hashing 60 => 156
Hashing 61 => 157
Hashing 62 => 158
Hashing 63 => 150
Hashing 64 => 151
Hashing 65 => 152
Hashing 66 => 153
Hashing 67 => 154
Hashing 68 => 155
Hashing 69 => 156
Hashing 70 => 157
Hashing 71 => 158
Hashing 72 => 150
Hashing 73 => 151
Hashing 74 => 152
Hashing 75 => 153
Hashing 76 => 154
Hashing 77 => 155
Hashing 78 => 156
Hashing 79 => 157
Hashing 80 => 158
Hashing 81 => 150
Hashing 82 => 151
Hashing 83 => 152
Hashing 84 => 153
Hashing 85 => 154
Hashing 86 => 155
Hashing 87 => 156
Hashing 88 => 157
Hashing 89 => 158
Hashing 90 => 150
Hashing 91 => 151
Hashing 92 => 152
Hashing 93 => 153
Hashing 94 => 154
Hashing 95 => 155
Hashing 96 => 156
Hashing 97 => 157
Hashing 98 => 158
Hashing 99 => 150
Hashing 100 => 154
Hashing 101 => 155
Hashing 102 => 156
Hashing 103 => 157
Hashing 104 => 158
Hashing 105 => 150
Hashing 106 => 151
Hashing 107 => 152
Hashing 108 => 153
Hashing 109 => 154
Hashing 110 => 155
Hashing 111 => 156
Hashing 112 => 157
Hashing 113 => 158
Hashing 114 => 150
Hashing 115 => 151
Hashing 116 => 152
Hashing 117 => 153
Hashing 118 => 154
Hashing 119 => 155
Hashing 120 => 156
Hashing 121 => 157
Hashing 122 => 158
Hashing 123 => 150
Hashing 124 => 151
Hashing 125 => 152
Hashing 126 => 153
Hashing 127 => 154
Hashing 128 => 155
Hashing 129 => 156
Hashing 130 => 157
Hashing 131 => 158
Hashing 132 => 150
Hashing 133 => 151
Hashing 134 => 152
Hashing 135 => 153
Hashing 136 => 154
Hashing 137 => 155
Hashing 138 => 156
Hashing 139 => 157
Hashing 140 => 158
Hashing 141 => 150
Hashing 142 => 151
Hashing 143 => 152
Hashing 144 => 153
Hashing 145 => 154
Hashing 146 => 155
Hashing 147 => 156
Hashing 148 => 157
Hashing 149 => 158
Hashing 150 => 150
Hashing 151 => 151
Hashing 152 => 152
Hashing 153 => 153
Hashing 154 => 154
Hashing 155 => 155
Hashing 156 => 156
Hashing 157 => 157
Hashing 158 => 158
Hashing 159 => 159
Hashing 160 => 151
Hashing 161 => 152
Hashing 162 => 153
Hashing 163 => 154
Hashing 164 => 155
Hashing 165 => 156
Hashing 166 => 157
Hashing 167 => 158
Hashing 168 => 159
Hashing 169 => 151
Hashing 170 => 152
Hashing 171 => 153
Hashing 172 => 154
Hashing 173 => 155
Hashing 174 => 156
Hashing 175 => 157
Hashing 176 => 158
Hashing 177 => 159
Hashing 178 => 151
Hashing 179 => 152
Hashing 180 => 153
Hashing 181 => 154
Hashing 182 => 155
Hashing 183 => 156
Hashing 184 => 157
Hashing 185 => 158
Hashing 186 => 159
Hashing 187 => 151
Hashing 188 => 152
Hashing 189 => 153
Hashing 190 => 154
Hashing 191 => 155
Hashing 192 => 156
Hashing 193 => 157
Hashing 194 => 158
Hashing 195 => 159
Hashing 196 => 151
Hashing 197 => 152
Hashing 198 => 153
Hashing 199 => 154
Hashing 200 => 155
Hashing 201 => 156
Hashing 202 => 157
Hashing 203 => 158
Hashing 204 => 150
Hashing 205 => 151
Hashing 206 => 152
Hashing 207 => 153
Hashing 208 => 154
Hashing 209 => 155
Hashing 210 => 156
Hashing 211 => 157
Hashing 212 => 158
Hashing 213 => 150
Hashing 214 => 151
Hashing 215 => 152
Hashing 216 => 153
Hashing 217 => 154
Hashing 218 => 155
Hashing 219 => 156
Hashing 220 => 157
Hashing 221 => 158
Hashing 222 => 150
Hashing 223 => 151
Hashing 224 => 152
Hashing 225 => 153
Hashing 226 => 154
Hashing 227 => 155
Hashing 228 => 156
Hashing 229 => 157
Hashing 230 => 158
Hashing 231 => 150
Hashing 232 => 151
Hashing 233 => 152
Hashing 234 => 153
Hashing 235 => 154
Hashing 236 => 155
Hashing 237 => 156
Hashing 238 => 157
Hashing 239 => 158
Hashing 240 => 150
Hashing 241 => 151
Hashing 242 => 152
Hashing 243 => 153
Hashing 244 => 154
Hashing 245 => 155
Hashing 246 => 156
Hashing 247 => 157
Hashing 248 => 158
Hashing 249 => 159
Hashing 250 => 151
Hashing 251 => 152
Hashing 252 => 153
Hashing 253 => 154
Hashing 254 => 155
Hashing 255 => 156

Hemos reducido a nosotros mismos hasta 8 valores ... Eso es malo ... Nuestra función original asignado S(∞)a S(256). Es decir, hemos creado una función sobreyectiva mapeo $inputa $output.

Dado que tenemos una función Surjective, no tenemos garantía de que el mapeo para cualquier subconjunto de la entrada no tenga colisiones (de hecho, en la práctica sí lo tendrán).

Eso es lo que pasó aquí! Nuestra función era mala, pero no es por eso que funcionó (es por eso que funcionó tan rápido y tan completamente).

Lo mismo sucede con MD5. Se asigna S(∞)a S(2^128). Dado que no hay garantía de que la ejecución MD5(S(output))sea inyectiva , lo que significa que no tendrá colisiones.

Sección TL / DR

Por lo tanto, dado que alimentar la salida md5directamente puede generar colisiones, cada iteración aumentará la posibilidad de colisiones. Sin embargo, este es un aumento lineal, lo que significa que si bien el conjunto de resultados 2^128se reduce, no se reduce significativamente lo suficientemente rápido como para ser un defecto crítico.

Entonces,

$output = md5($input); // 2^128 possibilities
$output = md5($output); // < 2^128 possibilities
$output = md5($output); // < 2^128 possibilities
$output = md5($output); // < 2^128 possibilities
$output = md5($output); // < 2^128 possibilities

Cuantas más veces repita, más se reduce la reducción.

La solución

Afortunadamente para nosotros, hay una forma trivial de solucionar esto: retroalimentar algo en las iteraciones posteriores:

$output = md5($input); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities    

Tenga en cuenta que las iteraciones posteriores no son 2 ^ 128 para cada valor individual para $input. Lo que significa que es posible que podamos generar $inputvalores que aún colisionen en la línea (y, por lo tanto, se estabilizarán o resonarán en resultados mucho menos que 2^128posibles). Pero el argumento general para $inputtodavía es tan fuerte como lo fue para una sola ronda.

Espera, ¿lo fue? Probemos esto con nuestra ourHash()función. Cambiando a $hash = ourHash($input . $hash);, por 100 iteraciones:

Hashing 0 => 201
Hashing 1 => 212
Hashing 2 => 199
Hashing 3 => 201
Hashing 4 => 203
Hashing 5 => 205
Hashing 6 => 207
Hashing 7 => 209
Hashing 8 => 211
Hashing 9 => 204
Hashing 10 => 251
Hashing 11 => 147
Hashing 12 => 251
Hashing 13 => 148
Hashing 14 => 253
Hashing 15 => 0
Hashing 16 => 1
Hashing 17 => 2
Hashing 18 => 161
Hashing 19 => 163
Hashing 20 => 147
Hashing 21 => 251
Hashing 22 => 148
Hashing 23 => 253
Hashing 24 => 0
Hashing 25 => 1
Hashing 26 => 2
Hashing 27 => 161
Hashing 28 => 163
Hashing 29 => 8
Hashing 30 => 251
Hashing 31 => 148
Hashing 32 => 253
Hashing 33 => 0
Hashing 34 => 1
Hashing 35 => 2
Hashing 36 => 161
Hashing 37 => 163
Hashing 38 => 8
Hashing 39 => 4
Hashing 40 => 148
Hashing 41 => 253
Hashing 42 => 0
Hashing 43 => 1
Hashing 44 => 2
Hashing 45 => 161
Hashing 46 => 163
Hashing 47 => 8
Hashing 48 => 4
Hashing 49 => 9
Hashing 50 => 253
Hashing 51 => 0
Hashing 52 => 1
Hashing 53 => 2
Hashing 54 => 161
Hashing 55 => 163
Hashing 56 => 8
Hashing 57 => 4
Hashing 58 => 9
Hashing 59 => 11
Hashing 60 => 0
Hashing 61 => 1
Hashing 62 => 2
Hashing 63 => 161
Hashing 64 => 163
Hashing 65 => 8
Hashing 66 => 4
Hashing 67 => 9
Hashing 68 => 11
Hashing 69 => 4
Hashing 70 => 1
Hashing 71 => 2
Hashing 72 => 161
Hashing 73 => 163
Hashing 74 => 8
Hashing 75 => 4
Hashing 76 => 9
Hashing 77 => 11
Hashing 78 => 4
Hashing 79 => 3
Hashing 80 => 2
Hashing 81 => 161
Hashing 82 => 163
Hashing 83 => 8
Hashing 84 => 4
Hashing 85 => 9
Hashing 86 => 11
Hashing 87 => 4
Hashing 88 => 3
Hashing 89 => 17
Hashing 90 => 161
Hashing 91 => 163
Hashing 92 => 8
Hashing 93 => 4
Hashing 94 => 9
Hashing 95 => 11
Hashing 96 => 4
Hashing 97 => 3
Hashing 98 => 17
Hashing 99 => 13
Hashing 100 => 246
Hashing 101 => 248
Hashing 102 => 49
Hashing 103 => 44
Hashing 104 => 255
Hashing 105 => 198
Hashing 106 => 43
Hashing 107 => 51
Hashing 108 => 202
Hashing 109 => 2
Hashing 110 => 248
Hashing 111 => 49
Hashing 112 => 44
Hashing 113 => 255
Hashing 114 => 198
Hashing 115 => 43
Hashing 116 => 51
Hashing 117 => 202
Hashing 118 => 2
Hashing 119 => 51
Hashing 120 => 49
Hashing 121 => 44
Hashing 122 => 255
Hashing 123 => 198
Hashing 124 => 43
Hashing 125 => 51
Hashing 126 => 202
Hashing 127 => 2
Hashing 128 => 51
Hashing 129 => 53
Hashing 130 => 44
Hashing 131 => 255
Hashing 132 => 198
Hashing 133 => 43
Hashing 134 => 51
Hashing 135 => 202
Hashing 136 => 2
Hashing 137 => 51
Hashing 138 => 53
Hashing 139 => 55
Hashing 140 => 255
Hashing 141 => 198
Hashing 142 => 43
Hashing 143 => 51
Hashing 144 => 202
Hashing 145 => 2
Hashing 146 => 51
Hashing 147 => 53
Hashing 148 => 55
Hashing 149 => 58
Hashing 150 => 198
Hashing 151 => 43
Hashing 152 => 51
Hashing 153 => 202
Hashing 154 => 2
Hashing 155 => 51
Hashing 156 => 53
Hashing 157 => 55
Hashing 158 => 58
Hashing 159 => 0
Hashing 160 => 43
Hashing 161 => 51
Hashing 162 => 202
Hashing 163 => 2
Hashing 164 => 51
Hashing 165 => 53
Hashing 166 => 55
Hashing 167 => 58
Hashing 168 => 0
Hashing 169 => 209
Hashing 170 => 51
Hashing 171 => 202
Hashing 172 => 2
Hashing 173 => 51
Hashing 174 => 53
Hashing 175 => 55
Hashing 176 => 58
Hashing 177 => 0
Hashing 178 => 209
Hashing 179 => 216
Hashing 180 => 202
Hashing 181 => 2
Hashing 182 => 51
Hashing 183 => 53
Hashing 184 => 55
Hashing 185 => 58
Hashing 186 => 0
Hashing 187 => 209
Hashing 188 => 216
Hashing 189 => 219
Hashing 190 => 2
Hashing 191 => 51
Hashing 192 => 53
Hashing 193 => 55
Hashing 194 => 58
Hashing 195 => 0
Hashing 196 => 209
Hashing 197 => 216
Hashing 198 => 219
Hashing 199 => 220
Hashing 200 => 248
Hashing 201 => 49
Hashing 202 => 44
Hashing 203 => 255
Hashing 204 => 198
Hashing 205 => 43
Hashing 206 => 51
Hashing 207 => 202
Hashing 208 => 2
Hashing 209 => 51
Hashing 210 => 49
Hashing 211 => 44
Hashing 212 => 255
Hashing 213 => 198
Hashing 214 => 43
Hashing 215 => 51
Hashing 216 => 202
Hashing 217 => 2
Hashing 218 => 51
Hashing 219 => 53
Hashing 220 => 44
Hashing 221 => 255
Hashing 222 => 198
Hashing 223 => 43
Hashing 224 => 51
Hashing 225 => 202
Hashing 226 => 2
Hashing 227 => 51
Hashing 228 => 53
Hashing 229 => 55
Hashing 230 => 255
Hashing 231 => 198
Hashing 232 => 43
Hashing 233 => 51
Hashing 234 => 202
Hashing 235 => 2
Hashing 236 => 51
Hashing 237 => 53
Hashing 238 => 55
Hashing 239 => 58
Hashing 240 => 198
Hashing 241 => 43
Hashing 242 => 51
Hashing 243 => 202
Hashing 244 => 2
Hashing 245 => 51
Hashing 246 => 53
Hashing 247 => 55
Hashing 248 => 58
Hashing 249 => 0
Hashing 250 => 43
Hashing 251 => 51
Hashing 252 => 202
Hashing 253 => 2
Hashing 254 => 51
Hashing 255 => 53

Todavía hay un patrón aproximado allí, pero tenga en cuenta que no es más un patrón que nuestra función subyacente (que ya era bastante débil).

Tenga en cuenta sin embargo, que 0y 3se convirtió en colisiones, a pesar de que no estaban en la única prueba. Esa es una aplicación de lo que dije antes (que la resistencia de colisión se mantiene igual para el conjunto de todas las entradas, pero las rutas de colisión específicas pueden abrirse debido a fallas en el algoritmo subyacente).

Sección TL / DR

Al retroalimentar la entrada en cada iteración, efectivamente rompemos cualquier colisión que pueda haber ocurrido en la iteración anterior.

Por lo tanto, md5($input . md5($input));debe ser (en teoría al menos) tan fuerte como md5($input).

¿Es esto importante?

Si. Esta es una de las razones por las que PBKDF2 reemplazó a PBKDF1 en RFC 2898 . Considere los lazos internos de los dos ::

PBKDF1:

T_1 = Hash (P || S) ,
T_2 = Hash (T_1) ,
...
T_c = Hash (T_{c-1}) 

¿Dónde cestá el recuento de iteraciones, Pes la contraseña y Ses la sal

PBKDF2:

U_1 = PRF (P, S || INT (i)) ,
U_2 = PRF (P, U_1) ,
...
U_c = PRF (P, U_{c-1})

Donde PRF es realmente solo un HMAC. Pero para nuestros propósitos aquí, digamos que PRF(P, S) = Hash(P || S)(es decir, el PRF de 2 entradas es el mismo, en términos generales, como hash con los dos concatenados juntos). Es en gran medida no , pero para nuestros propósitos es.

Por lo tanto, PBKDF2 mantiene la resistencia a la colisión de la Hashfunción subyacente , donde PBKDF1 no.

Atar todo junto:

Conocemos formas seguras de iterar un hash. De hecho:

$hash = $input;
$i = 10000;
do {
   $hash = hash($input . $hash);
} while ($i-- > 0);

Es típicamente seguro.

Ahora, para ver por qué querríamos hacer hash, analicemos el movimiento de entropía.

Un hash toma el conjunto infinito: S(∞)y produce un conjunto más pequeño y de tamaño consistente S(n). La siguiente iteración (suponiendo que la entrada se pasa de nuevo) mapea S(∞)en S(n)otra vez:

S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)

Observe que la salida final tiene exactamente la misma cantidad de entropía que la primera . Iterar no "lo hará más oscuro". La entropía es idéntica. No hay una fuente mágica de imprevisibilidad (es una función pseudoaleatoria, no una función aleatoria).

Sin embargo, hay una ganancia al iterar. Hace que el proceso de hash sea artificialmente más lento. Y es por eso que iterar puede ser una buena idea. De hecho, es el principio básico de la mayoría de los algoritmos modernos de hashing de contraseñas (el hecho de que hacer algo una y otra vez lo hace más lento).

Lento es bueno, porque combate la principal amenaza de seguridad: la fuerza bruta. Cuanto más lento hagamos nuestro algoritmo de hash, más agresivos tendrán que trabajar para atacar los hash de contraseñas que nos hayan robado. ¡Y eso es bueno!

ircmaxell
fuente
1
$output = md5($output); // < 2^128 possibilities--- ¿Es realmente estricto <o <=?
zerkms 01 de
2
@zerkms: No es estrictamente nada. Necesitaríamos conocer algunos detalles muy específicos de la función subyacente ( md5()en este caso) para saber con certeza. Pero, en general, será <y no <=... Recuerde, nosotros estamos hablando del tamaño del conjunto de los $outputde toda posible $inputs. Así que si tenemos incluso una colisión que será <, por lo tanto, <es el mejor generalizador.
ircmaxell 01 de
2
@ TomášFejfar Creo que la pregunta no se trata de colisiones en general, sino de colisiones en el conjunto de salida estricto (2 ^ 128 salidas, cada una exactamente de 128 bits de ancho). Eso podría ser inyectivo, pero hasta donde yo sé, una prueba genérica no es posible (solo una prueba por ejemplo de una colisión para un algoritmo específico). Considere la función hash que simplemente devuelve la entrada si es de 128 bits (y hashes de lo contrario) En general, sería surjective, pero cuando se alimenta su salida siempre sería inyectiva ... Ese es el punto de discusión ...
ircmaxell
3
Continuemos esta discusión en el chat .
ircmaxell
66
Para aquellos que quisieran ahorrar tiempo al no tener que ir a comprobar cómo terminó esa discusión entre Dan e ircmaxell, terminó bien : Dan está de acuerdo con ircmaxell.
jeromej
51

Sí, el nuevo hash reduce el espacio de búsqueda, pero no, no importa: la reducción efectiva es insignificante.

Volver a aplicar hash aumenta el tiempo que lleva la fuerza bruta, pero hacerlo solo dos veces también es subóptimo.

Lo que realmente desea es cifrar la contraseña con PBKDF2 , un método comprobado para usar un hash seguro con sal e iteraciones. Mira esta respuesta SO .

EDITAR : casi se me olvida - ¡NO UTILICE MD5! Utilice un hash criptográfico moderno como la familia SHA-2 (SHA-256, SHA-384 y SHA-512).

orip
fuente
2
@DFTR - de acuerdo. bcrypt o scrypt son mejores opciones.
orip
Tampoco los use (familia SHA-2), ahora también se pueden descifrar fácilmente, consulte crackstation.net para obtener pruebas. Si algo usa scrypt o PBKDF2, que son funciones hash criptográficas basadas en funciones de derivación de claves (KDF).
Theodore
3
En 2016, Argon2 y scrypt son los que todos deberían esforzarse por usar
silkfire el
10

Sí, reduce el número de posibles cadenas que coinciden con la cadena.

Como ya ha mencionado, los hash salados son mucho mejores.

Un artículo aquí: http://websecurity.ro/blog/2007/11/02/md5md5-vs-md5/ , intenta una prueba de por qué es equivalente, pero no estoy seguro con la lógica. En parte, suponen que no hay software disponible para analizar md5 (md5 (texto)), pero obviamente es bastante trivial producir las tablas del arco iris.

Todavía me quedo con mi respuesta de que hay un número menor de hash de tipo md5 (md5 (texto)) que de hash md5 (texto), lo que aumenta la posibilidad de colisión (incluso si es una probabilidad poco probable) y reduce el espacio de búsqueda.

Rich Bradshaw
fuente
5

La mayoría de las respuestas son de personas sin experiencia en criptografía o seguridad. Y están equivocados. Use una sal, si es posible, única por registro. MD5 / SHA / etc son demasiado rápidos, lo contrario de lo que quieres. PBKDF2 y bcrypt son más lentos (lo cual es bueno) pero pueden ser derrotados con ASIC / FPGA / GPU (muy asequible hoy en día). Por lo tanto, se necesita un algoritmo de memoria dura: ingrese scrypt .

Aquí hay una explicación simple sobre las sales y la velocidad (pero no sobre los algoritmos de memoria dura).

alecco
fuente
4

Solo miro esto desde un punto de vista práctico. ¿Qué busca el hacker? Por qué, la combinación de caracteres que, al pasar por la función hash, genera el hash deseado.

Solo está guardando el último hash, por lo tanto, el hacker solo tiene que aplicar fuerza bruta a un hash. Suponiendo que tiene aproximadamente las mismas probabilidades de tropezar con el hash deseado con cada paso de fuerza bruta, el número de hashes es irrelevante. Podrías hacer un millón de iteraciones de hash, y no aumentaría ni reduciría la seguridad un bit, ya que al final de la línea todavía hay un solo hash que romper, y las probabilidades de romperlo son las mismas que cualquier hash.

Tal vez los carteles anteriores piensan que la entrada es relevante; no es. Siempre que lo que pones en la función hash genere el hash deseado, te ayudará a ingresar, ingresar correctamente o ingresar incorrectamente.

Ahora, las mesas arcoiris son otra historia. Dado que una tabla de arco iris solo contiene contraseñas sin procesar, el hashing dos veces puede ser una buena medida de seguridad, ya que una tabla de arco iris que contiene cada hash de cada hash sería demasiado grande.

Por supuesto, solo estoy considerando el ejemplo que dio el OP, donde solo se trata de una contraseña de texto sin cifrar. Si incluye el nombre de usuario o una sal en el hash, es una historia diferente; el hashing dos veces es completamente innecesario, ya que la tabla del arco iris ya sería demasiado grande para ser práctica y contener el hash correcto.

De todos modos, no soy un experto en seguridad aquí, pero eso es lo que deduje de mi experiencia.

Palmadita
fuente
Esta respuesta es incorrecta en todos los aspectos. 1. Conocer el penúltimo hash no proporciona ningún valor a un atacante, porque la entrada a un hash iterado es la contraseña , que luego se codifica muchas veces (no una vez). 2. El espacio de entrada son las contraseñas, el espacio de salida son las contraseñas hash. El espacio de las contraseñas típicas es mucho más pequeño que el espacio de salida. 3. Las tablas de arco iris para contraseñas de doble hash sin sal no son más grandes que las tablas de arco iris para contraseñas de un solo hash sin sal. 4. Los nombres de usuario son de baja entropía, una buena sal es aleatoria. 5. La salazón no reemplaza la iteración. Necesitas ambos.
Clement Cherlin
3

Por lo que he leído, en realidad se recomienda volver a hacer hash la contraseña cientos o miles de veces.

La idea es que si puede hacer que le tome más tiempo codificar la contraseña, es más trabajo para un atacante ejecutar muchas conjeturas para descifrar la contraseña. Esa parece ser la ventaja de volver a hacer hash, no es que sea más criptográficamente seguro, sino que simplemente lleva más tiempo generar un ataque de diccionario.

Por supuesto, las computadoras se vuelven más rápidas todo el tiempo, por lo que esta ventaja disminuye con el tiempo (o requiere que aumente las iteraciones).

Bill Karwin
fuente
También mencioné esto en otro comentario, pero en.wikipedia.org/wiki/Key_stretching
2

Personalmente, no me molestaría con varios hashses, pero me aseguraría de también hash el nombre de usuario (u otro campo de ID de usuario), así como la contraseña para que dos usuarios con la misma contraseña no terminen con el mismo hash. También probablemente arrojaría alguna otra cadena constante en la cadena de entrada también para una buena medida.

$hashed_password = md5( "xxx" + "|" + user_name + "|" + plaintext_password);
CodeAndCats
fuente
13
En realidad, debería ser una cadena generada aleatoriamente para cada usuario, no una constante.
Bill the Lizard
77
Un secreto constante funciona (y es más fácil trabajar con él), si agrega el nombre de usuario como se sugiere. Eso esencialmente produce una clave aleatoria específica del usuario.
SquareCog
44
Una sal secreta constante es la seguridad a través de la oscuridad. Si se descubre el "secreto" de que estás usando "xxx" + nombre de usuario + contraseña, entonces un atacante ni siquiera necesita datos de tus tablas para lanzar un ataque contra él.
Bill the Lizard
8
No creo que sea seguridad a través de la oscuridad. La razón para usar una sal es que no se puede calcular una tabla de arco iris contra múltiples hash md5 simultáneamente. La creación de uno para "xxx" + contraseña (misma sal) ocurre una vez. Crear una tabla para "xxx" + nombre de usuario + contraseña es peor que el forzado bruto.
FryGuy
55
@Bill the Lizard: "el ataque se reduce a construir un diccionario para atacar a un nombre de usuario específico" es solo un ataque de fuerza bruta (en realidad aún peor, porque además de calcular todos los hashes tienes que almacenarlos), así que la sal funciona perfectamente en este caso
Kornel el
2

Supongamos que usa el algoritmo de hash: compute rot13, tome los primeros 10 caracteres. Si lo hace dos veces (o incluso 2000 veces), es posible hacer una función que sea más rápida, pero que dé el mismo resultado (es decir, solo tome los primeros 10 caracteres).

Del mismo modo, es posible hacer una función más rápida que proporcione la misma salida que una función de hashing repetida. Por lo tanto, su elección de la función de hashing es muy importante: como con el ejemplo de rot13, no se da que el hashing repetido mejorará la seguridad. Si no hay investigaciones que digan que el algoritmo está diseñado para uso recursivo, entonces es más seguro asumir que no le brindará protección adicional.

Dicho esto: para todas las funciones de hash, excepto las más simples, lo más probable es que los expertos en criptografía calculen las funciones más rápidas, por lo que si se protege contra los atacantes que no tienen acceso a expertos en criptografía, probablemente sea más seguro en la práctica usar una función de hashing repetida .

Ole Tange
fuente
1

En general, no proporciona seguridad adicional para doble hash o doble cifrado de algo. Si puede romper el hash una vez, puede hacerlo nuevamente. Sin embargo, generalmente no hace daño a la seguridad hacer esto.

En su ejemplo de uso de MD5, como probablemente sepa, hay algunos problemas de colisión. "Double Hashing" en realidad no ayuda a proteger contra esto, ya que las mismas colisiones aún resultarán en el mismo primer hash, que luego puedes usar MD5 nuevamente para obtener el segundo hash.

Esto protege contra ataques de diccionario, como las "bases de datos MD5 inversas", pero también lo hace la salazón.

En una tangente, el cifrado doble de algo no proporciona ninguna seguridad adicional porque todo lo que hace es dar como resultado una clave diferente, que es una combinación de las dos claves realmente utilizadas. Por lo tanto, el esfuerzo por encontrar la "clave" no se duplica porque en realidad no es necesario encontrar dos claves. Esto no es cierto para el hash, porque el resultado del hash generalmente no tiene la misma longitud que la entrada original.

Plataforma improvisada
fuente
1
Todo correcto, pero solo quiero señalar que el efecto del fuerte compromiso de resistencia a la colisión en MD5 está un poco desproporcionado: la mayoría de los escenarios que usan funciones de cifrado hash no se basan en una fuerte resistencia a la colisión, solo una resistencia débil. No están afectados por esta vulnerabilidad.
SquareCog
1

El doble hashing tiene sentido para mí solo si escribo la contraseña en el cliente y luego guardo el hash (con diferente sal) de ese hash en el servidor.

De esa manera, incluso si alguien hackeó el servidor (ignorando la seguridad que proporciona SSL), aún no puede acceder a las contraseñas claras.

Sí, tendrá los datos necesarios para ingresar al sistema, pero no podrá usar esos datos para comprometer las cuentas externas que tiene el usuario. Y se sabe que las personas usan la misma contraseña para prácticamente cualquier cosa.

La única forma en que podría obtener las contraseñas claras es instalar un keygen en el cliente, y ese ya no es su problema.

En resumen:

  1. El primer hashing en el cliente protege a sus usuarios en un escenario de 'violación de servidor'.
  2. El segundo hash en el servidor sirve para proteger su sistema si alguien tiene una copia de seguridad de su base de datos, por lo que no puede usar esas contraseñas para conectarse a sus servicios.
Vedran
fuente
1
+1 Estaba esperando ver una respuesta como esta, porque pensé en el mismo escenario en el que no desea almacenar la contraseña de texto sin formato en el cliente, pero tampoco enviar la contraseña encriptada final por cable para hacer un simple comparación con el DB.
Mark
1
No ayuda para aplicaciones web. Si su servidor está comprometido, el código que su servidor está enviando al cliente también está comprometido. El atacante deshabilitará el hash del lado del cliente y capturará las contraseñas sin procesar.
Clement Cherlin
0

La preocupación por reducir el espacio de búsqueda es matemáticamente correcta, aunque el espacio de búsqueda sigue siendo lo suficientemente grande como para todos los fines prácticos (suponiendo que use sales), en 2 ^ 128. Sin embargo, dado que estamos hablando de contraseñas, el número de posibles cadenas de 16 caracteres (alfanuméricos, mayúsculas, algunos símbolos incluidos) es aproximadamente 2 ^ 98, según mis cálculos al final del sobre. Por lo tanto, la disminución percibida en el espacio de búsqueda no es realmente relevante.

Aparte de eso, realmente no hay diferencia, criptográficamente hablando.

Aunque hay una cripto primitiva llamada "cadena de hash", una técnica que le permite hacer algunos trucos geniales, como revelar una clave de firma después de que se haya usado, sin sacrificar la integridad del sistema, dada la sincronización de tiempo mínima, esto le permite evitar el problema de la distribución inicial de claves. Básicamente, precalcula un gran conjunto de hashes de hashes: h (h (h (h .... (h (k)) ...))), usa el enésimo valor para firmar, después de un intervalo establecido, envías sacar la clave y firmar con la clave (n-1). Los destinatarios ahora pueden verificar que enviaste todos los mensajes anteriores, y nadie puede falsificar tu firma desde que pasó el período de tiempo para el que es válida.

Repasar cientos de miles de veces, como sugiere Bill, es solo un desperdicio de su CPU ... use una clave más larga si le preocupa que las personas rompan 128 bits.

SquareCog
fuente
1
Volver a hacer hash se trata precisamente de ralentizar el hash. Esta es una característica de seguridad clave en la criptografía basada en contraseña. Vea los enlaces para PCKS5 y PBKDF2.
orip
0

Como sugieren varias respuestas en este artículo, hay algunos casos en los que puede mejorar la seguridad y otros en los que definitivamente lo perjudica. Hay una mejor solución que definitivamente mejorará la seguridad. En lugar de duplicar la cantidad de veces que calcula el hash, duplique el tamaño de su sal, o duplique el número de bits utilizados en el hash, ¡o haga ambas cosas! En lugar de SHA-245, salta a SHA-512.

Stefan Rusek
fuente
Esto no responde la pregunta.
Bill the Lizard
1
El doble hashing no vale la pena, pero duplicar el tamaño de tu hash sí. Creo que este es un punto más valioso.
Stefan Rusek
-1

El doble hashing es feo porque es más que probable que un atacante haya creado una tabla para crear la mayoría de los hashes. Es mejor salar los hashes y mezclarlos. También hay nuevos esquemas para "firmar" hashes (básicamente salazón), pero de una manera más segura.

Sargun Dhillon
fuente
-1

Si.

Absolutamente no use múltiples iteraciones de una función hash convencional, como md5(md5(md5(password))). En el mejor de los casos , obtendrá un aumento marginal en la seguridad (un esquema como este no ofrece casi ninguna protección contra un ataque de GPU; solo canalícelo). En el peor de los casos, está reduciendo su espacio de hash (y, por lo tanto, la seguridad) con cada iteración que agregue . En seguridad, es aconsejable asumir lo peor.

No use una contraseña ha sido que la diseñada por un criptógrafo competente para ser un hash de contraseña eficaz y resistente tanto a la fuerza bruta y ataques de tiempo-espacio. Estos incluyen bcrypt, scrypt y, en algunas situaciones, PBKDF2. El hash basado en glibc SHA-256 también es aceptable.

hobbs
fuente
-1

Voy a arriesgarme y decir que es más seguro en ciertas circunstancias ... ¡sin embargo, no me denigren!

Desde un punto de vista matemático / criptográfico, es menos seguro, por razones que estoy seguro de que alguien más le dará una explicación más clara de lo que yo podría.

Sin embargo , existen grandes bases de datos de hashes MD5, que tienen más probabilidades de contener el texto de "contraseña" que el MD5. Entonces, al hacer doble hashing estás reduciendo la efectividad de esas bases de datos.

Por supuesto, si usa una sal, esta ventaja (¿desventaja?) Desaparece.

Greg
fuente