Bases de Criptografía — Cifrado, Hashing y Firmas
Guía de criptografía para desarrolladores: cifrado simétrico y asimétrico, hashing, firmas digitales y gestión de claves con ejemplos de código prácticos.
Resumen
La criptografía está debajo de casi todos los controles de seguridad que toca un desarrollador: almacenamiento de contraseñas, sesiones TLS, JWT firmados, backups cifrados, verificación de webhooks. Usarla bien es sobre todo elegir la primitiva correcta para cada trabajo — cifra lo que necesites recuperar después, hashea lo que solo necesites comparar, firma lo que otros deban verificar — y luego no improvisar en los detalles como IVs, almacenamiento de claves y elección de algoritmo.
Esta guía cubre las cuatro primitivas que usa todo desarrollador de aplicaciones — cifrado simétrico, cifrado asimétrico, hashing y firmas digitales — más los dos temas operativos que causan la mayoría de fallos reales: gestión de claves y configuración de TLS. Todos los ejemplos usan pyca/cryptography o el módulo crypto de Node, las dos librerías con menos probabilidades de permitirte hacer algo peligroso por accidente. No hacen falta matemáticas — cuando una decisión depende de detalles internos, lo decimos y te damos el default seguro.
Cuándo usarla
- Necesitas proteger datos en reposo (campos de base de datos, archivos, backups) o en tránsito (llamadas API, service mesh)
- Estás implementando autenticación, tokens de sesión o webhooks firmados
- Necesitas verificar la integridad u origen de datos — checksums, firmas, tags HMAC
- Estás eligiendo entre librerías criptográficas, algoritmos o tamaños de clave
Para profundizar en proteger datos almacenados — AES-256, integración con KMS, cifrado de sobre — consulta Cifrado en Reposo: AES-256, KMS, Envelope Encryption.
Cifrado Simétrico
La misma clave cifra y descifra. Los cifradores simétricos son rápidos — AES-256 procesa gigabytes por segundo en CPUs modernas — así que soportan casi todo el trabajo de cifrado en volumen. El compromiso es la distribución: todo el que pueda descifrar debe tener la clave, lo que convierte la gestión de claves en la parte difícil.
AES (Advanced Encryption Standard)
AES-256 en modo GCM es el default correcto para sistemas nuevos. GCM es cifrado autenticado: produce un tag que detecta manipulación, así que obtienes confidencialidad e integridad en un paso. Los modos CBC y CTR solo dan confidencialidad — hace falta un HMAC aparte, y equivocar el orden (“encrypt-then-MAC” frente a “MAC-then-encrypt”) ha roto sistemas reales.
from cryptography.fernet import Fernet
# Generar una clave (Fernet = AES-128-CBC + HMAC, API de alto nivel segura)
key = Fernet.generate_key()
cipher = Fernet(key)
# Cifrar
token = cipher.encrypt(b"datos sensibles")
# Descifrar — lanza InvalidToken si el ciphertext fue manipulado
data = cipher.decrypt(token)
Para control de bajo nivel, usa AESGCM directamente. El IV (nonce) debe ser único por clave — nonces aleatorios de 96 bits funcionan hasta aproximadamente 2³² mensajes por clave; reutilizar un nonce con la misma clave destruye por completo la seguridad de GCM.
| Modo | Caso de uso | Seguridad |
|---|---|---|
| AES-GCM | Casi todo lo nuevo | Cifrado autenticado — la elección obvia |
| AES-CBC | Solo compatibilidad legacy | Necesita HMAC separado para integridad |
| AES-CTR | Streaming, poco común | Necesita manejo cuidadoso del IV |
| AES-ECB | Nunca | Filtra patrones del plaintext — no usar |
Gestión de Claves
- Nunca hardcodees claves ni las subas a un repo; usa un KMS (AWS KMS, GCP KMS, Azure Key Vault) o un gestor de secretos
- Rota claves periódicamente y siempre ante sospecha de compromiso — diseña para rotación desde el día uno
- Separa claves por ambiente y propósito: una para campos de BD en dev, otra para backups en prod
- Guarda las claves en un límite de confianza distinto al de los datos que protegen
Cifrado Asimétrico
Dos claves matemáticamente enlazadas: lo que la clave pública cifra solo la clave privada lo descifra; lo que la clave privada firma, cualquiera con la pública lo verifica. La criptografía asimétrica resuelve el problema de distribución de claves — puedes publicar la mitad pública — pero es miles de veces más lenta que AES y solo cifra payloads pequeños. Por eso los sistemas reales son híbridos: la asimétrica intercambia o protege una clave simétrica, y la simétrica hace el trabajo pesado.
RSA
RSA-2048 es la base; RSA-3072 para márgenes de seguridad más largos. Para cifrar, usa siempre padding OAEP — el RSA de libro de texto y el padding PKCS#1 v1.5 han producido oráculos explotables.
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
# Generar par de claves
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
public_key = private_key.public_key()
# Cifrar con la clave pública (solo payloads pequeños — normalmente una clave simétrica)
encrypted = public_key.encrypt(
b"mensaje secreto",
padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None)
)
# Descifrar con la clave privada
decrypted = private_key.decrypt(
encrypted,
padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None)
)
Curva Elíptica (ECDH/ECDSA)
Seguridad equivalente con claves mucho más pequeñas: una clave EC de 256 bits equivale aproximadamente a una RSA de 3072 bits, y las operaciones son más rápidas. Prefiere SECP256R1 (P-256) por compatibilidad, X25519/Ed25519 donde controlas ambos extremos.
from cryptography.hazmat.primitives.asymmetric import ec
private_key = ec.generate_private_key(ec.SECP256R1())
public_key = private_key.public_key()
Hashing
Un hash mapea cualquier entrada a una huella de tamaño fijo en una sola dirección — no existe “descifrar”. Se usa para comprobaciones de integridad, direccionamiento por contenido y (con el algoritmo adecuado) almacenamiento de contraseñas.
Algoritmos de Hashing Seguros
| Algoritmo | Tamaño de salida | Estado |
|---|---|---|
| SHA-256 | 256 bits | Recomendado de propósito general |
| SHA-3 | Variable | Recomendado, diseño diverso |
| BLAKE2b / BLAKE3 | 256+ bits | Rápido, moderno |
| Argon2id | Variable | Hashing de contraseñas — recomendado |
| bcrypt | 184 bits | Hashing de contraseñas — sólido legacy |
| scrypt | Variable | Hashing de contraseñas — memory-hard |
| MD5 | 128 bits | Roto — solo checksums, nunca seguridad |
| SHA-1 | 160 bits | Roto — no usar |
import hashlib
# SHA-256 para comprobaciones de integridad y huellas
digest = hashlib.sha256(b"datos").hexdigest()
# Para contraseñas: usa Argon2id, bcrypt o scrypt — nunca SHA-256 puro.
# Los hashes rápidos están diseñados para velocidad; un atacante prueba miles
# de millones de candidatos por segundo.
Hashing de Contraseñas
Las contraseñas necesitan funciones deliberadamente lentas y memory-hard. Argon2id es la recomendación actual (OWASP, RFC 9106); bcrypt con factor de costo ≥ 12 sigue siendo una opción sólida y muy soportada.
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
# Verificar — usa compare de la librería, no ==, para evitar fugas por timing
ok = bcrypt.checkpw(password.encode(), hashed)
HMAC — Hashing con Clave
Para verificar integridad y autenticidad de un mensaje sobre el que compartes un secreto — payloads de webhooks, URLs firmadas — usa HMAC, no un hash simple. HMAC-SHA256 es el estándar; un SHA-256(secreto + mensaje) a secas es vulnerable a ataques de extensión de longitud.
import hmac
tag = hmac.new(secret_key, message, hashlib.sha256).digest()
# Verificar con hmac.compare_digest(tag, received_tag) — comparación de tiempo constante
Firmas Digitales
Una firma demuestra que un mensaje vino del dueño de una clave privada y no fue modificado. Los JWT, releases de software, cadenas de certificados y webhooks firmados dependen de esto.
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives import hashes, serialization
# Firmar con la clave privada
signature = private_key.sign(
message,
padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH),
hashes.SHA256()
)
# Verificar con la clave pública — lanza InvalidSignature si falla
public_key.verify(
signature,
message,
padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH),
hashes.SHA256()
)
Para sistemas nuevos, Ed25519 es más simple y seguro que RSA-PSS: parámetros fijos, más rápido, firmas pequeñas. RSA persiste sobre todo por compatibilidad con JWT (RS256) y PKI legacy.
Transport Layer Security (TLS)
TLS es criptografía híbrida en acción: la asimétrica (certificados + ECDHE) autentica el servidor y negocia claves de sesión, y luego la simétrica AES-GCM o ChaCha20 transporta el tráfico real.
- Usa TLS 1.3; desactiva 1.0 y 1.1 por completo y prefiere 1.2 solo por compatibilidad
- Habilita HSTS para que los navegadores rechacen intentos de downgrade
- Considera certificate pinning en apps móviles (evalúa bien el costo operativo)
- Monitorea el vencimiento de certificados a 30, 14 y 7 días — los certificados vencidos son la causa más común de caídas TLS
- Termina TLS lo más cerca del borde que sea práctico, y cifra también los saltos internos si tu modelo de amenazas incluye movimiento lateral
Certificados y Fundamentos de PKI
Un certificado vincula una clave pública con una identidad — un dominio, una organización — a través de una cadena de firmas que termina en una Certificate Authority que tu cliente ya confía. Cuando la verificación TLS falla, el problema casi siempre está en esa cadena, no en la criptografía.
Las piezas que conviene conocer:
- Hoja vs cadena: el servidor presenta su certificado hoja más los intermedios; el cliente verifica cada firma hasta llegar a una CA raíz en su trust store. Un intermedio que falta es el clásico “funciona en Chrome, falla en curl” — los navegadores cachean intermedios, otros clientes no.
- SAN (Subject Alternative Name): el certificado debe listar cada hostname que protege.
ejemplo.comyapi.ejemplo.comnecesitan entradas separadas o un wildcard. - Autofirmado vs emitido por CA: autofirmado vale para servicios internos donde controlas el trust store; cualquier cosa pública necesita una CA real. Let’s Encrypt emite certificados gratuitos y automatizados de 90 días — las vidas más cortas son una ventaja, no un defecto, porque la rotación está automatizada.
- Revocación: OCSP stapling es el mecanismo práctico; las CRL aún importan en PKI interna. Si una clave se compromete, rota el certificado de inmediato — no esperes al vencimiento.
La PKI interna merece la misma disciplina: certificados hoja de vida corta, un emisor automatizado (Vault PKI, smallstep) y una historia clara de qué pasa cuando rota un intermedio.
Elegir la Primitiva Correcta
La mayoría de preguntas de “qué algoritmo” son en realidad de “qué primitiva”. Parte de lo que necesitas garantizar:
| Necesidad | Primitiva | Elección típica |
|---|---|---|
| Leer los datos después | Cifrado simétrico | AES-256-GCM |
| Intercambiar claves / cifrar para un destinatario | Cifrado asimétrico | ECDH, RSA-OAEP |
| Comparar sin guardar el original | Hashing | SHA-256; Argon2id para contraseñas |
| Demostrar que un mensaje no se alteró | MAC / firma | HMAC-SHA256 (secreto compartido), Ed25519 (verificación pública) |
| Combinar integridad + secreto | Cifrado autenticado | AES-GCM, ChaCha20-Poly1305 |
El patrón híbrido de fondo — asimétrica para intercambio de claves, simétrica para datos — vale la pena memorizarlo porque explica TLS, PGP, el cifrado de sobre y la mayoría de diseños de gestión de claves.
Errores Comunes
- Inventar tu propia criptografía — las construcciones caseras fallan de formas que los tests unitarios nunca muestran. Usa librerías validadas:
pyca/cryptography, libsodium, OpenSSL, Bouncy Castle,cryptode Node. - Usar modo ECB — bloques de plaintext idénticos producen ciphertext idéntico; el pingüino sigue visible. GCM en todo.
- Reutilizar IVs/nonces — catastrófico en CTR y GCM: misma clave + mismo nonce filtra relaciones del plaintext y permite falsificar tags.
- Hashear contraseñas con SHA-256 — los hashes rápidos son un regalo para la fuerza bruta. Solo Argon2id o bcrypt.
- Guardar claves junto a los datos — un volcado de la base de datos no debería entregar también las claves. Separa los límites de confianza.
- Implementar tu propio
compare_digest—==sobre secretos filtra por timing. Usa la comparación de tiempo constante de la librería. - Ignorar side channels — el timing y los mensajes de error pueden filtrar más de lo que protegen las matemáticas; los padding oracles (POODLE, Lucky13) explotaron exactamente eso.
Solución de Problemas
InvalidToken/InvalidTagal descifrar: el ciphertext, la clave, el IV o el auth tag no coinciden — datos corruptos en almacenamiento, versión de clave incorrecta tras una rotación, o manipulación. Registra qué versión de clave se usó.Bad padding/ errores OAEP: estás descifrando con la clave privada equivocada o el modo de padding incorrecto. Confirma que el par de certificados y el algoritmo hash de OAEP coinciden en ambos extremos.signature verification faileden JWTs: desfase de reloj (exp/nbf), algoritmo equivocado (la confusiónRS256vsHS256es un ataque clásico) o caché JWKS desactualizado. Refresca claves, revisaiss/aud, fija el algoritmo.- Fallos de handshake tras habilitar TLS 1.3: un middlebox o cliente viejo en el camino. Habilita 1.2+1.3 durante la migración y revisa el
ClientHellocon una captura de paquetes. cert expireda las 3 de la mañana: no había alertas de vencimiento. Pon la renovación 30 días antes en el runbook y monitorea el certificado hoja y la cadena.- Caída de rendimiento bajo carga: firmas RSA en la ruta crítica. Cambia a Ed25519/ECDSA, o firma una vez y cachea.
Ejemplo Avanzado: Cifrado de Sobre en una App Fintech
Un diseño común en producción: una app fintech que maneja transacciones y PII necesita cifrado en tres capas — tránsito, almacenamiento y nivel de campo para las columnas más sensibles.
Capa Tecnología Propósito
---- ---------- ---------
Tránsito TLS 1.3 Comunicación de red
Reposo (BD) AES-256-GCM Cifrado de disco/volumen
Nivel campo AES-256-GCM + sobre Columnas sensibles (PII)
Backups AES-256 + KMS Archivos de backup
Secretos KMS + rotación Custodia de claves
El cifrado de sobre mantiene la clave de datos cerca de los datos: genera una Data Encryption Key (DEK) aleatoria, cifra el campo con ella y luego cifra la propia DEK con una Key Encryption Key (KEK) custodiada por KMS. El almacén guarda ciphertext + DEK cifrada; para descifrar hay que pedir a KMS que desenvuelva la DEK primero. Comprometer la base de datos sola no expone nada — la KEK nunca sale del KMS.
// Cifrado de sobre con AWS KMS (Node.js)
const { KMSClient, EncryptCommand, DecryptCommand } = require("@aws-sdk/client-kms");
const crypto = require("crypto");
async function encryptField(plaintext, kmsKeyId) {
// 1. Generar una DEK aleatoria de 256 bits y un IV único de 96 bits
const dek = crypto.randomBytes(32);
const iv = crypto.randomBytes(12);
// 2. Cifrar el campo con la DEK (AES-256-GCM)
const cipher = crypto.createCipheriv("aes-256-gcm", dek, iv);
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
const authTag = cipher.getAuthTag();
// 3. Cifrar la DEK bajo la KEK de KMS
const kms = new KMSClient();
const response = await kms.send(new EncryptCommand({
KeyId: kmsKeyId,
Plaintext: dek,
}));
// 4. Almacenar: DEK cifrada + IV + authTag + ciphertext
return {
encryptedDek: response.CiphertextBlob.toString("base64"),
iv: iv.toString("base64"),
authTag: authTag.toString("base64"),
ciphertext: ciphertext.toString("base64"),
};
}
async function decryptField(encrypted) {
// 1. Desenvolver la DEK vía KMS
const kms = new KMSClient();
const response = await kms.send(new DecryptCommand({
CiphertextBlob: Buffer.from(encrypted.encryptedDek, "base64"),
}));
const dek = response.Plaintext;
// 2. Descifrar el campo
const decipher = crypto.createDecipheriv(
"aes-256-gcm", dek, Buffer.from(encrypted.iv, "base64")
);
decipher.setAuthTag(Buffer.from(encrypted.authTag, "base64"));
const plaintext = Buffer.concat([
decipher.update(Buffer.from(encrypted.ciphertext, "base64")),
decipher.final()
]);
return plaintext.toString("utf8");
}
Conclusiones del diseño:
- Una DEK por registro (o por campo) limita el radio de impacto; una KEK las envuelve a todas
- La DEK solo existe en texto plano dentro de la memoria del proceso
- KMS rota las KEK automáticamente y registra cada desenvoltura en el audit log
- El IV debe ser único por DEK; guardarlo junto al ciphertext está bien — no es secreto
Lecturas Adicionales
- OWASP Cryptographic Storage Cheat Sheet — selección de algoritmos y reglas de almacenamiento mantenidas por OWASP
- NIST SP 800-38D — Recomendación GCM — el estándar detrás de los modos de cifrado autenticado
- Documentación de libsodium — la API de alto nivel con opiniones firmes que muchos equipos adoptan en lugar de componer primitivas
- Ejemplos de código del repositorio companion — ejemplos ejecutables de cifrado, hashing y KMS en el repositorio complementario
Preguntas frecuentes
¿Cuál es la diferencia entre cifrado y hashing?
El cifrado es reversible — quien tenga la clave puede recuperar el original. El hashing es unidireccional — solo puedes verificar una hipótesis hasheándola y comparando. Cifra lo que necesites después; hashea lo que solo compares, como las contraseñas (con Argon2id o bcrypt, no SHA-256).
¿Debería usar AES-256 o AES-128?
Cualquiera de los dos vale hoy; ambos resisten la fuerza bruta mucho más allá de los presupuestos de ataque prácticos. AES-256 añade margen frente a avances futuros y cuesta algo más de CPU. Los sistemas nuevos normalmente eligen AES-256-GCM y listo.
¿Qué es el cifrado autenticado?
Cifrado que además detecta manipulación. AES-GCM produce un auth tag junto al ciphertext; el descifrado falla ruidosamente si algo se modificó. AES-CBC puro solo oculta los datos — un atacante aún puede voltear bits sin ser detectado salvo que añadas un HMAC.
¿Cuándo uso cifrado simétrico frente a asimétrico?
Simétrico (AES) para datos en volumen en reposo — es rápido y simple pero exige compartir la clave. Asimétrico (RSA, ECDH) para intercambio de claves y firmas — resuelve la distribución pero es lento y con payloads limitados. Los sistemas reales combinan ambos: la asimétrica protege o intercambia una clave simétrica, que luego hace el trabajo pesado. Ese patrón híbrido es exactamente lo que hacen TLS y el cifrado de sobre.
¿Por qué no puedo hashear contraseñas con SHA-256?
SHA-256 está construido para velocidad — las GPUs prueban miles de millones de candidatos por segundo. El hashing de contraseñas necesita lo contrario: funciones deliberadamente lentas y memory-hard como Argon2id o bcrypt que hacen caro cada intento. Misma familia de primitivas, objetivos de diseño opuestos.
¿Es seguro escribir mi propio formato de cifrado?
Casi nunca. Los fallos viven en los detalles: reutilización de IV, padding oracles, falta de autenticación, manejo de claves. APIs de alto nivel como Fernet (Python), secretbox de libsodium o AWS Encryption SDK hacen que la opción segura sea el default — úsalas antes de componer primitivas por tu cuenta.
Recursos Relacionados
Gestión de Secretos: Vault y Cloud Managers
Guía práctica de gestión de secretos: HashiCorp Vault, AWS Secrets Manager, Azure Key Vault y GCP Secret Manager con rotación, control de acceso e integración CI/CD.
GuideCifrado en Reposo: AES-256, KMS y Cifrado Envolvente
Cifra los datos almacenados correctamente: AES-256-GCM, claves gestionadas por KMS, cifrado envolvente, rotación de claves y cifrado a nivel de campo.
RecipeHashing de Contraseñas en Producción
Hashea y verifica contraseñas de forma segura usando bcrypt, scrypt y Argon2 con lo que funciona.
RecipeEncripta y Desencripta Datos con AES-GCM en Python
Encripta datos sensibles usando AES-GCM con la librería cryptography. Cubre derivación de claves, generación de nonces, encriptación autenticada y encriptación de archivos.
GuideOWASP Top 10: Explicado con Mitigaciones
Guía orientada a desarrolladores sobre los 10 riesgos de seguridad más críticos de OWASP: cómo funciona cada vulnerabilidad, ejemplos del mundo real y mitigaciones prácticas.
RecipeGestiona Secretos con HashiCorp Vault y Python
Almacena, recupera y rota secretos de aplicación de forma segura usando HashiCorp Vault con cliente Python hvac, secretos dinámicos y renovación automática de leases.