MD5 vs SHA-1 vs SHA-256: ¿qué hash conviene usar?
Use SHA-256 para todo lo que tenga que ver con seguridad; MD5 y SHA-1 solo son aceptables cuando nadie gana nada falsificando un hash que coincida.
MD5, SHA-1 y SHA-256 hacen el mismo trabajo: convierten una entrada de cualquier longitud en una huella corta y de tamaño fijo llamada resumen o digest. La misma entrada produce siempre el mismo resumen, y cambiar un solo bit cambia el resultado entero. Los tres se diferencian en la longitud del resumen, en la velocidad y, sobre todo, en si alguien puede fabricar a propósito dos entradas distintas con el mismo resumen. Con MD5 y SHA-1 eso ya se ha hecho públicamente. Con SHA-256, no.
Los tres algoritmos de un vistazo
| MD5 | SHA-1 | SHA-256 | |
|---|---|---|---|
| Tamaño del resumen | 128 bits, 32 caracteres hexadecimales | 160 bits, 40 caracteres hexadecimales | 256 bits, 64 caracteres hexadecimales |
| Publicación | 1992 (RFC 1321) | 1995 (FIPS 180-1) | 2001, como parte de la familia SHA-2 |
| Colisiones | Prácticas desde 2004 | Prácticas desde 2017 | Ningún ataque conocido |
| Encontrar una entrada para un resumen dado | Ningún ataque práctico | Ningún ataque práctico | Ningún ataque práctico |
| Uso adecuado hoy | Checksums sin fines de seguridad, claves de caché | Solo compatibilidad con sistemas antiguos | La opción por defecto para cualquier proyecto nuevo |
Estos son los resúmenes completos de los cinco bytes hello, tal como los imprimen md5sum, sha1sum y sha256sum. El generador de hash muestra los mismos valores uno junto a otro, además de SHA-384 y SHA-512, tanto para texto como para un archivo de hasta 50 MB.
MD5 5d41402abc4b2a76b9719d911017c592
SHA-1 aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
SHA-256 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
Qué aportan los bits de más
La longitud del resumen fija el coste de un ataque genérico, es decir, uno que trata la función como una caja negra. Por la paradoja del cumpleaños, encontrar dos entradas cualesquiera que colisionen exige unos 2^(n/2) intentos para un resumen de n bits: 2^64 en MD5, 2^80 en SHA-1 y 2^128 en SHA-256. Encontrar una entrada que coincida con un resumen concreto (una preimagen) exige unos 2^n.
Esas cifras solo valen mientras el diseño no tenga atajos. Tanto MD5 como SHA-1 tienen atajos para encontrar colisiones, y por eso su longitud ya no cuenta toda la historia. SHA-256 no tiene ningún atajo conocido, y 2^128 queda muy lejos de cualquier capacidad de cómputo que exista.
Cómo se rompieron MD5 y SHA-1
Un ataque de colisión no permite a nadie invertir un hash. Lo que permite es preparar de antemano dos entradas distintas que comparten resumen, conseguir que una de ellas sea aceptada como fiable y después sustituirla por la otra. La variante peligrosa es la colisión de prefijo elegido (chosen-prefix): el atacante escoge dos comienzos con sentido, por ejemplo dos certificados con nombres distintos, y calcula bytes adicionales que igualan los resúmenes.
- MD5, 2004: Xiaoyun Wang y sus colaboradores publicaron colisiones prácticas de MD5.
- MD5, 2008: un equipo de investigadores usó una colisión de MD5 con prefijo elegido para crear un certificado de autoridad de certificación falso en el que los navegadores habrían confiado, porque una CA comercial seguía firmando certificados con MD5.
- MD5, 2012: el malware Flame usó una colisión de MD5 para falsificar un certificado de firma de código de Microsoft.
- SHA-1, 2017: Google y el CWI de Ámsterdam anunciaron SHAttered, dos archivos PDF distintos con el mismo resumen SHA-1. El equipo informó de unos 6500 años de CPU y 110 años de GPU de cómputo.
- SHA-1, 2020: Gaëtan Leurent y Thomas Peyrin publicaron una colisión práctica de SHA-1 con prefijo elegido y la usaron para suplantar una clave PGP.
Los navegadores dejaron de aceptar certificados TLS con SHA-1 en 2017, y el NIST anunció en diciembre de 2022 que SHA-1 debe retirarse por completo antes de que termine 2030. Ni MD5 ni SHA-1 tienen un ataque de preimagen práctico: a partir de un resumen, nadie puede encontrar una entrada que lo produzca. Por eso los dos siguen sirviendo para detectar daños accidentales.
Velocidad: medida, no supuesta
A MD5 se le suele describir como el rápido. Calculamos el hash de los mismos 256 MB de datos aleatorios con Node 24 (OpenSSL 3.5) en un mismo ordenador de escritorio, con tres ejecuciones por algoritmo:
| Algoritmo | Rendimiento en esa máquina |
|---|---|
| SHA-1 | unos 1000 MB/s |
| MD5 | unos 730 MB/s |
| SHA-512 | unos 690 MB/s |
| SHA-256 | unos 460 MB/s |
El orden depende del procesador. Esta CPU no tiene las extensiones del juego de instrucciones SHA; los procesadores que sí las tienen aceleran SHA-1 y SHA-256 por hardware, y entonces SHA-256 puede superar a MD5. SHA-512 ganó aquí a SHA-256 porque trabaja con palabras de 64 bits. Con entradas cortas la diferencia desaparece: un millón de hashes de una dirección de correo corta tardó entre 1,8 y 2,1 segundos con MD5, con SHA-1 y con SHA-256, porque el coste fijo de cada llamada pesa más que el propio cálculo. La velocidad casi nunca es un motivo real para elegir MD5.
Para qué sigue sirviendo cada uno
- MD5: detectar corrupción accidental, eliminar duplicados entre sus propios archivos, claves de caché y ETags, y comunicarse con sistemas antiguos que lo esperan. Nunca para firmas, certificados ni comprobaciones de integridad en las que un atacante pueda aportar el archivo.
- SHA-1: sistemas existentes que todavía no puede cambiar. Git lleva mucho tiempo nombrando los objetos con SHA-1, mediante una implementación reforzada que detecta los ataques de colisión conocidos, y también admite repositorios que usan SHA-256. HMAC-SHA1, el algoritmo por defecto detrás de los códigos TOTP de las apps de autenticación, no se ve afectado por los ataques de colisión.
- SHA-256: checksums de descargas, firmas digitales, certificados, HMAC, direccionamiento por contenido y cualquier cosa nueva. Use SHA-256 cuando un checksum tenga que resistir manipulaciones, y reserve MD5 para los valores heredados con los que todavía tenga que coincidir.
¿Va a comprobar una descarga? El comprobador de checksum de archivos calcula el hash del archivo en su propio equipo y le indica con qué algoritmo coincide el checksum que pegue.
Ninguno sirve para guardar contraseñas
Los tres están diseñados para ser rápidos, y la rapidez es justo lo que quiere un atacante que prueba miles de millones de contraseñas. Una sal (salt) frena las tablas precalculadas, pero no ralentiza los intentos. Para almacenar contraseñas hace falta una función deliberadamente lenta y, a ser posible, que consuma mucha memoria (memory-hard). En la misma máquina que antes:
| Función y parámetros | Tiempo por contraseña |
|---|---|
| SHA-256, una llamada desde Node | unos 2,5 microsegundos |
| Argon2id, 19 MiB de memoria, 2 iteraciones, 1 carril | unos 33 milisegundos |
| PBKDF2-HMAC-SHA256, 600 000 iteraciones | unos 270 milisegundos |
| scrypt, N=2^17, r=8, p=1 | unos 310 milisegundos |
Esos parámetros son los mínimos de la OWASP Password Storage Cheat Sheet, que recomienda Argon2id en primer lugar, después scrypt, bcrypt con un factor de trabajo de 10 o más para sistemas heredados, y PBKDF2 cuando se exige cumplir FIPS-140. Use la API de hash de contraseñas de su framework en lugar de montar una a partir de un hash simple.
HMAC: cuando un hash necesita una clave
Para demostrar que un mensaje viene de alguien que conoce un secreto, no calcule sha256(secret + message). MD5, SHA-1 y SHA-256 son vulnerables a la extensión de longitud (length extension): conocer el resumen de un mensaje permite a un atacante calcular un resumen válido para ese mismo mensaje con datos añadidos al final, sin conocer el secreto. HMAC (RFC 2104) lo evita. HMAC-SHA256 es la opción estándar, y todas estas líneas imprimen el mismo valor:
// Node.js
crypto.createHmac('sha256', 'secret').update('hello').digest('hex')
# Python
hmac.new(b'secret', b'hello', hashlib.sha256).hexdigest()
// PHP
hash_hmac('sha256', 'hello', 'secret')
// 88aab3ede8d3adf94d26ab90d3bafd4a2083070c3bcce9c014ee04a443847c0b
Compare los HMAC con una función de tiempo constante, como crypto.timingSafeEqual, hmac.compare_digest o hash_equals, y no con ==.
Calcular hashes en la línea de comandos y en código
Estos son los comandos equivalentes en Linux, macOS, Windows PowerShell y cualquier sistema con OpenSSL. En PowerShell, SHA256 es el algoritmo por defecto. La última parte muestra por qué el hash de una cadena cambia según se use printf o echo: printf no añade salto de línea y echo sí.
# Linux (coreutils)
md5sum file.iso
sha1sum file.iso
sha256sum file.iso
# macOS
md5 file.iso
shasum -a 1 file.iso
shasum -a 256 file.iso
# Windows PowerShell
Get-FileHash file.iso -Algorithm MD5
Get-FileHash file.iso -Algorithm SHA1
Get-FileHash file.iso # SHA256 is the default
# Any system with OpenSSL
openssl dgst -sha256 file.iso
# Hashing a string: printf adds no newline, echo does
printf '%s' hello | sha256sum # 2cf24dba5fb0a30e...
echo hello | sha256sum # 5891b5b522d5df08...
En el navegador, crypto.subtle.digest() admite SHA-1, SHA-256, SHA-384 y SHA-512, pero no MD5; si se le pide MD5, lanza un NotSupportedError. Por eso las herramientas que funcionan en el navegador, incluidas las de este sitio, calculan MD5 con una implementación aparte en JavaScript.