Fundamentos y criptografía
"Si pensás que la criptografía resolverá tus problemas de seguridad, no entendiste tus problemas y no entendiste la criptografía." — Bruce Schneier.
Qué vas a aprender en este capítulo
La criptografía es la base matemática de toda la seguridad moderna: HTTPS, mensajería cifrada, contraseñas guardadas, firmas digitales, blockchain. Este capítulo explica las primitivas (cifrado simétrico, asimétrico, hash) y cómo se combinan en protocolos como TLS.
1.1 Principios de defensa
💡 Intuición
La seguridad no es un solo muro alto — son múltiples capas de defensa. Si un atacante rompe la primera, encuentra otra. Si rompe esa, otra más. Esto se llama defensa en profundidad.
Una pupusería con una sola puerta de seguridad: si el ladrón la abre, entra. Una con: cerca, alarma, cámaras, caja fuerte → el ladrón puede pasar la primera, pero no las cinco.
📐 Fundamento
Principios fundamentales:
| Principio | Descripción | Ejemplo |
|---|---|---|
| Defensa en profundidad | Múltiples capas | Firewall + WAF + auth + cifrado |
| Mínimo privilegio | Solo permisos necesarios | App de cocina no necesita acceso a pagos |
| Fail securely | Al fallar, denegar acceso | Si auth service cae, denegar login (no permitir) |
| Secure by default | Seguro sin configuración | TLS habilitado por defecto |
| No security through obscurity | No depender del secreto del diseño | Algoritmos públicos, claves secretas |
| Zero trust | No confiar en la red interna | Autenticar cada request, incluso interno |
Modelo de amenazas (Threat Model):
Antes de defender, hay que saber qué se defiende y de quién:
- ¿Qué activos tenemos? (datos de clientes, infraestructura, reputación)
- ¿Quiénes son los atacantes? (script kiddies, criminales, estado-nación, insiders)
- ¿Qué pueden hacer? (ataques de red, ingeniería social, acceso físico)
- ¿Qué tan motivados están?
- ¿Qué pasaría si tienen éxito?
STRIDE — taxonomía de amenazas (Microsoft):
| Letra | Amenaza | Propiedad violada |
|---|---|---|
| Spoofing | Pretender ser otro usuario | Autenticación |
| Tampering | Modificar datos | Integridad |
| Repudiation | Negar haber hecho algo | No-repudio |
| Information Disclosure | Filtrar datos | Confidencialidad |
| Denial of Service | Hacer el sistema indisponible | Disponibilidad |
| Elevation of Privilege | Obtener permisos no autorizados | Autorización |
1.2 Cifrado simétrico
💡 Intuición
Cifrado simétrico: la misma clave cifra y descifra. Como una caja fuerte: la misma combinación abre y cierra.
Es muy rápido (megabytes por segundo) pero tiene un problema: ¿cómo le pasás la clave al otro lado sin que un atacante la intercepte?
📐 Fundamento
AES (Advanced Encryption Standard): el estándar moderno.
- Tamaños de clave: 128, 192, 256 bits.
- AES-256 es virtualmente irrompible con tecnología actual.
Modos de operación:
| Modo | Características | Uso |
|---|---|---|
| ECB (Electronic Codebook) | Cada bloque cifrado independientemente | NUNCA usar (patrones visibles) |
| CBC (Cipher Block Chaining) | Cada bloque depende del anterior + IV | Requiere padding |
| GCM (Galois/Counter Mode) | Cifrado autenticado (cifra + verifica integridad) | Recomendado actual |
Demostración: por qué ECB es malo
Si cifrás una imagen con ECB, el patrón sigue siendo visible. Con CBC o GCM, el resultado parece ruido aleatorio.
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
# Generar clave aleatoria de 256 bits
clave = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(clave)
nonce = os.urandom(12) # nonce de 96 bits — único por mensaje
# Cifrar
mensaje = b"Numero de tarjeta: 4532-1234-5678-9012"
asociado = b"pedido_id=12345" # datos asociados (no cifrados pero autenticados)
ciphertext = aesgcm.encrypt(nonce, mensaje, asociado)
print(f"Cifrado: {ciphertext.hex()}")
# Descifrar
plaintext = aesgcm.decrypt(nonce, ciphertext, asociado)
print(f"Descifrado: {plaintext.decode()}")
# Si alguien modifica el ciphertext o el asociado, decrypt falla
# → garantía de integridad además de confidencialidad
Reglas de oro:
- Nunca reutilizar un nonce con la misma clave — rompe la seguridad de GCM.
- Generar la clave con
os.urandom, nunca conrandom(no es criptográficamente seguro). - Rotar claves periódicamente y guardarlas en un KMS (Key Management Service: AWS KMS, HashiCorp Vault).
1.3 Cifrado asimétrico
💡 Intuición
Cifrado asimétrico: dos claves matemáticamente relacionadas. Una pública (la podés repartir libremente) y una privada (la guardás como un secreto absoluto).
- Lo cifrado con la pública solo se descifra con la privada → confidencialidad.
- Lo cifrado con la privada se verifica con la pública → firma digital (autenticidad).
Resuelve el problema del intercambio de claves del cifrado simétrico, pero es ~1000x más lento.
📐 Fundamento
RSA — el algoritmo asimétrico clásico:
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization
# Generar par de claves
clave_privada = rsa.generate_private_key(public_exponent=65537, key_size=2048)
clave_publica = clave_privada.public_key()
# Cifrar con clave pública
mensaje = b"Mi mensaje secreto"
ciphertext = clave_publica.encrypt(
mensaje,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# Descifrar con clave privada
plaintext = clave_privada.decrypt(
ciphertext,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
print(plaintext.decode()) # "Mi mensaje secreto"
# Firma digital
firma = clave_privada.sign(
mensaje,
padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH),
hashes.SHA256()
)
# Verificar firma con clave pública
clave_publica.verify(
firma,
mensaje,
padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH),
hashes.SHA256()
) # No lanza excepción → firma válida
Comparación de algoritmos asimétricos:
| Algoritmo | Tamaño de clave típico | Velocidad | Uso |
|---|---|---|---|
| RSA-2048 | 2048 bits | Lenta | Estándar histórico, en migración |
| RSA-4096 | 4096 bits | Más lenta | Mayor seguridad |
| ECDSA P-256 | 256 bits | Rápida | Recomendado actual |
| Ed25519 | 256 bits | Muy rápida | Recomendado actual (SSH, TLS modernos) |
Cifrado híbrido (lo que realmente se usa):
Como asimétrico es lento, en la práctica se combina:
1. Generar una clave simétrica aleatoria (clave de sesión)
2. Cifrar el mensaje grande con la clave simétrica (AES)
3. Cifrar la clave simétrica con la pública del destinatario (RSA)
4. Enviar: ciphertext_simétrico + clave_de_sesión_cifrada
Esto es exactamente lo que hace TLS internamente.
1.4 Funciones hash criptográficas
📐 Fundamento
Una función hash: transforma datos de cualquier tamaño en una huella de tamaño fijo.
import hashlib
mensaje = b"Pupusas de queso"
print(hashlib.sha256(mensaje).hexdigest())
# c8e9...9aa3 (64 caracteres hex = 256 bits)
# Cualquier cambio mínimo cambia el hash completamente
mensaje2 = b"pupusas de queso" # solo cambia mayúscula
print(hashlib.sha256(mensaje2).hexdigest())
# 1f4b...8e02 (completamente diferente — efecto avalancha)
Propiedades criptográficas:
| Propiedad | Significado |
|---|---|
| Preimagen | Dado h, no se puede encontrar m tal que hash(m)=h |
| Segunda preimagen | Dado m1, no se puede encontrar m2 ≠ m1 con hash(m1)=hash(m2) |
| Resistencia a colisiones | No se pueden encontrar dos mensajes m1, m2 con hash(m1)=hash(m2) |
| Efecto avalancha | Cambio mínimo en input → cambio masivo en output |
Algoritmos hash:
| Algoritmo | Tamaño | Estado |
|---|---|---|
| MD5 | 128 bits | ROTO — no usar |
| SHA-1 | 160 bits | ROTO — no usar |
| SHA-256 | 256 bits | Recomendado |
| SHA-512 | 512 bits | Recomendado para mayor seguridad |
| BLAKE2 | Variable | Más rápido que SHA-2 |
| BLAKE3 | Variable | El más moderno y rápido |
Hashing para contraseñas — NUNCA usar SHA por sí solo:
# MAL — SHA-256 puro es muy rápido, fácil de bruteforcear
password_hash = hashlib.sha256(b"contrasena123").hexdigest()
# Un GPU moderno puede probar miles de millones de hashes/segundo
# BIEN — usar bcrypt, scrypt, o Argon2 (lentos y con sal automática)
import bcrypt
password_hash = bcrypt.hashpw(b"contrasena123", bcrypt.gensalt(rounds=12))
# bcrypt es deliberadamente lento → solo se pueden probar miles/segundo
HMAC (Hash-based Message Authentication Code):
Verifica que un mensaje no fue modificado y que viene de quien dice:
import hmac, hashlib
clave_compartida = b"clave_secreta_compartida_entre_partes"
mensaje = b"transferencia: $100 de A a B"
# Emisor genera HMAC
mac = hmac.new(clave_compartida, mensaje, hashlib.sha256).hexdigest()
# Receptor verifica
mac_recibido = mac
mac_calculado = hmac.new(clave_compartida, mensaje, hashlib.sha256).hexdigest()
if hmac.compare_digest(mac_recibido, mac_calculado):
print("Mensaje válido y no modificado")
Importante: Usar hmac.compare_digest() en lugar de == para evitar timing attacks (un atacante puede medir cuánto tarda la comparación letra por letra y deducir el HMAC correcto).
1.5 TLS y la PKI
💡 Intuición
Cuando entrás a https://banco.com, ¿cómo sabe tu navegador que está hablando con el banco real y no con un imitador? La respuesta: certificados digitales firmados por Autoridades Certificadoras (CAs) en las que tu navegador confía.
📐 Fundamento
TLS Handshake (TLS 1.3 simplificado):
Cliente Servidor
│ │
│ ──── ClientHello (cipher suites, ECDHE) ──→│
│ │
│ ←──── ServerHello + Certificate + ────────│
│ Firma digital + ECDHE_pubkey │
│ │
│ Verifica certificado contra CAs confiables │
│ Calcula clave maestra (ECDHE) │
│ │
│ ──── Finished (cifrado con clave maestra) ─→│
│ │
│ ←──── Aplicación: HTTP cifrado ────────────│
Verificación del certificado:
- El servidor envía su certificado (contiene clave pública + dominio + firma de CA).
- El cliente verifica que la firma sea válida usando la clave pública de la CA.
- La clave pública de la CA está pre-instalada en el sistema operativo / navegador.
- El cliente verifica que el dominio del certificado coincida con el dominio al que se conectó.
- Verifica que el certificado no esté expirado o revocado.
Cadena de confianza:
Root CA (DigiCert, Let's Encrypt, etc.) ← preinstalada en SO
│ firma
▼
Intermediate CA
│ firma
▼
Certificate de la-esquina.com ← contiene clave pública del servidor
Let's Encrypt: Autoridad Certificadora gratuita y automatizada. Permite obtener certificados TLS válidos sin costo:
# En el servidor de producción
certbot --nginx -d la-esquina.com -d www.la-esquina.com
# → instala automáticamente certificado válido por 90 días
# → certbot lo renueva automáticamente
Forward Secrecy: Si la clave privada del servidor es comprometida en el futuro, los mensajes pasados siguen siendo seguros (porque la clave de sesión se generó efímeramente con ECDHE y nunca se guardó). TLS 1.3 lo hace por defecto.
1.6 Ejercicios
✏️ Ejercicio 1.1 — Elegir el algoritmo correcto
Para cada caso de uso, elegí el algoritmo apropiado:
a. Cifrar la base de datos completa de pagos en disco (encryption at rest). b. Verificar que un archivo descargado no fue modificado. c. Guardar la contraseña de un usuario en la base de datos. d. Firmar un documento electrónico para que sea legalmente válido. e. Cifrar la comunicación entre el navegador del cliente y el servidor de La Esquina Cloud.
Solución
a. AES-256-GCM (cifrado simétrico) — rápido para grandes volúmenes; GCM agrega autenticación.
b. SHA-256 (función hash) — comparar el hash del archivo descargado con el publicado por el sitio oficial.
c. bcrypt, scrypt o Argon2 — funciones de hashing diseñadas para contraseñas (lentas + sal automática).
d. ECDSA P-256 o Ed25519 (firma digital asimétrica) — la firma con clave privada es legalmente vinculante; cualquiera con la clave pública puede verificar.
e. TLS 1.3 que internamente combina: ECDHE (intercambio de claves), AES-256-GCM (cifrado simétrico), SHA-256 (integridad), ECDSA (autenticación del servidor).
✏️ Ejercicio 1.2 — Modelo de amenazas (STRIDE)
Para cada componente de La Esquina Cloud, identificá la amenaza STRIDE más crítica y propone una mitigación:
a. API que recibe órdenes de pedidos desde la app móvil. b. Base de datos de pagos. c. Panel administrativo del dueño (web). d. Servicio de notificaciones por SMS.
Solución
a. Tampering / Spoofing — un atacante podría enviar requests falsificados pretendiendo ser un cliente. Mitigación: autenticación con JWT firmado, HTTPS obligatorio, validación de inputs.
b. Information Disclosure — fuga de números de tarjeta. Mitigación: cifrado at-rest con AES-256-GCM, no guardar tarjetas (usar tokenización con Stripe), cifrado en tránsito (TLS), control de acceso estricto (mínimo privilegio).
c. Elevation of Privilege — un usuario con permisos limitados podría intentar acceder a funciones de administrador. Mitigación: RBAC estricto, validación server-side de permisos en cada request, MFA para acciones sensibles.
d. Denial of Service — alguien podría enviar millones de requests para enviar SMS y agotar el saldo del proveedor. Mitigación: rate limiting por IP/usuario, captcha, validación de teléfonos.
1.7 Para profundizar
- Schneier, Applied Cryptography — el libro clásico de criptografía aplicada.
- Ferguson, Schneier & Kohno, Cryptography Engineering — más moderno.
- Cryptopals Challenges (cryptopals.com) — ejercicios prácticos para entender ataques.
- Siguiente: Autenticación y autorización.
Definiciones nuevas: triada CIA, defensa en profundidad, modelo de amenazas, STRIDE, cifrado simétrico, AES, GCM, nonce, IV, cifrado asimétrico, RSA, ECDSA, Ed25519, función hash criptográfica, SHA-256, bcrypt, HMAC, timing attack, TLS, PKI, certificado digital, CA, Let's Encrypt, Forward Secrecy, ECDHE.