saltar al contenido
volver al mapa
técnica
etapa: code
3 min de lectura

A04 Cryptographic Failures

La criptografía rara vez se rompe; se omite, se configura mal o se usa mal. El transporte en texto plano, los hashes de contraseña rápidos y las claves incrustadas en el código son la forma en que los datos realmente se filtran.

testssl.sh / SSL LabsSemgrep crypto ruleslibsodiumage / KMS envelope encryptionHashcat (to test your own hashes)

Qué es

Una falla criptográfica es cualquier cosa que expone datos sensibles porque la criptografía faltaba, era débil o se usó de forma incorrecta: datos enviados sin TLS, contraseñas almacenadas con un hash rápido, secretos en el código fuente, tokens "aleatorios" predecibles, algoritmos caseros o claves que nunca rotan.

Es el A04 en el :2025. La primera pregunta siempre es: ¿qué datos son sensibles, por dónde viajan y dónde reposan, y quién puede leerlos ahí?

Cómo funciona el ataque

  • El sniffing de red o un ataque de downgrade lee el tráfico que no está protegido por TLS moderno.
  • La filtración de una base de datos entrega contraseñas con hash MD5 o SHA-1 (o sin salt); una GPU rompe la mayoría en horas.
  • Los tokens construidos a partir de un timestamp o de un generador aleatorio no criptográfico se predicen y se falsifican.
  • Una clave incluida en un commit de un repositorio, o compartida por todos los clientes, descifra todo de una vez.
  • El modo ECB, los nonces reutilizados o el cifrado sin autenticación permiten que un atacante detecte patrones o modifique el texto cifrado.

Código vulnerable

Un hash rápido y sin salt para contraseñas y un generador predecible para un token de restablecimiento.

auth.pypython
import hashlib, random

def store_password(pw):
    return hashlib.md5(pw.encode()).hexdigest()      # rápido y sin salt

def reset_token():
    return str(random.randint(100000, 999999))         # predecible
auth.jsjavascript
const crypto = require("crypto");

const storePassword = (pw) => crypto.createHash("sha1").update(pw).digest("hex");
const resetToken = () => Math.random().toString(36).slice(2);
Auth.javajava
String hash = DigestUtils.md5Hex(password);
String token = String.valueOf(new Random().nextInt(999999));

La corrección

Usa un hash de contraseñas lento, con salt y diseñado para ese fin (, scrypt o bcrypt) mediante una biblioteca mantenida, y obtén los tokens de la fuente aleatoria criptográfica del sistema operativo. No inventes algoritmos; no gestiones claves en bruto a mano si una biblioteca o un puede hacerlo.

auth.pypython
import secrets
from argon2 import PasswordHasher

ph = PasswordHasher()

def store_password(pw):
    return ph.hash(pw)                    # el salt y los parámetros ya están incluidos

def verify_password(stored, pw):
    try:
        return ph.verify(stored, pw)
    except Exception:
        return False

def reset_token():
    return secrets.token_urlsafe(32)      # 256 bits del CSPRNG del SO
auth.jsjavascript
const crypto = require("crypto");
const argon2 = require("argon2");

const storePassword = (pw) => argon2.hash(pw, { type: argon2.argon2id });
const verifyPassword = (stored, pw) => argon2.verify(stored, pw);
const resetToken = () => crypto.randomBytes(32).toString("base64url");
Auth.java (Spring Security)java
PasswordEncoder encoder = Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8();
String hash = encoder.encode(password);
boolean ok = encoder.matches(password, hash);

byte[] raw = new byte[32];
new SecureRandom().nextBytes(raw);
String token = Base64.getUrlEncoder().withoutPadding().encodeToString(raw);
token.gogo
func ResetToken() (string, error) {
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil { // crypto/rand
		return "", err
	}
	return base64.RawURLEncoding.EncodeToString(b), nil
}

Demuestra la corrección

Prueba propiedades, no algoritmos: dos hashes de la misma contraseña son distintos (salt), el valor almacenado no es un simple hash rápido, los tokens son largos y únicos, y el transporte exige TLS moderno.

test_crypto.py (pytest)python
def test_hashes_are_salted_and_slow_format():
    a, b = store_password("correct horse"), store_password("correct horse")
    assert a != b                          # salt aleatorio por hash
    assert a.startswith("$argon2id$")       # no es hex de md5/sha1
    assert verify_password(a, "correct horse")
    assert not verify_password(a, "wrong")

def test_reset_tokens_are_long_and_unique():
    tokens = {reset_token() for _ in range(1000)}
    assert len(tokens) == 1000
    assert all(len(t) >= 43 for t in tokens)   # 32 bytes, base64url
tls-check.shbash
# fallar si el servidor todavía acepta protocolos heredados
testssl.sh --protocols --quiet https://staging.example.com | tee tls.txt
! grep -E 'TLS 1(\.0|\.1)? .*offered' tls.txt

Checklist

  • Clasifica los datos: sabe qué es sensible antes de elegir los controles.
  • TLS en todas partes, solo versiones modernas, HSTS activado; ningún dato sensible en las URLs.
  • Contraseñas: Argon2id, scrypt o bcrypt con una biblioteca mantenida; nunca un hash rápido.
  • La aleatoriedad para seguridad sale del del SO, nunca de Math.random ni de random.
  • Usa (AES-GCM, ChaCha20-Poly1305); nunca reutilices un nonce con la misma clave.
  • Las claves viven en un KMS o gestor de secretos, se rotan y nunca están en el repositorio.
  • No almacenes lo que no necesitas; bórralo cuando termines.
OWASP Top 10La lista de referencia más conocida de riesgos de seguridad en aplicaciones web (A01 control de acceso roto, A02 fallas criptográficas, A03 inyección...), revisada cada pocos años por la comunidad OWASP. Es un mapa educativo de categorías de riesgo, no un checklist de herramientas ni una certificación.ver en el glosario →Argon2idUna función de hash de contraseñas de uso intensivo de memoria con salt integrado. La lentitud es el objetivo: hace que cada intento sea costoso para el atacante.ver en el glosario →KMSkey management serviceServicio administrado que crea, almacena y controla el uso de claves criptográficas. La aplicación rara vez toca la clave maestra: le pide al KMS que cifre o descifre, y el proveedor registra cada uso en un log auditable. Sin KMS, la clave termina en disco y se vuelve un objetivo.ver en el glosario →CSPRNGgenerador de números aleatorios criptográficamente seguroLa fuente de bytes impredecibles del sistema operativo (secrets en Python, crypto.randomBytes en Node, crypto/rand en Go). Úsala para tokens y claves, nunca Math.random.ver en el glosario →Cifrado autenticadoAEADCifrado que además detecta la manipulación (AES-GCM, ChaCha20-Poly1305). El cifrado por sí solo no impide que un atacante modifique el texto cifrado.ver en el glosario →

conexiones en el mapa

actualizado el 14/10/2026 · fuentes para profundizar · reportar un error

en otros dominios

Estudia antes

Desbloquea

pon a prueba lo que entendiste

Cargando preguntas…