Una función criptográfica hash es un algoritmo matemático que transforma cualquier información de entrada en una secuencia de caracteres de salida, con un tamaño fijo o variable, dependiendo del algoritmo hash que se esté utilizando. En los algoritmos de hash con un tamaño de salida fijo, la longitud será constante independientemente del tamaño de los datos de entrada. Por otro lado, los algoritmos hash diseñados específicamente para proteger contraseñas suelen tener una longitud variable. En este artículo vamos a explicar todo lo que necesitas saber sobre los hashes.
¿Cuál es el propósito de los valores hash?
Los hashes criptográficos se utilizan principalmente para proteger las contraseñas y evitar almacenarlas en texto plano en una base de datos. Si alguna vez has oído hablar de las funciones hash, seguramente haya sido en relación a su uso principal, que es la protección de contraseñas para evitar su exposición en caso de que alguien acceda a la base de datos de un servicio. Si las contraseñas no se encuentran hasheadas, las credenciales estarían inmediatamente expuestas al robo.
Para verificar si una contraseña introducida es correcta y coincide con la almacenada en la base de datos (que se guarda como el hash de la clave), se aplica el algoritmo hash a la contraseña introducida y se compara con el valor almacenado. Si coinciden, la clave es correcta; si no, la clave es incorrecta. Este procedimiento se utiliza en sistemas operativos, sitios web con autenticación de usuario/clave, entre otros.
Si alguna vez necesitas recuperar tu contraseña de un servicio en línea, deberás restablecerla, ya que el servicio no podrá proporcionarte la contraseña en texto plano, sino que solo almacenará el hash de la misma. Si un servicio te envía la contraseña en texto plano al solicitar su recuperación, eso indica que el servicio no la almacena de manera segura. Aunque los hashes para contraseñas sencillas como «123456» son conocidos, cuando se utiliza una contraseña robusta, no se encontrará en ningún sistema de crackeo en línea y será necesario utilizar herramientas como Hashcat para romperla.
No todos los usos de los algoritmos hash se limitan a las contraseñas. También se utilizan para detectar malware, identificar canciones o películas protegidas por derechos de autor y crear listas negras. Además, existen bases de datos públicas de malware que contienen firmas de malware, que consisten en valores hash de partes completas o fragmentos de malware. Los usuarios pueden consultar estas bases de datos para determinar si un archivo sospechoso es malicioso o no, y los antivirus también utilizan esta técnica para detectar y bloquear el malware comparando los hashes con sus propias bases de datos y las públicas mencionadas anteriormente.
Otro uso importante de las funciones criptográficas hash es asegurar la integridad de los mensajes. Se verifica el hash antes y después de la transmisión de datos para garantizar que sean idénticos, lo que indica que la comunicación ha sido segura y que los datos no han sido alterados. Si los hashes difieren, significa que hubo un fallo en el proceso y que los datos obtenidos al final de la comunicación no son los mismos que los emitidos al inicio.
Rendimiento de los algoritmos hash
Estos algoritmos son fundamentales en muchas aplicaciones y sistemas en la actualidad, pero también pueden tener un impacto significativo en los mismos. A menudo se utilizan para convertir datos de entrada en representaciones de tamaño fijo, lo que los hace muy útiles en una variedad de tareas, como la verificación de contraseñas o la detección de intrusos. El rendimiento de los algoritmos hash depende de diversos factores, como el tamaño de los datos de entrada, el algoritmo utilizado y la capacidad de procesamiento del hardware. Cuanto más complejos sean los algoritmos hash, mayor será su impacto en el rendimiento del sistema.
Una de las características más importantes es la velocidad a la que el sistema puede calcular el hash. Deben ser lo suficientemente rápidos como para no crear cuellos de botella tanto en las aplicaciones como en los sistemas utilizados. La mayoría de los algoritmos están diseñados específicamente para ser rápidos y eficientes, pero aún así pueden afectar el rendimiento en diferentes situaciones.
La cantidad de datos de entrada desempeña un papel crucial, ya que el tiempo necesario para realizar el cálculo del hash aumenta a medida que aumenta el tamaño del conjunto de datos a procesar. Esto puede ser problemático, especialmente en aplicaciones y sistemas que deben lidiar con grandes cantidades de datos en tiempo real, como la minería de criptomonedas, donde los hashes se calculan constantemente. Además, los algoritmos más complejos suelen requerir una mayor cantidad de memoria para poder ser procesados en caché, lo que puede aumentar significativamente el costo del hardware necesario para cumplir con los requisitos del sistema.
Características de un buen algoritmo de hash
Existen algunas características clave que todos los buenos hashes comparten, las cuales se resumen a continuación:
- Determinismo: un algoritmo de hash debe ser determinista, lo que significa que siempre produce una salida de tamaño idéntico, independientemente del tamaño de la entrada. Por ejemplo, si se codifica una sola oración, la salida resultante debe tener el mismo tamaño que si se codificara un libro completo.
- Resistencia a la imagen previa: un buen algoritmo de hash debe ser resistente a la imagen previa, lo que significa que no es prácticamente posible revertir un valor hash para recuperar el mensaje de texto sin formato original. Esto implica que el proceso de hash es irreversible y utiliza funciones de una sola vía.
- Resistencia a la colisión: una colisión ocurre cuando dos entradas diferentes producen resultados idénticos. Un algoritmo de hash confiable debe ser resistente a las colisiones, ya que estas podrían ser utilizadas maliciosamente para hacer que un archivo falsificado pase como auténtico. Por lo tanto, un buen algoritmo de hash debe evitar estas colisiones.
- Efecto avalancha: cualquier cambio realizado en la entrada de datos, por pequeño que sea, debe generar un cambio significativo en la salida del hash. Incluso un cambio mínimo, como agregar una coma, debería resultar en una salida completamente diferente. Esto se denomina «efecto avalancha».
- Velocidad de hash: los algoritmos de hash deben ser lo suficientemente rápidos para calcular valores hash en situaciones prácticas. La velocidad es una propiedad subjetiva, ya que depende del uso específico del algoritmo de hash. Algunas aplicaciones requieren mayor velocidad, como las conexiones a sitios web, mientras que otras pueden beneficiarse más de un hash más lento pero más seguro, como en el caso de las contraseñas.
Ahora que hemos cubierto las características de los hashes, veamos cuáles son los más utilizados en la actualidad.
SHA2
El algoritmo SHA (Secure Hash Algorithm o Algoritmo de Hash Seguro) fue creado inicialmente por la NSA y el NIST con el propósito de generar códigos hash únicos basados en un estándar. En 1993 surgió SHA-1, la primera versión de este protocolo, pero tuvo una adopción limitada y poca repercusión. Posteriormente, se desarrolló SHA-2, que tiene cuatro variantes según el tamaño de salida en bits: SHA2-224, SHA2-256, SHA2-384 y SHA2-512. SHA-1 ya no se considera seguro y se recomienda utilizar SHA2 o SHA3 dentro de la familia de algoritmos SHA.
Funcionamiento de SHA2
Los algoritmos de hash solo funcionan en una dirección, es decir, se puede generar el hash a partir de cualquier contenido, pero es prácticamente imposible generar el contenido original a partir del hash. La única forma de hacerlo es mediante métodos como el uso de diccionarios o fuerza bruta, lo que llevaría miles de años en la actualidad. Entre las muchas formas de crear hashes, el algoritmo SHA2-256 es uno de los más utilizados debido a su equilibrio entre seguridad y velocidad. Es altamente eficiente y tiene una resistencia destacada a las colisiones, lo cual es clave para garantizar su seguridad. Por ejemplo, el proceso de verificación de transacciones de Bitcoin se basa en SHA2-256.
- Tamaño de salida: es el tamaño de caracteres que formarán el hash.
- Tamaño del estado interno: es la suma hash interna, después de cada compresión de un bloque de datos.
- Tamaño del bloque: es el tamaño del bloque que maneja el algoritmo.
- Tamaño máximo del mensaje: es el tamaño máximo del mensaje sobre el que aplicamos el algoritmo.
- Longitud de la palabra: es la longitud en bits de la operación que aplica en cada ronda el algoritmo.
- Interacciones o rondas: es el número de operaciones que realiza el algoritmo para obtener el hash final.
- Operaciones soportadas: son las operaciones que lleva a cabo el algoritmo para obtener el hash final.
SHA-256
Tiene un tamaño de salida de 256 bits, un tamaño de estado interno de 256 bits, un tamaño de bloque de 512 bits, el tamaño máximo del mensaje que puede manejar es de 264 – 1, la longitud de la palabra es de 32 bits, y el número de rondas que se aplican son 64, así como las operaciones que aplica al hash son +, and, or, xor, shr y rot. La longitud del hash siempre es igual, no importa lo grande que sea el contenido que uses para generar el hash: ya sea de sola una letra o una imagen ISO de 4GB de tamaño, el resultado siempre será una sucesión de 40 letras y números.
SHA2-384
Este algoritmo es diferente en cuanto a características, pero su funcionamiento es el mismo. Tiene un tamaño de salida de 384 bits, un tamaño de estado interno de 512 bits, un tamaño de bloque de 1024 bits, el tamaño máximo del mensaje que puede manejar es de 2128 – 1, la longitud de la palabra es de 64 bits, y el número de rondas que se aplican son 80, así como las operaciones que aplica al hash son +, and, or, xor, shr y rot. Este algoritmo es una versión más segura que el SHA2-256, puesto que se aplican más rondas de operaciones y también puede aplicarse sobre una información más extensa. Este algoritmo de hash se suele utilizar para comprobar la integridad de los mensajes y la autenticidad en las redes privadas virtuales. Un aspecto negativo, es que es algo más lento que SHA2-256, pero en determinadas circunstancias puede ser una muy buena opción usar este.
SHA2-512
Como en todos los SHA2, el funcionamiento es el mismo, cambian una sola característica. Tiene un tamaño de salida de 512 bits. El resto de características son iguales que el SHA2-384. 512 bits de tamaño de estado interno, 1024 bits de tamaño de bloque, 2128 – 1 para el tamaño máximo del mensaje, 64 bits de longitud de palabra, y son 80 el número de rondas que se le aplican. Este algoritmo también aplica las mismas operaciones en cada ronda +, and, or, xor, shr y rot.
SHA2-224
No hemos comentado este algoritmo como principal, porque su hermano mayor (SHA2-256) se usa mucho más, ya que la diferencia computacional entre ambos es irrisoria y SHA2-256 está mucho más estandarizado. Lo mencionamos porque, por lo menos hasta el momento, no se han encontrado colisiones para este algoritmo, lo que lo convierte en una opción segura y utilizable.
En la siguiente tabla podremos comprobar mucho mejor las diferencias entre todos los algoritmos en base a sus características.
En la tabla se muestran los algoritmos hash MD5, SHA-0 y SHA-1, pero se han excluido debido a que ya no se consideran seguros ya que se han encontrado colisiones. En la actualidad, los algoritmos SHA2, en todas sus variantes, y SHA3 son los que se utilizan.
Una colisión de hash es cuando dos entradas distintas en una función de hash producen la misma salida. En informática, esto se considera una situación no deseada.
SHA-3
SHA3 es el algoritmo hash más reciente de la familia SHA, fue publicado por el NISH en 2015, pero aún no se ha adoptado ampliamente. Aunque pertenece a la misma familia, su estructura interna es bastante diferente. Este nuevo algoritmo se basa en la «construcción de esponjas», que se basa en una función aleatoria o permutación aleatoria de datos. Permite ingresar cualquier cantidad de datos y generar cualquier cantidad de salida. Además, la función es pseudoaleatoria en relación con las entradas anteriores. Esto brinda a SHA-3 una gran flexibilidad y su objetivo es reemplazar a SHA2 en protocolos típicos de TLS o VPN que utilicen este tipo de hash para verificar la integridad y autenticidad de los datos.
SHA-3 surgió como una alternativa a SHA2, no porque SHA-2 sea inseguro, sino como un plan B en caso de un ataque exitoso contra SHA2. Como resultado, SHA-2 y SHA-3 coexistirán durante muchos años. Actualmente, SHA-3 no se utiliza ampliamente como SHA-2.
Funcionamiento y características
SHA-3 utiliza la construcción de «esponja», que absorbe y procesa los datos para generar la salida deseada. En la etapa de absorción de datos, se utiliza la operación XOR y luego se transforman en una función de permutación. SHA-3 permite bits adicionales de información para protegerse de ataques de extensión, que son posibles en MD5, SHA-1 y SHA-2. Otra característica importante es su flexibilidad, lo que permite probar ataques criptoanalíticos y usarlo en aplicaciones ligeras. Actualmente, SHA2-512 es el doble de rápido que SHA3-512, pero es posible implementar SHA-3 mediante hardware, lo que lo haría igual de rápido o incluso más rápido.
Algoritmos Hash KDF
La diferencia entre una función de derivación de clave (KDF) y una función de hash para contraseñas radica en la longitud de salida. Mientras que una función de hash para contraseñas siempre tiene la misma longitud de salida, con KDF puede variar. Dependiendo si se están hasheando claves de cifrado o contraseñas almacenadas en una base de datos, es recomendable utilizar diferentes algoritmos de hashing. Por ejemplo, para contraseñas almacenadas, se recomienda que el algoritmo hash tome unos segundos para calcularse, pero que sea robusto y costoso de crackear.
Los desarrolladores menos experimentados pueden pensar que las funciones de hash criptográficas genéricas de longitud fija y resistentes a colisiones, como SHA2-256 o SHA2-512, son mejores sin tener en cuenta sus posibles problemas. El problema de los hashes de longitud fija es que son rápidos, lo que permite a un atacante crackear la contraseña rápidamente con un ordenador potente. Los hashes de longitud variable, en cambio, son más lentos, lo cual es ideal para dificultar los intentos de crackear contraseñas.
La comunidad criptográfica se unió para implementar funciones de hash diseñadas específicamente para contraseñas, que incluyen un parámetro de «coste«. Además, se desarrollaron funciones de derivación de claves con un coste similar. Utilizando estas funciones, la comunidad ha creado varios algoritmos para proteger contraseñas:
- Argon2 (KDF)
- scrypt (KDF)
- bcrypt
- PBKDF2 (KDF)
La principal diferencia entre un KDF y una función de hash de contraseñas radica en que la longitud en los KDF es arbitraria, mientras que las funciones hash típicas como MD5, SHA-1, SHA2-256 y SHA2-512 tienen una longitud de salida fija.
En cuanto al almacenamiento de contraseñas, la principal amenaza es una posible filtración de la base de datos de claves en Internet, lo que permitiría a los crackeadores de contraseñas trabajar en los hashes de la base de datos para recuperar las contraseñas.
Un ejemplo común de almacenamiento de contraseñas en una base de datos se presenta al iniciar sesión en un sitio web. En este caso, es necesario que el hashing de la clave se realice de manera rápida para evitar demoras en el acceso. Sin embargo, esto también plantea un problema, ya que el proceso de cracking puede acelerarse, especialmente si se utiliza la potencia de las GPU junto con herramientas como Hashcat.
bcrypt, sha256crypt, sha512crypt y PBKDF2
En la siguiente tabla se realiza una comparación entre varios algoritmos de hash ampliamente utilizados, junto con su correspondiente coste. Se destaca en verde una fila donde un factor de trabajo puede implicar un tiempo de hash de contraseña de medio segundo, lo cual es una relación bastante buena, y en rojo una fila donde un factor de trabajo puede significar dedicar cinco segundos completos a generar una clave de cifrado basada en la contraseña, lo cual es ineficiente.
Es importante tener en cuenta que, para bcrypt, un factor de 13 para el hash de contraseñas proporcionaría un coste de aproximadamente medio segundo para codificar la contraseña, mientras que un factor de 16 estaría cerca de los cinco segundos para crear una clave basada en la contraseña. En el caso de sha256crypt, sha512crypt y PBKDF2, estos valores serían aproximadamente 640.000 y 5.120.000 iteraciones, respectivamente.
scrypt
La razón por la cual consideramos cambiar a scrypt es porque la situación se está volviendo un poco más difícil. Con bcrypt, sha256crypt, sha512crypt y PBKDF2, el coste está completamente relacionado con la carga de la CPU, es decir, a mayor capacidad de procesamiento, mayor eficiencia del algoritmo. Sin embargo, estos algoritmos aún son vulnerables a ataques con FPGA y ASIC específicos para algoritmos. Para hacer frente a esto, se puede incluir un coste de memoria. Con scrypt, el coste afecta tanto a la CPU como a la RAM.
En la siguiente tabla se puede ver una comparativa con diferentes valores de coste.
Estas pruebas se han realizado con una CPU de cuatro núcleos de un solo procesador, se ha tratado de limitar el coste «p» a 1, 2 y 4. Se ha limitado también el uso de la RAM y así no tener que interrumpir el resto de acciones en curso que se estaban llevando a cabo. Por lo que se ha limitado el coste «r» a 4, 8 y 16 multiplicado por 128 bytes (512 bytes, 1024 bytes y 2048 bytes).
Argon2
Argon2 tiene dos versiones distintas: Argon2d y Argon2i; el primero depende de los datos (d) y el segundo es independiente de los datos (i). Se supone que el primero es resistente al crackeo de la GPU, mientras que el segundo es resistente a los ataques de canal lateral. En otras palabras, Argon2d sería adecuado para el hash de contraseñas, mientras que Argon2i sería adecuado para la derivación de claves de cifrado.
Argon2 tiene un coste de CPU y un coste de RAM, ambos se manejan por separado. El coste de la CPU se maneja a través de iteraciones estándar, como con bcrypt o PBKDF2, y el coste de la RAM se maneja aumentando específicamente la memoria. Cuando se comenzaron a hacer pruebas con este algoritmo, se vio que simplemente con manipular las iteraciones se acababa pareciendo mucho a bcrypt, pero a su vez, se podía afectar el tiempo total que tomaba calcular el hash simplemente manipulando la memoria. Al combinar los dos, se descubrió que las iteraciones afectaban al coste de CPU más que al de la RAM, pero ambos tenían una participación significativa en el tiempo de cálculo, como se puede ver en las tablas a continuación. Al igual que con scrypt, también tiene un coste de paralelización, que define la cantidad de subprocesos que desea que trabajen en el problema:
La nota a tener en cuenta en este proceso de parametrización, es que el coste de la RAM varía entre 256 KiB y 16 MiB, además del número de iteraciones y el coste de recuento del procesador. A medida que aumentamos la RAM usada en la parametrización, podremos reducir nuestro coste de iteración. Como necesitamos más subprocesos para trabajar en el hash, podemos reducir aún más esa iteración. Con lo que los dos conceptos que se tratan derivan en que, independientemente, se está tratando de apuntar 0,5 segundos para un inicio de sesión de contraseña interactivo, y 5 segundos completos para la derivación de la clave de cifrado basada en contraseña.
Conclusión
Podemos resumir el uso de estos algoritmos hash a lo siguiente: al aplicar hash a las contraseñas, ya sea para almacenarlas en el disco o para crear claves de cifrado, se deben utilizar criptográficas basadas en contraseñas, diseñadas específicamente para el problema a tratar. No se deben utilizar funciones hash de propósito general de ningún tipo, debido a su velocidad. Además, no deberían implementar su propio algoritmo de «estiramiento de claves», como el hash recursivo de su resumen de contraseña y salida adicional.
Por lo que, si tenemos en cuenta que, si el algoritmo fue diseñado específicamente para manejar contraseñas, y el coste es suficiente para cubrir las necesidades, modelo de amenaza y adversario, entonces podremos decir, sin lugar a dudas, que lo estamos haciendo bien. Realmente, no nos equivocaremos si elegimos cualquiera de ellos, simplemente tenemos que tener claro el uso que le vamos a dar, para así evitar cualquier algoritmo que no esté diseñado específicamente para contraseñas con lo que reforzaremos la seguridad sobre las mismas.
Ahora ya tenéis una idea clara de qué algoritmos se usan hoy en día, hemos explicado el funcionamiento de cada algoritmo e incluso los costes de procesamiento para que podamos tener claro cuál usar dependiendo de la situación. Lo que sí que ha quedado claro es que todos son usados para un objetivo claro en común, nuestra protección, tanto los algoritmos fijos basado en hash como los variables se usan para proteger información, ya que como sabéis la información es poder. Gracias a ellos nuestras contraseñas, archivos y transmisiones de datos está a salvo de todo agente externo que quiera conocerlas.