Tres tareas distintas, tres herramientas distintas
"Hashear" se usa para describir tres trabajos que no tienen nada que ver entre sí, y buena parte de los errores de seguridad con hashes vienen de usar la herramienta de uno para hacer otro:
- Verificar integridad: comprobar que un archivo o un mensaje no cambió. Herramienta: un hash rápido y resistente a colisiones, como SHA-256.
- Autenticar un mensaje: comprobar que un mensaje viene de quien tiene una clave secreta y no fue alterado. Herramienta: HMAC.
- Guardar contraseñas: poder verificar una contraseña sin almacenarla. Herramienta: una función deliberadamente lenta, con sal, como Argon2id o bcrypt.
Esta guía explica qué garantiza cada una, cómo cayeron MD5 y SHA-1, y qué usar en cada caso. Para calcular hashes o HMAC de un texto o un archivo, el generador de hash trabaja en el navegador.
Qué garantiza un hash criptográfico
Una función hash criptográfica tiene que resistir tres tipos de ataque, de menor a mayor dificultad para quien la diseña:
- Preimagen: dado un hash, encontrar alguna entrada que lo produzca.
- Segunda preimagen: dada una entrada concreta, encontrar otra con el mismo hash.
- Colisión: encontrar dos entradas cualesquiera con el mismo hash.
La colisión es la más fácil de lograr por una razón estadística, la paradoja del cumpleaños: con un hash de n bits, basta con probar unas 2^(n/2) entradas para tener una probabilidad alta de encontrar dos que coincidan. Por eso la seguridad frente a colisiones es la mitad del tamaño del hash:
| Algoritmo | Tamaño | Seguridad teórica frente a colisiones | Situación real |
|---|---|---|---|
| MD5 | 128 bits | 2⁶⁴ | Colisiones en segundos con un equipo común |
| SHA-1 | 160 bits | 2⁸⁰ | Colisiones demostradas (2017) y con prefijo elegido (2020) |
| SHA-256 | 256 bits | 2¹²⁸ | Sin ataques prácticos |
| SHA-512 | 512 bits | 2²⁵⁶ | Sin ataques prácticos |
| SHA3-256 | 256 bits | 2¹²⁸ | Sin ataques prácticos; diseño distinto (Keccak) |
Cómo cayeron MD5 y SHA-1
MD5 fue diseñado por Ron Rivest en 1991. En 1996 aparecieron las primeras debilidades teóricas, y en 2004 el equipo de Xiaoyun Wang publicó colisiones reales. A partir de ahí los ataques mejoraron rápido: en 2008, un grupo de investigadores usó una colisión de MD5 para crear un certificado de autoridad de certificación falso que los navegadores aceptaban como válido. En 2012, el malware de espionaje Flame usó una técnica similar para falsificar una firma de Microsoft y distribuirse a través de Windows Update.
SHA-1 resistió más. En 2005 se publicó un ataque teórico, y en 2017 Google y el instituto CWI de Ámsterdam presentaron SHAttered: dos archivos PDF distintos con el mismo hash SHA-1, obtenidos tras unos 9 trillones (9 × 10¹⁸) de cálculos de SHA-1. Ese mismo año los navegadores dejaron de aceptar certificados firmados con SHA-1. En 2020 llegó la colisión con prefijo elegido, que permite fabricar colisiones a partir de dos documentos cualesquiera, el tipo de ataque más peligroso en la práctica. Git, que usaba SHA-1 para identificar todo su contenido, incorporó detección de colisiones y agregó soporte para repositorios con SHA-256.
La lección no es que MD5 y SHA-1 den resultados "incorrectos": siguen detectando perfectamente un archivo dañado por accidente. Lo que perdieron es la garantía frente a alguien que elige el contenido a propósito.
Integridad: checksums de descargas
Para verificar que un archivo descargado es idéntico al publicado, se compara su hash con el que publica el sitio. Con SHA-256, si coinciden, el archivo es el mismo bit a bit. Con MD5, si coinciden, el archivo es el mismo salvo que alguien haya preparado deliberadamente una versión distinta con el mismo hash, algo que hoy es factible.
Dos aclaraciones prácticas. Primero, un CRC32 (el que usan los archivos ZIP o Ethernet) detecta errores de transmisión pero no es criptográfico: fabricar una colisión es trivial. Segundo, el checksum solo vale tanto como el canal por el que se obtuvo: si el atacante controla el servidor, cambia el archivo y el checksum juntos. Por eso las distribuciones de Linux firman su archivo SHA256SUMS con GPG.
Autenticar mensajes: por qué HMAC y no hash(clave + mensaje)
Supongamos una API que firma cada petición calculando SHA-256(clave_secreta + mensaje) y la envía junto al mensaje. Parece seguro: sin la clave, nadie puede calcular la firma. Pero MD5, SHA-1, SHA-256 y SHA-512 comparten una construcción interna (Merkle–Damgård) que permite un ataque de extensión de longitud: quien conoce el hash de clave + mensaje y el largo de la clave puede calcular el hash de clave + mensaje + relleno + datos_agregados sin conocer la clave. En 2009, la API de Flickr fue vulnerable exactamente a esto: un atacante podía agregar parámetros a una petición firmada y producir una firma válida.
La solución estándar es HMAC (RFC 2104), que aplica el hash dos veces con la clave combinada de una forma que neutraliza el ataque. HMAC-SHA256 es lo que usan los webhooks de la mayoría de las plataformas, la firma de JWT con HS256 y la autenticación de muchas APIs. Un ejemplo de prueba del RFC 4231: con la clave Jefe y el mensaje what do ya want for nothing?, el HMAC-SHA256 es 5bdcc146bf60754e…64ec3843. Puede comprobarse en el generador escribiendo la clave en el campo HMAC.
Al verificar un HMAC recibido, la comparación debe hacerse en tiempo constante (crypto.timingSafeEqual en Node.js, hmac.compare_digest en Python). Una comparación normal termina en el primer carácter distinto, y medir esa diferencia de tiempo permite adivinar la firma de a un carácter.
Contraseñas: por qué un hash rápido es un error
Para guardar contraseñas no hay que guardarlas, sino algo que permita verificarlas: al iniciar sesión se calcula lo mismo con la contraseña ingresada y se compara. La tentación es usar SHA-256(contraseña). El problema es la velocidad:
| Función | Orden de magnitud en una GPU moderna |
|---|---|
| MD5 | más de 100.000 millones de intentos por segundo |
| SHA-256 | decenas de miles de millones por segundo |
| bcrypt (costo 10–12) | miles por segundo |
| Argon2id (19 MiB de memoria) | comparable a bcrypt, y además exige 19 MiB de memoria por intento |
Si se filtra una base de datos con hashes SHA-256, un atacante prueba diccionarios de miles de millones de contraseñas conocidas en segundos. Las funciones diseñadas para contraseñas agregan tres defensas:
- Sal: un valor aleatorio distinto por usuario, guardado junto al hash. Hace que dos usuarios con la misma contraseña tengan hashes distintos e inutiliza las tablas precalculadas (rainbow tables).
- Costo ajustable: se pueden configurar para que cada cálculo tarde, por ejemplo, 100 ms en el servidor. Para el usuario es imperceptible; para el atacante significa millones de veces menos intentos.
- Uso de memoria (Argon2id, scrypt): obligan a usar decenas de megas por intento, lo que limita cuántos cálculos puede hacer en paralelo una GPU.
Las recomendaciones actuales de OWASP, en orden de preferencia:
| Función | Parámetros mínimos recomendados |
|---|---|
| Argon2id | 19 MiB de memoria, 2 iteraciones, paralelismo 1 |
| scrypt | N = 2¹⁷, r = 8, p = 1 |
| bcrypt | costo 10 o más; tiene un límite de 72 bytes de contraseña |
| PBKDF2-HMAC-SHA256 | 600.000 iteraciones (cuando se requiere cumplir FIPS-140) |
Cuánto resiste una contraseña depende también de su entropía, que se explica en Cómo crear contraseñas seguras: la entropía explicada. Una función lenta convierte una contraseña de 40 bits de "minutos" en "años", pero no salva a 123456, que está primera en cualquier diccionario.
Qué usar para cada cosa
| Tarea | Usar | Evitar |
|---|---|---|
| Verificar una descarga o un archivo | SHA-256 | MD5 y SHA-1 si hay posibilidad de manipulación |
| Detectar duplicados o cambios accidentales | SHA-256; MD5 aceptable sin adversario | CRC32 si importa la seguridad |
| Firmar mensajes con una clave compartida | HMAC-SHA256 | hash(clave + mensaje) |
| Guardar contraseñas | Argon2id, scrypt o bcrypt | Cualquier hash rápido, con o sin sal |
| Firmas digitales y certificados | SHA-256 o superior | MD5 y SHA-1 |
| Identificar contenido (Git, caches) | SHA-256 | SHA-1 en sistemas nuevos |
Resumen
Un hash criptográfico resume cualquier entrada en una huella de tamaño fijo, y su valor depende de que nadie pueda fabricar dos entradas con la misma huella: MD5 y SHA-1 perdieron esa propiedad, SHA-256 la conserva. Para autenticar mensajes, el hash solo no es suficiente y se usa HMAC. Para contraseñas, la velocidad que hace útil a SHA-256 se vuelve su peor defecto, y la respuesta son funciones lentas con sal: Argon2id, scrypt o bcrypt.