MD5 vs SHA256: funciones hash explicadas de forma simple

9 min18 de mayo de 2026

Qué hacen las funciones hash (MD5 vs SHA256 en español llano)

Una función hash toma cualquier entrada — un solo carácter, un archivo de 10 GB, un string vacío — y produce una salida de tamaño fijo llamada digest. MD5 siempre produce 128 bits (32 caracteres hexadecimales). SHA-256 siempre produce 256 bits (64 caracteres hexadecimales). La misma entrada siempre produce la misma salida, pero no puedes revertir el proceso para recuperar la entrada. Ese es todo el concepto. El debate md5 vs sha256 se reduce a una pregunta: ¿necesitas resistencia a colisiones?

Resistencia a colisiones significa que debería ser computacionalmente inviable encontrar dos entradas diferentes que produzcan el mismo hash. MD5 perdió esta propiedad en 2004 cuando Xiaoyun Wang demostró ataques de colisión prácticos. Para 2012, los investigadores podían crear PDFs colisionantes con contenido visible diferente pero hashes MD5 idénticos. SHA-256 no tiene colisiones conocidas y se espera que permanezca seguro por décadas.

Pero esto es lo que la mayoría de artículos explican mal: MD5 no está "roto" para todo. Está roto para seguridad (firmas digitales, certificados, verificación de integridad contra manipulación maliciosa). Es perfectamente válido para usos no relacionados con seguridad: deduplicación, claves de caché, checksums para corrupción accidental, distribución en tablas hash. Si tu modelo de amenazas no incluye un atacante fabricando colisiones deliberadamente, MD5 es rápido y adecuado.

Cómo funcionan las funciones hash (la mecánica)

Todas las funciones hash criptográficas siguen el mismo patrón: rellenar la entrada a un múltiplo del tamaño de bloque, dividirla en bloques, y procesar cada bloque a través de una función de compresión que lo mezcla con el estado acumulado. MD5 usa bloques de 512 bits y 4 rondas de 16 operaciones cada una. SHA-256 usa bloques de 512 bits y 64 rondas. Más rondas = más mezcla = más difícil de revertir o encontrar colisiones.

El efecto avalancha es lo que hace útiles a los hashes: cambiar un solo bit en la entrada invierte aproximadamente el 50% de los bits de salida. "hello" y "hellp" producen hashes completamente diferentes sin relación visible. Esto significa que no puedes deducir nada sobre la entrada a partir de la salida, y entradas similares no producen salidas similares. Nuestro hash-generator demuestra esto — prueba cambiar un carácter y observa cómo cambia todo el hash.

El rendimiento varía dramáticamente. En hardware moderno (Intel i7-13700K), MD5 procesa unos 6 GB/s, SHA-256 unos 2 GB/s (u 8 GB/s con aceleración hardware SHA-NI), SHA-3 unos 1.5 GB/s, y BLAKE3 unos 12 GB/s (está diseñado para velocidad con SIMD). Para hashear contraseñas, quieres lentitud — bcrypt con cost 12 hace unas 4 hashes/segundo en el mismo CPU. Eso es deliberado.

Un detalle que confunde a la gente: las funciones hash son deterministas pero no portables entre codificaciones. El SHA-256 del string "hello" depende de si lo codificas como UTF-8, UTF-16 o ASCII. Los bytes son diferentes, así que el hash es diferente. Siempre especifica la codificación. En JavaScript, new TextEncoder().encode("hello") te da bytes UTF-8, que es la convención estándar.

// Same string, different encodings = different hashes
const text = "hello";

// UTF-8 (standard): 68 65 6c 6c 6f (5 bytes)
// SHA-256: 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

// UTF-16LE: 68 00 65 00 6c 00 6c 00 6f 00 (10 bytes)  
// SHA-256: completely different hash

// Always use UTF-8 unless you have a specific reason not to
const encoder = new TextEncoder(); // UTF-8 by default
const bytes = encoder.encode(text);
const hash = await crypto.subtle.digest("SHA-256", bytes);

Cuándo usar cada algoritmo (guía práctica)

Integridad de archivos (detectar corrupción accidental): MD5 o CRC32 está bien. Estás protegiendo contra errores de disco y fallos de red, no contra atacantes. MD5 es más rápido que SHA-256 y la salida de 128 bits es suficiente. Los gestores de paquetes de Linux todavía usan checksums MD5 junto con SHA-256 por compatibilidad. Si quieres seguridad extra a costo mínimo, usa SHA-256 — es suficientemente rápido para la mayoría de tamaños de archivo.

Direccionamiento por contenido y deduplicación: SHA-256 es el estándar. Git usa SHA-1 (migrando a SHA-256), IPFS usa SHA-256, las capas de imágenes Docker usan SHA-256. El hash se convierte en la identidad del contenido — si dos archivos tienen el mismo SHA-256, son el mismo archivo (con probabilidad abrumadora). No uses MD5 aquí porque un atacante podría fabricar un archivo malicioso con el mismo MD5 que uno legítimo.

Firmas digitales y certificados: SHA-256 como mínimo. Los certificados TLS migraron de SHA-1 a SHA-256 en 2017 después de que Google demostró una colisión de SHA-1 (el ataque "SHAttered", que costó ~$110,000 en tiempo de GPU). Para sistemas nuevos, SHA-256 o SHA-3 son buenas opciones. Las firmas Ed25519 usan SHA-512 internamente.

Hashing de contraseñas: NINGUNO de los anteriores. MD5, SHA-256, SHA-3 son todos incorrectos para contraseñas porque son demasiado rápidos. Usa bcrypt, scrypt o Argon2id — estas son funciones deliberadamente lentas diseñadas para resistir ataques de fuerza bruta. Una GPU puede computar 10 mil millones de hashes SHA-256 por segundo pero solo ~70 hashes bcrypt por segundo. Mira nuestro password-generator para generar contraseñas que resistan incluso el brute-forcing con hashes lentos.

La familia SHA: SHA-1, SHA-2, SHA-3

SHA-1 (160 bits): Roto desde 2017. El ataque SHAttered de Google produjo dos PDFs diferentes con el mismo hash SHA-1. Costo: ~$110,000 en tiempo de GPU en la nube en 2017, probablemente menos de $10,000 hoy. No uses SHA-1 para nada relacionado con seguridad. Git todavía lo usa pero está migrando a SHA-256. Chrome y Firefox rechazan certificados SHA-1 desde 2017.

Familia SHA-2 (SHA-224, SHA-256, SHA-384, SHA-512): El estándar actual. SHA-256 es la variante más común. SHA-512 es más rápido en procesadores de 64 bits (contraintuitivamente — procesa bloques de 1024 bits vs los bloques de 512 bits de SHA-256, y las operaciones de 64 bits son nativas). SHA-384 es simplemente SHA-512 con un estado inicial diferente y salida truncada. Para la mayoría de propósitos, SHA-256 es la opción por defecto.

SHA-3 (Keccak, estandarizado en 2015): Un diseño interno completamente diferente a SHA-2 (construcción esponja vs Merkle-Damgård). Existe como respaldo en caso de que SHA-2 sea comprometido — tener dos algoritmos no relacionados significa que un avance contra uno no afecta al otro. SHA-3 es ligeramente más lento que SHA-2 en software pero tiene un margen de seguridad más amplio. Úsalo si tus requisitos de cumplimiento lo especifican, de lo contrario SHA-256 está bien.

BLAKE3 (2020): No es una variante de SHA pero vale la pena mencionarlo. Es 3-6x más rápido que SHA-256, paralelizable (escala con núcleos de CPU), y tiene una salida de 256 bits. Se usa en Bao (streaming verificado), el ecosistema Rust, y cada vez más en almacenamiento direccionado por contenido. La desventaja: es más nuevo y aún no está en estándares NIST, así que las industrias reguladas no pueden usarlo. Para herramientas internas y aplicaciones sensibles al rendimiento, BLAKE3 es excelente.

Colisiones de hash: qué significan en la práctica

Una colisión son dos entradas diferentes que producen la misma salida hash. Para un hash de 128 bits (MD5), la paradoja del cumpleaños dice que probablemente encontrarás una colisión después de unos 2^64 intentos (~18 trillones). Para SHA-256 (256 bits), son 2^128 intentos — más que el número de átomos en el universo observable. En la práctica, las colisiones de SHA-256 nunca serán encontradas por fuerza bruta.

Pero la fuerza bruta no es el único ataque. El criptoanálisis explota debilidades matemáticas en el algoritmo. La función de compresión de MD5 tiene fallas estructurales que permiten encontrar colisiones en segundos en una laptop (no 2^64 intentos, sino unos 2^18 — unos cientos de miles de operaciones). SHA-1 requiere unas 2^63 operaciones (todavía costoso pero factible para estados-nación y atacantes bien financiados).

¿Qué puede hacer un atacante con una colisión? Puede crear dos documentos con el mismo hash — uno benigno, uno malicioso. Obtiene la firma/certificación del benigno, luego sustituye el malicioso. Así es como el malware Flame (2012) usó una colisión MD5 para falsificar un certificado de Windows Update de Microsoft. La autoridad certificadora firmó un certificado de apariencia legítima, pero los atacantes tenían un certificado colisionante que funcionaba para su malware.

Para usos no relacionados con seguridad, las colisiones no son un problema. Si estás usando MD5 como clave de tabla hash o identificador de caché, una colisión solo significa que dos entradas diferentes mapean al mismo bucket — tu código maneja esto con encadenamiento o direccionamiento abierto. La probabilidad es tan baja (1 en 2^64 para entradas aleatorias) que nunca la verás en la práctica. La preocupación de seguridad es solo sobre colisiones fabricadas deliberadamente.

HMAC: cuando necesitas autenticación, no solo hashing

Un hash simple verifica integridad (los datos no fueron corrompidos accidentalmente) pero no autenticidad (los datos vinieron de quien crees). Un atacante que modifica los datos puede recalcular el hash. HMAC (Hash-based Message Authentication Code) resuelve esto mezclando una clave secreta en el hash: HMAC(key, message) = Hash((key ⊕ opad) || Hash((key ⊕ ipad) || message)).

Usa HMAC cuando: verificas firmas de webhooks de APIs (Stripe, GitHub, Shopify todos usan HMAC-SHA256), creas tokens a prueba de manipulación (las firmas JWT usan HMAC-SHA256 con el algoritmo HS256), o validas que los datos no han sido modificados en tránsito por un atacante. La clave debe mantenerse secreta — si el atacante tiene la clave, HMAC no provee protección.

Error común: usar Hash(key + message) en lugar de HMAC. Esto es vulnerable a ataques de extensión de longitud — un atacante que conoce Hash(key + message) puede computar Hash(key + message + attacker_data) sin conocer la clave. SHA-256 y MD5 son ambos vulnerables a esto. La construcción de doble hashing de HMAC lo previene. SHA-3 no es vulnerable a extensión de longitud (estructura interna diferente), pero usa HMAC de todos modos por consistencia.

En código: Node.js tiene crypto.createHmac("sha256", key).update(message).digest("hex"). Python tiene hmac.new(key, message, hashlib.sha256).hexdigest(). Nunca implementes HMAC tú mismo — usa la librería estándar de tu lenguaje. La construcción parece simple pero los ataques de timing en la comparación (usar === en lugar de crypto.timingSafeEqual) pueden filtrar el HMAC correcto byte por byte.

Errores comunes con funciones hash

Error 1: Usar SHA-256 para contraseñas. SHA-256 es rápido — eso es malo para contraseñas. Una RTX 4090 computa 22 mil millones de hashes SHA-256 por segundo. Una contraseña de 8 caracteres del set ASCII completo (6.6 cuatrillones de combinaciones) cae en 3.5 días. Usa bcrypt/Argon2id que reducen eso a siglos. Si estás almacenando contraseñas de usuarios con SHA-256 (incluso con salt), migra inmediatamente.

Error 2: Comparar hashes con == en un contexto de seguridad. La comparación de strings se cortocircuita en el primer carácter diferente, filtrando información de timing. Un atacante puede determinar el HMAC correcto un carácter a la vez midiendo tiempos de respuesta. Usa comparación en tiempo constante: crypto.timingSafeEqual() en Node.js, hmac.compare_digest() en Python, subtle.ConstantTimeCompare() en Go.

Error 3: Truncar hashes para "IDs más cortos". Si tomas los primeros 8 caracteres de un hash SHA-256 (32 bits), tu probabilidad de colisión salta a 50% con solo 77,000 elementos (paradoja del cumpleaños). He visto esto en acortadores de URL y claves de caché. Si necesitas identificadores más cortos, usa un generador de IDs cortos diseñado para eso (nanoid, hashids) en lugar de truncar un hash criptográfico.

Error 4: Asumir que hash = encriptación. El hashing es unidireccional — no puedes recuperar la entrada a partir de la salida. La encriptación es bidireccional — puedes desencriptar con la clave. Si necesitas almacenar datos que necesitarás leer después (API keys, números de tarjeta de crédito), usa encriptación (AES-256-GCM). Si necesitas verificar datos sin almacenarlos (contraseñas, verificaciones de integridad), usa hashing. Son operaciones fundamentalmente diferentes.