Bueno, puedes buscarlo en Wikipedia ... Pero como quieres una explicación, haré lo mejor que pueda aquí:
Funciones hash
Proporcionan un mapeo entre una entrada de longitud arbitraria y una salida (generalmente) de longitud fija (o longitud más pequeña). Puede ser desde un simple crc32, hasta una función hash criptográfica completa como MD5 o SHA1 / 2/256/512. El punto es que hay un mapeo unidireccional en curso. Siempre es un mapeo de muchos: 1 (lo que significa que siempre habrá colisiones) ya que cada función produce una salida más pequeña de la que es capaz de ingresar (si alimenta todos los archivos de 1mb posibles en MD5, obtendrá una tonelada de colisiones).
La razón por la que son difíciles (o imposibles en la práctica) es revertir debido a cómo funcionan internamente. La mayoría de las funciones hash criptográficas iteran sobre el conjunto de entrada muchas veces para producir la salida. Entonces, si observamos cada porción de entrada de longitud fija (que depende del algoritmo), la función hash llamará a eso el estado actual. Luego iterará sobre el estado y lo cambiará a uno nuevo y lo usará como retroalimentación en sí mismo (MD5 hace esto 64 veces por cada fragmento de datos de 512 bits). Luego, de alguna manera, combina los estados resultantes de todas estas iteraciones para formar el hash resultante.
Ahora, si quisiera decodificar el hash, primero tendría que descubrir cómo dividir el hash dado en sus estados iterados (1 posibilidad para entradas más pequeñas que el tamaño de un fragmento de datos, muchas para entradas más grandes). Entonces necesitarías invertir la iteración para cada estado. Ahora, para explicar por qué esto es muy difícil, imagínese tratando de deducir a
y b
de la siguiente fórmula: 10 = a + b
. Hay 10 combinaciones positivas de a
y b
que pueden funcionar. Ahora repite eso un montón de veces:tmp = a + b; a = b; b = tmp
. Para 64 iteraciones, tendría más de 10 ^ 64 posibilidades para probar. Y eso es solo una simple adición donde se conserva cierto estado de iteración a iteración. Las funciones hash reales realizan más de 1 operación (MD5 realiza aproximadamente 15 operaciones en 4 variables de estado). Y dado que la siguiente iteración depende del estado de la anterior y la anterior se destruye al crear el estado actual, es casi imposible determinar el estado de entrada que condujo a un estado de salida dado (para cada iteración no menos). Combine eso, con la gran cantidad de posibilidades involucradas, y decodificar incluso un MD5 tomará una cantidad de recursos casi infinita (pero no infinita). Tantos recursos que '
Funciones de cifrado
Proporcionan un mapeo 1: 1 entre una entrada y salida de longitud arbitraria. Y siempre son reversibles. Lo importante a tener en cuenta es que es reversible utilizando algún método. Y siempre es 1: 1 para una clave determinada. Ahora, hay múltiples entradas: pares de claves que pueden generar la misma salida (de hecho, generalmente las hay, dependiendo de la función de cifrado). Los buenos datos cifrados no se pueden distinguir del ruido aleatorio. Esto es diferente de una buena salida de hash que siempre tiene un formato consistente.
Casos de uso
Use una función hash cuando desee comparar un valor pero no pueda almacenar la representación simple (por cualquier número de razones). Las contraseñas deben ajustarse muy bien a este caso de uso, ya que no desea almacenarlas en texto sin formato por razones de seguridad (y no debería). Pero, ¿qué pasaría si quisieras verificar un sistema de archivos para archivos de música pirateados? No sería práctico almacenar 3 mb por archivo de música. Entonces, en su lugar, tome el hash del archivo y guárdelo (md5 almacenaría 16 bytes en lugar de 3mb). De esa manera, simplemente hash cada archivo y se compara con la base de datos almacenada de hashes (esto no funciona tan bien en la práctica debido a la nueva codificación, el cambio de encabezados de archivos, etc., pero es un caso de uso de ejemplo).
Use una función hash cuando verifique la validez de los datos de entrada. Para eso están diseñados. Si tiene 2 entradas, y desea verificar si son iguales, ejecute ambas a través de una función hash. La probabilidad de una colisión es astronómicamente baja para tamaños de entrada pequeños (suponiendo una buena función hash). Por eso se recomienda para contraseñas. Para contraseñas de hasta 32 caracteres, md5 tiene 4 veces el espacio de salida. SHA1 tiene 6 veces el espacio de salida (aproximadamente). SHA512 tiene aproximadamente 16 veces el espacio de salida. En realidad, no importa lo que la contraseña era , te importa si es la misma que la que se almacenó. Es por eso que debes usar hashes para contraseñas.
Utilice el cifrado siempre que necesite recuperar los datos de entrada. Note la palabra necesidad . Si está almacenando números de tarjetas de crédito, debe recuperarlos en algún momento, pero no desea almacenarlos en texto sin formato. Por lo tanto, almacene la versión encriptada y mantenga la clave lo más segura posible.
Las funciones de hash también son excelentes para firmar datos. Por ejemplo, si está utilizando HMAC, firma un dato tomando un hash de los datos concatenados con un valor conocido pero no transmitido (un valor secreto). Por lo tanto, envía el texto sin formato y el hash HMAC. Luego, el receptor simplemente mezcla los datos enviados con el valor conocido y verifica si coincide con el HMAC transmitido. Si es lo mismo, sabes que no fue manipulado por una parte sin el valor secreto. Esto se usa comúnmente en sistemas de cookies seguros por marcos HTTP, así como en la transmisión de mensajes de datos a través de HTTP, donde se desea garantizar la integridad de los datos.
Una nota sobre hashes para contraseñas:
Una característica clave de las funciones criptográficas de hash es que deben ser muy rápidas de crear y muy difíciles / lentas de revertir (tanto que es prácticamente imposible). Esto plantea un problema con las contraseñas. Si almacena sha512(password)
, no está haciendo nada para protegerse contra las mesas de arco iris o los ataques de fuerza bruta. Recuerde, la función hash fue diseñada para la velocidad. Por lo tanto, es trivial para un atacante simplemente ejecutar un diccionario a través de la función hash y probar cada resultado.
Agregar una sal ayuda a las cosas, ya que agrega un poco de datos desconocidos al hash. Entonces, en lugar de encontrar algo que coincida md5(foo)
, necesitan encontrar algo que cuando se agrega a la sal conocida produce md5(foo.salt)
(que es mucho más difícil de hacer). Pero todavía no resuelve el problema de la velocidad, ya que si conocen la sal es solo cuestión de ejecutar el diccionario.
Entonces, hay formas de lidiar con esto. Un método popular se llama fortalecimiento clave (o estiramiento clave). Básicamente, iteras sobre un hash muchas veces (usualmente miles). Esto hace dos cosas. Primero, ralentiza significativamente el tiempo de ejecución del algoritmo de hashing. En segundo lugar, si se implementa correctamente (pasando de nuevo la entrada y la sal en cada iteración) en realidad aumenta la entropía (espacio disponible) para la salida, reduciendo las posibilidades de colisiones. Una implementación trivial es:
var hash = password + salt;
for (var i = 0; i < 5000; i++) {
hash = sha512(hash + password + salt);
}
Hay otras implementaciones, más estándar, como PBKDF2 , Bcrypt . Pero esta técnica es utilizada por varios sistemas relacionados con la seguridad (como PGP, WPA, Apache y OpenSSL).
El resultado final hash(password)
no es lo suficientemente bueno. hash(password + salt)
es mejor, pero aún no es lo suficientemente bueno ... Use un mecanismo de hash extendido para producir los hashes de su contraseña ...
Otra nota sobre estiramiento trivial
Bajo ninguna circunstancia alimente la salida de un hash directamente a la función hash :
hash = sha512(password + salt);
for (i = 0; i < 1000; i++) {
hash = sha512(hash); // <-- Do NOT do this!
}
La razón de esto tiene que ver con las colisiones. Recuerde que todas las funciones hash tienen colisiones porque el espacio de salida posible (el número de salidas posibles) es menor que el espacio de entrada. Para ver por qué, veamos qué sucede. Para comenzar, supongamos que hay una probabilidad de colisión del 0.001% sha1()
(es mucho menor en realidad, pero con fines de demostración).
hash1 = sha1(password + salt);
Ahora, hash1
tiene una probabilidad de colisión de 0.001%. Pero cuando hacemos lo siguiente hash2 = sha1(hash1);
, todas las colisiones hash1
se convierten automáticamente en colisiones dehash2
. Entonces, ahora tenemos la tasa de hash1 en 0.001%, y la segunda sha1()
llamada se suma a eso. Entonces ahora, hash2
tiene una probabilidad de colisión de 0.002%. ¡Eso es el doble de posibilidades! Cada iteración agregará otra 0.001%
posibilidad de colisión al resultado. Entonces, con 1000 iteraciones, la posibilidad de colisión aumentó de un trivial 0.001% a 1%. Ahora, la degradación es lineal, y las probabilidades reales son mucho más pequeñas, pero el efecto es el mismo (una estimación de la posibilidad de una sola colisión md5
es aproximadamente 1 / (2 128 ) o 1 / (3x10 38) Si bien eso parece pequeño, gracias al ataque de cumpleaños no es realmente tan pequeño como parece).
En cambio, al volver a agregar la sal y la contraseña cada vez, está reintroduciendo los datos nuevamente en la función hash. Por lo tanto, cualquier colisión de una ronda en particular ya no es una colisión de la siguiente ronda. Entonces:
hash = sha512(password + salt);
for (i = 0; i < 1000; i++) {
hash = sha512(hash + password + salt);
}
Tiene la misma posibilidad de colisión que la sha512
función nativa . Que es lo que quieres. Use eso en su lugar.