El módulo anterior terminó con una lista de defensas que ya has visto funcionar —TLS, las URLs firmadas de 120 segundos, el HMAC del webhook de la pasarela, DKIM, las passkeys, la firma RS256 del JWT, el cifrado de las copias, la comparación en tiempo constante del TOTP— y con una constatación: todas descansan sobre el mismo cimiento y hasta ahora lo hemos tratado como una caja negra. Esta lección abre la caja. No vas a salir de ella sabiendo cifrar: vas a salir sabiendo qué pide uno a la criptografía, qué le puede pedir de verdad, con qué vocabulario preciso se habla de ella y por qué —esta es la idea que atraviesa todo el módulo— la parte difícil casi nunca es el algoritmo. Es importante porque la mayoría de los fallos criptográficos reales de las PYMES no consisten en romper AES, sino en usar la primitiva equivocada para el problema equivocado, confundir codificar con cifrar, o generar una clave con una fuente de aleatoriedad que no lo era.
Contenido
- Qué problema resuelve exactamente la criptografía
- Vocabulario preciso: cifrar, codificar, hashear y ofuscar no son lo mismo
- Historia como palanca conceptual: César, Vigenère y Enigma
- El principio de Kerckhoffs y por qué la seguridad vive en la clave
- Modelos de atacante y tipos de ataque criptográfico
- Qué significa «seguro» hoy: bits de seguridad y tamaños de clave
- Aleatoriedad criptográfica: el cimiento invisible
- El mapa del módulo 3
- La criptografía que Nimbus ya usa sin haberla explicado
- Qué problema resuelve exactamente la criptografía
La criptografía es el conjunto de técnicas matemáticas que permiten proteger información frente a un adversario que tiene acceso al canal, al soporte o a ambos. Esa última frase es la clave y conviene leerla despacio: la criptografía no supone un canal seguro ni un disco a salvo. Supone lo contrario. Asume que el atacante ve los datos, y aun así consigue que no le sirvan de nada.
Es la aplicación directa del principio de asume la brecha de 01-03. Una nota clínica de una clínica de fisioterapia cliente de Nimbus viaja por wifis de cafeterías, se guarda en discos de un proveedor cloud y se copia a buckets de respaldo. En ninguno de esos tres puntos controlamos el medio físico. La criptografía convierte «que nadie toque el medio» —imposible— en «que tocar el medio no sirva de nada» —alcanzable.
Qué propiedad aporta cada primitiva
En 01-01 se definió la tríada CIA y sus complementos: autenticidad, no repudio y trazabilidad. Cada una de esas propiedades tiene una primitiva criptográfica concreta que la proporciona. Esta tabla es el mapa mental del módulo entero:
| Propiedad que se quiere | Primitiva criptográfica | Ejemplo concreto | Se estudia en |
|---|---|---|---|
| Confidencialidad: que nadie más lo lea | Cifrado (simétrico o asimétrico) | AES-GCM sobre un adjunto de Nimbus | 03-02, 03-03 |
| Integridad: detectar que ha cambiado | Función hash (frente a error) y MAC (frente a un atacante) | SHA-256 de una copia; HMAC de un webhook | 03-04 |
| Autenticidad: saber quién lo produjo | MAC (entre dos que comparten clave) o firma digital (frente a todos) | HMAC de la pasarela; firma del JWT | 03-03, 03-04 |
| No repudio: que el emisor no pueda negarlo | Firma digital exclusivamente | Ed25519 sobre un recibo de reserva | 03-03 |
| Frescura: que no sea un mensaje viejo reenviado | Nonce, marca de tiempo, contador | El nonce de AES-GCM; la ventana de 30 s del TOTP | 03-02, 03-05 |
| Acuerdo de clave: compartir un secreto sin haberse visto | Intercambio de claves | ECDHE en el handshake de TLS | 03-03, 03-05 |
Tres lecturas importantes de esta tabla:
- Cifrar no da integridad por sí solo. Es el error conceptual más extendido. Un texto cifrado con un modo antiguo puede ser alterado por un atacante de forma controlada sin conocer la clave. Por eso hoy se usa cifrado autenticado, que junta las dos propiedades en una sola operación (03-02).
- Un MAC y una firma no son intercambiables. Ambos dan autenticidad e integridad, pero el MAC usa una clave compartida: si Nimbus y la pasarela comparten la clave, Nimbus podría haber fabricado ese mensaje, así que no sirve como prueba frente a un tercero. La firma, con clave privada, sí (03-03).
- La frescura no la da ninguna de las anteriores. Un mensaje cifrado, íntegro y auténtico de hace tres meses sigue siendo cifrado, íntegro y auténtico hoy. Reenviarlo es un ataque de repetición, y se combate con nonces y ventanas temporales, no con más cifrado.
Y qué no resuelve la criptografía
Igual de importante que lo anterior. La criptografía no:
- Protege frente a quien tiene la clave legítimamente. Si un atacante roba las credenciales de Iván, la API descifra para él con total corrección. Ese es un problema de identidad (02-05) y de autorización, no de criptografía.
- Oculta metadatos. TLS oculta el contenido de la petición, no que tu portátil hable con
nimbusreservas.examplea las 03:14, ni el tamaño aproximado de la respuesta. - Da disponibilidad. Un ransomware usa criptografía perfectamente correcta contra ti. Cifrar no evita que borren.
- Sustituye al control de acceso. Volveremos a esto en 03-07: cifrar una base de datos cuya API expone un IDOR no arregla el IDOR.
- Se arregla sola. Una clave mal guardada convierte AES-256 en un adorno. Ese es el asunto de 03-06.
- Vocabulario preciso: cifrar, codificar, hashear y ofuscar no son lo mismo
El módulo entero depende de usar bien cinco palabras. Empecemos por las básicas:
| Término | Definición |
|---|---|
| Texto en claro (plaintext) | El dato original legible. No tiene por qué ser texto: un PDF, una imagen o una fila de base de datos también lo son |
| Texto cifrado (ciphertext) | El resultado de aplicar el cifrado. Debe ser indistinguible de datos aleatorios |
| Algoritmo (cipher) | El procedimiento matemático. Público, estudiado, estandarizado |
| Clave | El secreto que parametriza el algoritmo. Lo único que el atacante no debe conocer |
| Cifrar / descifrar | Las dos operaciones inversas del algoritmo con la clave |
| Criptoanálisis | El estudio de cómo romper un esquema sin la clave |
Y ahora la distinción que más veces se hace mal en la práctica profesional:
| Operación | ¿Reversible? | ¿Necesita clave? | Para qué sirve | Ejemplo |
|---|---|---|---|---|
| Codificar | Sí, por cualquiera | No | Representar datos en otro alfabeto para transportarlos | Base64, URL-encoding, UTF-8, hexadecimal |
| Cifrar | Sí, solo con la clave | Sí | Confidencialidad | AES-GCM, ChaCha20-Poly1305 |
| Hashear | No (es unidireccional) | No (el HMAC sí) | Integridad, huella, almacenamiento de contraseñas | SHA-256, Argon2id |
| Ofuscar | Sí, con esfuerzo | No | Dificultar la lectura casual. No es seguridad | Minificar JavaScript, invertir una cadena |
Codificar no protege absolutamente nada. Base64 aparece constantemente en tokens, en cabeceras HTTP y en configuraciones, y con una regularidad deprimente alguien concluye que «está encriptado». Vamos a demostrar que no:
import base64
# Un dato sensible ficticio de Nimbus
dato = "paciente=Ana Ruiz; sesion=rehabilitacion rodilla; clinica=CL-014"
# CODIFICAR: convierte a un alfabeto seguro para URLs y cabeceras
codificado = base64.b64encode(dato.encode("utf-8")).decode("ascii")
print("Codificado:", codificado)
# DECODIFICAR: cualquiera, sin ningun secreto, lo revierte
recuperado = base64.b64decode(codificado).decode("utf-8")
print("Recuperado:", recuperado)Salida:
Codificado: cGFjaWVudGU9QW5hIFJ1aXo7IHNlc2lvbj1yZWhhYmlsaXRhY2lvbiByb2RpbGxhOyBjbGluaWNhPUNMLTAxNA==
Recuperado: paciente=Ana Ruiz; sesion=rehabilitacion rodilla; clinica=CL-014Analicemos el código línea a línea, porque el detalle importa:
dato.encode("utf-8")convierte el texto Python en bytes. Base64 opera sobre bytes, no sobre caracteres.base64.b64encode(...)produce la representación en el alfabetoA-Z a-z 0-9 + /, con=de relleno al final. No ha intervenido ninguna clave. El resultado depende únicamente de la entrada.base64.b64decode(...)revierte la operación. No se le pasa ningún secreto porque no existe ninguno.
La conclusión operativa: si ves un valor terminado en == en un log, un ticket o una URL, no está protegido; está transportado. Y si contiene datos personales, ese log es una fuga (esto conecta directamente con la regla de «qué registrar y qué no» de 02-04).
Prueba de bolsillo para distinguirlas. Pregúntate: ¿puedo deshacer esto sin ningún secreto? Si la respuesta es sí, es codificación u ofuscación, y no aporta seguridad. Si necesito un secreto, es cifrado. Si no se puede deshacer de ninguna manera, es un hash.
- Historia como palanca conceptual: César, Vigenère y Enigma
La historia de la criptografía no se estudia aquí por erudición, sino porque cada fracaso histórico dejó una lección que sigue vigente palabra por palabra.
| Esquema | Época | Idea | Por qué cayó | Lección permanente |
|---|---|---|---|---|
| Cifrado César | Roma | Desplazar cada letra N posiciones | Solo hay 25 claves posibles | El espacio de claves debe ser inabarcable |
| Sustitución monoalfabética | Medievo | Cada letra se cambia por otra fija | El análisis de frecuencias lo revienta | El texto cifrado no debe conservar estructura del original |
| Vigenère | S. XVI | Sustitución con clave repetida | La repetición de la clave crea patrones detectables | Nunca reutilices material de clave (regla que reaparecerá con los nonces) |
| Enigma | II GM | Rotores mecánicos, clave diaria | Errores de procedimiento, mensajes predecibles y máquinas capturadas | Se ataca la implementación y el procedimiento, no las matemáticas |
Romper un César en cuatro líneas
Este ejemplo existe solo para que veas con tus propios ojos qué significa «espacio de claves pequeño».
Advertencia explícita. El cifrado César no tiene ningún valor de seguridad. No lo uses jamás para proteger nada, ni siquiera como «capa adicional». Aparece aquí exclusivamente como ilustración conceptual, igual que un dibujo de una cerradura de 1900 en un manual de cerrajería.
def cesar(texto: str, desplazamiento: int) -> str:
"""Desplaza las letras del alfabeto ingles N posiciones. Solo didactico."""
salida = []
for c in texto:
if c.isalpha():
base = ord("A") if c.isupper() else ord("a")
# (ord(c) - base) da la posicion 0..25; sumamos y volvemos a 0..25 con %26
salida.append(chr((ord(c) - base + desplazamiento) % 26 + base))
else:
salida.append(c) # espacios, numeros y signos pasan intactos
return "".join(salida)
interceptado = "Wudvodgr gh od uhvhuyd d odv rfkr"
# FUERZA BRUTA: recorremos TODAS las claves posibles. Son 25.
for clave in range(1, 26):
print(f"clave={clave:2d} -> {cesar(interceptado, -clave)}")Fragmento de la salida:
clave= 1 -> Vtcuncfq fg nc tgugtxc c ncu qejq
clave= 2 -> Usbtmbep ef mb sftfswb b mbt pdip
clave= 3 -> Traslado de la reserva a las ocho
clave= 4 -> Sqzrkzcn cd kz qdrdquz z kzr nbgnQué hay que entender de esto:
- No ha hecho falta ninguna matemática. El bucle prueba las 25 claves en microsegundos y un humano identifica la correcta de un vistazo.
- La operación
% 26es lo que hace que el alfabeto sea circular: tras lazvuelve laa. - Descifrar es cifrar con el desplazamiento negativo:
cesar(texto, -clave). - Un ordenador moderno probaría del orden de mil millones de claves por segundo en un esquema tan simple. Con 25 claves, no hay defensa posible. Con 2^128, como veremos en el apartado 6, la fuerza bruta deja de existir como opción.
La lección permanente de César, la de Vigenère y la de Enigma se resumen en tres frases que valen para 2026: el espacio de claves tiene que ser astronómico; el material de clave no se reutiliza jamás; y el atacante ataca lo más débil, que casi siempre es el procedimiento humano, no el álgebra.
- El principio de Kerckhoffs y por qué la seguridad vive en la clave
En 01-03 conociste el principio de Kerckhoffs como uno de los quince principios de diseño seguro. Aquí es donde cobra todo su sentido:
Un sistema criptográfico debe ser seguro incluso si todo lo relativo a él, salvo la clave, es de dominio público.
Dicho de otro modo: el algoritmo es público; el secreto es la clave, y solo la clave. Lo contrario —confiar en que nadie conozca el algoritmo— se llama seguridad por oscuridad y es una de las peores decisiones posibles en criptografía, por cuatro razones concretas:
| Razón | Explicación |
|---|---|
| El secreto del diseño no se sostiene | Se filtra por ingeniería inversa, por un empleado que se va, por un repositorio, por un binario descargable |
| No se puede rotar un algoritmo | Si se filtra una clave, se genera otra en un segundo. Si se filtra tu algoritmo propietario, hay que reescribir el sistema |
| Nadie lo ha revisado | AES lleva más de dos décadas siendo atacado por miles de criptógrafos. Tu algoritmo lo han mirado dos personas de tu equipo, con prisa |
| Impide la interoperabilidad y la auditoría | Ni el cliente ni el auditor pueden verificar nada |
De aquí sale la regla pedagógica central de este módulo, que vas a leer en las siete lecciones:
Nunca implementes criptografía a mano. Ni el algoritmo, ni el modo, ni el relleno, ni la comparación de etiquetas. Usa librerías establecidas, mantenidas y auditadas —en Python,
cryptography, y los móduloshashlib,hmacysecretsde la biblioteca estándar—. Tu trabajo profesional no es inventar primitivas: es elegir la correcta, configurarla bien y gestionar sus claves. Las tres cosas se hacen mal a menudo, y las tres son suficientes para arruinar un sistema.
Esto no es una recomendación de estilo. La criptografía de calidad industrial exige defenderse de canales laterales, de errores de relleno, de multiplicaciones que tardan distinto según los bits, de generadores de números aleatorios defectuosos. Una implementación «que da el resultado correcto» puede estar filtrando la clave por el tiempo de ejecución sin que ningún test lo detecte.
- Modelos de atacante y tipos de ataque criptográfico
Para decir que algo es seguro hay que decir frente a qué atacante. La criptografía formaliza esto en modelos de atacante, que se distinguen por lo que el adversario puede hacer:
| Modelo | El atacante puede... | Ejemplo real en el mundo de Nimbus |
|---|---|---|
| Solo texto cifrado | Ver textos cifrados | Alguien captura tráfico en la wifi de invitados |
| Texto claro conocido | Ver pares (claro, cifrado) que no eligió | Sabe que toda respuesta empieza por {"reservas":[ |
| Texto claro elegido | Hacer que el sistema cifre lo que él quiera | Crea una reserva con un texto a medida y observa el cifrado resultante |
| Texto cifrado elegido | Hacer que el sistema descifre lo que él quiera y observar la reacción | Envía tokens manipulados y distingue «error de formato» de «error de firma» |
| Atacante activo (MITM) | Además, modificar, reordenar y reenviar mensajes | Un punto de acceso falso en una cafetería, como en 02-02 |
El estándar moderno es exigir seguridad frente al modelo más fuerte. Un algoritmo actual como AES-GCM se considera seguro incluso frente a un atacante que elige textos claros a voluntad; si un esquema solo aguanta el modelo débil, se descarta.
Familias de ataque
| Familia | En qué consiste | Defensa |
|---|---|---|
| Fuerza bruta | Probar todas las claves | Espacio de claves de 128 bits o más (apartado 6) |
| Criptoanálisis | Explotar debilidades matemáticas del algoritmo | Usar solo primitivas estandarizadas y vigentes |
| Diccionario / tablas precalculadas | Probar contraseñas frecuentes o hashes ya calculados | Sal única y funciones lentas (03-04) |
| Canal lateral | Medir tiempo, consumo eléctrico, caché o ruido para deducir la clave | Operaciones en tiempo constante; usar librerías que ya lo hacen |
| Ataque a la implementación | Explotar un fallo del código, no del algoritmo: relleno mal validado, nonce repetido, comparación con == |
Revisión, librerías de alto nivel, cifrado autenticado |
| Ataque al entorno | Robar la clave del disco, del repositorio, de la memoria o de la persona | Gestión de claves (03-06) |
Detengámonos en la de temporización porque es contraintuitiva. Si tu código compara un código de verificación carácter a carácter y devuelve False en cuanto encuentra una diferencia, tarda más cuando acierta más caracteres. Un atacante que mida miles de intentos reconstruye el secreto carácter a carácter, reduciendo un problema imposible a uno trivial. Por eso existe hmac.compare_digest, que verás en detalle en 03-04, y por eso comparar secretos con == está prohibido en todo este curso.
La idea que debes llevarte del apartado. Casi ningún incidente criptográfico real se debe a que alguien rompiera el álgebra. Se debe a claves mal guardadas, nonces repetidos, versiones obsoletas de protocolo, validaciones desactivadas y comparaciones ingenuas. El atacante ataca al implementador, no al algoritmo.
- Qué significa «seguro» hoy: bits de seguridad y tamaños de clave
Un esquema tiene n bits de seguridad cuando el mejor ataque conocido requiere del orden de 2^n operaciones. No es lo mismo que el tamaño de la clave: RSA con clave de 2048 bits ofrece aproximadamente 112 bits de seguridad, porque factorizar es más fácil que probar todas las claves.
| Bits de seguridad | Equivalencias típicas | Estado en 2026 |
|---|---|---|
| 56 | DES | Roto. Se rompe en horas con hardware barato |
| ~63–80 | SHA-1 (resistencia a colisión real) | Roto. Colisiones demostradas públicamente |
| 112 | 3DES, RSA-2048, DH-2048 | Mínimo legado. En retirada |
| 128 | AES-128, RSA-3072, ECC P-256, SHA-256 | El estándar actual. Adecuado para todo uso general |
| 192 | AES-192, ECC P-384 | Alta seguridad, datos de larga vida |
| 256 | AES-256, ECC P-521, SHA-512 | Máximo práctico. Margen a muy largo plazo |
Por qué 2^128 es inalcanzable
Los números grandes no significan nada hasta que se aterrizan. Supongamos una máquina imaginaria capaz de probar mil millones de claves por segundo (10^9), y supongamos que juntamos mil millones de esas máquinas (10^18 claves por segundo, muy por encima de toda la capacidad de cómputo del planeta):
Claves posibles con 128 bits : 2^128 ~= 3,4 x 10^38
Velocidad supuesta : 10^18 claves por segundo
Segundos necesarios : 3,4 x 10^20
Anios necesarios : ~ 1,1 x 10^13 (once billones de anios)Once billones de años frente a los ~13.800 millones de años de edad del universo. Y cada bit adicional duplica ese número: 256 bits no es «el doble de seguro» que 128, es 2^128 veces más.
La conclusión práctica es liberadora: el tamaño de clave no es el problema. Si usas AES-128 o AES-256, la fuerza bruta contra la clave está descartada para siempre con tecnología clásica. Cuando leas que «han roto un cifrado», casi siempre significa una de estas cinco cosas:
- La clave se derivó de una contraseña débil (03-04).
- La clave estaba guardada donde el atacante llegó (03-06).
- Se usó un modo o algoritmo obsoleto (ECB, RC4, MD5).
- Se repitió un nonce o se reutilizó material de clave (03-02).
- Se rompió la implementación: canal lateral, validación desactivada, error de relleno.
¿Y AES-256 frente a AES-128? Elige 256 cuando el dato deba seguir siendo confidencial durante décadas o cuando una norma sectorial lo exija; el coste de rendimiento es pequeño. Para el uso general de Nimbus, cualquiera de los dos está fuera del alcance de cualquier adversario.
- Aleatoriedad criptográfica: el cimiento invisible
Toda la criptografía descansa en poder generar valores que el atacante no pueda predecir: claves, nonces, sales, identificadores de sesión, tokens de recuperación de contraseña. Si esos valores son adivinables, el algoritmo es irrelevante: el atacante no rompe nada, simplemente regenera tu secreto.
Python tiene dos generadores y elegir mal es un fallo de seguridad real:
| Módulo | Tipo | Predecible | Uso correcto |
|---|---|---|---|
random |
PRNG estadístico (Mersenne Twister) | Sí. Con ~624 salidas se reconstruye su estado y se predice todo lo demás | Simulaciones, juegos, muestreo, tests |
secrets |
CSPRNG, alimentado por el sistema operativo | No, por diseño | Todo lo que sea un secreto |
import random
import secrets
# --- INCORRECTO: nunca uses random para material de seguridad ---
token_malo = "".join(random.choice("0123456789abcdef") for _ in range(32))
print("INCORRECTO (predecible):", token_malo)
# --- CORRECTO ---
token_bueno = secrets.token_urlsafe(32) # ~256 bits de entropia, apto para URLs
clave_bytes = secrets.token_bytes(32) # 32 bytes = clave de 256 bits
codigo_sms = secrets.randbelow(1_000_000) # entero uniforme en [0, 999999]
print("CORRECTO token :", token_bueno)
print("CORRECTO clave :", clave_bytes.hex())
print("CORRECTO codigo:", f"{codigo_sms:06d}")Salida (distinta en cada ejecución, por definición):
INCORRECTO (predecible): 8f3a1c0b74e2d95a6b0f4c8137ae52d9
CORRECTO token : 9tHqR2vXbN0sLpKcYfWm3Zj7aQe1UoIdT5gRnVxB4yA
CORRECTO clave : 4c1b9f0a7d6e5382bb44c0f19a7e2d33518c6b09f2a4d7e1c3059b8a6f4e2d10
CORRECTO codigo: 048217Explicación de cada línea:
random.choiceusa el Mersenne Twister, un generador excelente para estadística y catastrófico para seguridad: es determinista y su estado interno se puede reconstruir observando suficientes salidas. Si Nimbus generase con él los tokens de «he olvidado mi contraseña», un atacante que pidiera unos cuantos tokens para su propia cuenta podría predecir el de Sara.secrets.token_urlsafe(32)pide 32 bytes al generador seguro del sistema operativo y los codifica en Base64 seguro para URLs. Ojo: los 32 son bytes de entropía, no caracteres de salida.secrets.token_bytes(32)devuelve bytes crudos: es la forma correcta de generar una clave simétrica de 256 bits desde cero.secrets.randbelow(1_000_000)da un entero uniforme sin el sesgo que introduciría un% 1000000sobre un número aleatorio grande. El formateo:06dconserva los ceros a la izquierda:048217es un código válido.
Caso histórico. En 2013 se descubrió que un fallo en el generador de números aleatorios de determinadas aplicaciones Android hacía que las claves de firma de transacciones se repitieran. Con dos firmas que compartieran ese valor, la clave privada quedaba expuesta con álgebra elemental. El algoritmo era correcto y estaba bien implementado. La aleatoriedad no lo era, y eso bastó.
Regla operativa para Nimbus, sin excepciones: cualquier valor que el atacante no deba adivinar se genera con secrets (o con el equivalente de la plataforma: crypto.getRandomValues en el navegador, crypto/rand en Go). Nunca con random, nunca con la hora, nunca con un contador, nunca con un UUID versión 1.
- El mapa del módulo 3
Antes de entrar en los algoritmos, este es el recorrido y qué pieza aporta cada lección:
flowchart TB
A["03-01 FUNDAMENTOS\nVocabulario, Kerckhoffs,\nbits de seguridad, aleatoriedad"]
A --> B["03-02 SIMETRICA\nUna clave compartida.\nAES-GCM, modos, nonces.\nProblema: distribuir la clave"]
A --> C["03-04 HASH Y MAC\nSHA-256, HMAC,\nArgon2id y contrasenias"]
B --> D["03-03 ASIMETRICA\nPar de claves. RSA, ECC,\nDiffie-Hellman, firma digital"]
C --> D
D --> E["03-05 PROTOCOLOS\nTLS, SSH, VPN.\nCombinar primitivas bien"]
E --> F["03-06 CLAVES Y PKI\nCiclo de vida, certificados,\nCA. El problema dificil"]
F --> G["03-07 APLICACIONES\nCifrado en reposo, campos,\ncopias, firma de artefactos"]
La lógica de la secuencia: lo simétrico resuelve la confidencialidad pero deja abierta la distribución de la clave; lo asimétrico resuelve la distribución pero es lento, lo que obliga a combinarlos; los hashes y los MAC aportan la integridad que el cifrado no da; los protocolos son la manera correcta de ensamblar todo eso; la PKI responde a «¿de quién es esta clave pública?»; y la última lección lo aplica de punta a punta al sistema de Nimbus.
- La criptografía que Nimbus ya usa sin haberla explicado
Cierra la lección un inventario honesto: en dos módulos hemos apoyado defensas sobre criptografía sin explicarla. Esta tabla es la deuda que el módulo 3 viene a pagar.
| Elemento ya visto | Dónde apareció | Qué primitiva hay debajo | Se explica en |
|---|---|---|---|
| HTTPS/TLS en la SPA y la API | Todo el curso | Intercambio ECDHE + certificado + AES-GCM | 03-02, 03-03, 03-05 |
| JWT firmado con RS256 y validado con JWKS | 02-05 | Firma asimétrica RSA sobre un hash | 03-03, 03-07 |
| URLs firmadas de 120 s del bucket de adjuntos (A-05) | 01-04, 02-04 | HMAC sobre método, ruta y caducidad | 03-04, 03-07 |
| HMAC del webhook de la pasarela de pago | 02-02, 02-04 | HMAC-SHA256 con clave compartida | 03-04 |
| TOTP de seis dígitos del MFA | 02-05 | HMAC + contador temporal de 30 s | 03-04, 03-05 |
| Passkeys / FIDO2 | 02-05 | Par de claves por sitio; firma de un reto | 03-03 |
| Hash de contraseñas en la tabla de credenciales | 02-05 | Derivación lenta con sal (Argon2id) | 03-04 |
| DKIM en el correo saliente | 02-03 | Firma asimétrica de las cabeceras | 03-03 |
| Copias 3-2-1-1-0 cifradas e inmutables | 02-04 | Cifrado simétrico + gestión de claves | 03-02, 03-07 |
| Firma de código (lección de SolarWinds) | 02-06 | Firma asimétrica sobre el artefacto | 03-03, 03-07 |
Secreto del .env que permitió la escalada |
02-06 | Ninguna: ahí precisamente falló la gestión | 03-06 |
La última fila es la más elocuente. En el caso de ransomware de Nimbus, ninguna primitiva criptográfica falló: falló que una clave estaba guardada donde no debía. Es la tesis que gobernará 03-06 y que conviene interiorizar desde hoy: la matemática aguanta; la gestión es lo que se rompe.
Errores Comunes y Consejos
Errores conceptuales
- Llamar «encriptado» a algo codificado en Base64. Es el error número uno. Base64 y hexadecimal son transporte, no protección. Aplica la prueba de bolsillo del apartado 2.
- Creer que cifrar aporta integridad. No la aporta salvo que uses cifrado autenticado. Un atacante puede alterar el texto cifrado de forma controlada en modos antiguos.
- Confundir hash con cifrado. «Ciframos las contraseñas» es una frase incorrecta y peligrosa: si se pudieran descifrar, el atacante que roba la clave las tendría todas. Las contraseñas se derivan con una función lenta y unidireccional (03-04).
- Pensar que un algoritmo secreto es más seguro. Kerckhoffs dice justo lo contrario y la experiencia de un siglo lo confirma.
- Obsesionarse con el tamaño de clave. Discutir AES-128 frente a AES-256 mientras la clave vive en un fichero
.envdel repositorio es optimizar la parte que no falla. - Suponer que la criptografía sustituye al control de acceso. Son capas distintas. El IDOR de 01-01 ocurría sobre una conexión TLS impecable.
Errores de implementación
- Usar
randompara material de seguridad. Tokens, sales, nonces, claves: siempresecrets. - Comparar secretos con
==. Abre un canal lateral de temporización. Se usahmac.compare_digest. - Escribir tu propia primitiva «porque es un XOR sencillo». No.
- Copiar el primer fragmento que aparece en un buscador. Buena parte del código criptográfico que circula en foros es de 2011 y usa ECB, MD5 o IV fijos.
Consejos prácticos
- Antes de elegir algoritmo, escribe en una frase qué propiedad necesitas: ¿confidencialidad, integridad, autenticidad, no repudio o frescura? La tabla del apartado 1 te da la primitiva casi sola.
- Escribe también frente a quién proteges. Cifrar el disco de un servidor no defiende de una API comprometida, y saberlo evita comprar tranquilidad falsa.
- Mantén una regla de equipo: el código criptográfico se revisa siempre entre dos personas, aunque el cambio parezca trivial.
- Consulta fuentes vigentes para parámetros y tamaños (NIST SP 800-57, las guías del CCN-CERT y ENISA, la hoja de ruta de OWASP). Los valores concretos caducan; los principios no.
Ejercicios
Ejercicio 1 — Clasificar operaciones
Para cada situación real de Nimbus, indica: (a) qué operación se está aplicando —codificar, cifrar, hashear u ofuscar—; (b) qué propiedad de seguridad aporta realmente; (c) si es adecuada para el fin declarado y, si no lo es, qué debería hacerse.
- El token de sesión que la SPA guarda contiene el correo del usuario en Base64, «para que no se vea a simple vista».
- Rubén envía a un cliente un fichero ZIP protegido con contraseña y le manda la contraseña por el mismo correo.
- Iván guarda en la base de datos el resultado de
sha256(contraseña). - El adjunto de una clínica se sube al bucket A-05 cifrado con AES-GCM y la clave está en un gestor de secretos.
- Lucía publica junto a la copia de seguridad nocturna un fichero
.sha256con la huella del archivo. - El JavaScript de la SPA está minificado y con nombres de variable de una letra, «para que nadie entienda la lógica de precios».
Ejercicio 2 — Auditar un generador de tokens
Este es el código real que Nimbus usa para los enlaces de recuperación de contraseña:
import random, time, base64
def token_recuperacion(email: str) -> str:
random.seed(int(time.time())) # (1)
n = random.randint(100000, 999999) # (2)
crudo = f"{email}:{n}"
return base64.b64encode(crudo.encode()).decode() # (3)Se pide: (a) identifica los tres problemas marcados y explica el ataque concreto que habilita cada uno; (b) estima cuántos intentos necesitaría un atacante que conoce el correo de Sara y el minuto aproximado en que pidió la recuperación; (c) reescribe la función correctamente, explicando cada decisión; (d) indica qué otras dos medidas, ajenas a la generación del token, debería tener este flujo.
Ejercicio 3 — Elegir la primitiva
Para cada requisito de Nimbus, di qué propiedad se necesita, qué primitiva la proporciona y en qué lección del módulo se estudia. No hace falta código.
- Que la consultora externa (A-19) no pueda leer el contenido de las copias que custodia.
- Que Nimbus pueda demostrar ante un cliente, meses después, que fue ese cliente quien canceló una reserva.
- Que la API detecte si un webhook de la pasarela de pago fue fabricado por un tercero.
- Que un atacante que grabe hoy el tráfico cifrado de la app móvil no pueda leerlo si dentro de dos años roba la clave privada del servidor.
- Que un adjunto descargado del bucket no pueda reutilizarse mañana con la misma URL.
- Que, si roban el volcado de la tabla de credenciales, las contraseñas no se puedan recuperar en un plazo útil.
Soluciones
Ejercicio 1
| # | Operación | Qué aporta realmente | ¿Adecuada? |
|---|---|---|---|
| 1 | Codificar | Nada. Cualquiera lo revierte | No. Si el token debe ocultar datos, ciframos; pero lo correcto es que el token sea un identificador opaco aleatorio (secrets) sin datos personales dentro |
| 2 | Cifrar (el ZIP), pero con clave por el mismo canal | Casi nada: quien intercepte el correo tiene ambas cosas | No. La clave debe viajar por un canal distinto (teléfono, con verificación como en 02-03) o usar un enlace con caducidad |
| 3 | Hashear, pero con función rápida y sin sal | Muy poco: rainbow tables y GPU lo revientan | No. Argon2id con sal única por usuario (03-04) |
| 4 | Cifrar con AEAD | Confidencialidad e integridad del adjunto | Sí. Es el patrón correcto (03-02, 03-07) |
| 5 | Hashear | Integridad frente a errores (corrupción, transferencia incompleta) | Parcialmente. No protege de un atacante, que recalcularía el .sha256. Para eso hace falta firma o HMAC (03-04) |
| 6 | Ofuscar | Nada frente a un analista con diez minutos | No. La lógica que debe permanecer secreta vive en el servidor, no en el navegador |
Ejercicio 2
(a) Los tres problemas:
random.seed(int(time.time()))— se siembra el generador con la hora en segundos. El espacio de semillas de un día entero son 86.400 valores. Un atacante que sepa el día reproduce todos los tokens posibles.random.randint(100000, 999999)— dos fallos: usarandom(predecible) y solo hay 900.000 valores posibles, unos 20 bits. Es fuerza bruta trivial incluso sin conocer la semilla.base64.b64encode(...)— el token no está protegido: contiene el correo en claro para cualquiera y, además, revela el formato interno, lo que facilita fabricar candidatos.
(b) Estimación. Si el atacante conoce el correo y el minuto aproximado, tiene ~60 semillas candidatas. Cada semilla determina de forma determinista la primera salida de randint, así que produce ~60 tokens candidatos. Con reintentos automatizados, acierta en segundos. Ni siquiera hace falta explorar los 900.000: la siembra por tiempo destruye toda la entropía.
(c) Versión correcta:
import secrets
import hashlib
from datetime import datetime, timedelta, timezone
def crear_token_recuperacion(usuario_id: int, repo) -> str:
# 1. 32 bytes de un generador CRIPTOGRAFICO -> ~256 bits, imposible de adivinar
token = secrets.token_urlsafe(32)
# 2. En la BD NO se guarda el token, sino su hash: si se filtra la tabla,
# los enlaces pendientes no son utilizables. Aqui SHA-256 basta porque
# el token ya tiene entropia alta (no es una contrasenia humana).
huella = hashlib.sha256(token.encode()).hexdigest()
# 3. Caducidad corta y un solo uso
repo.guardar(
usuario_id=usuario_id,
huella=huella,
caduca=datetime.now(timezone.utc) + timedelta(minutes=15),
usado=False,
)
# 4. El token en claro solo se envia por correo; no se registra en logs
return tokenDecisiones: entropía suficiente y de fuente segura; el token no contiene datos (es opaco, no revela el correo); se almacena hasheado; caduca en 15 minutos; es de un solo uso.
(d) Otras dos medidas: (1) respuesta idéntica exista o no la cuenta —«si el correo está registrado, recibirás un enlace»— para no permitir enumeración de usuarios; (2) límite de tasa por correo y por IP, más registro del evento para alerta (02-04). Una tercera deseable: invalidar todas las sesiones activas al completar el cambio.
Ejercicio 3
| # | Propiedad | Primitiva | Lección |
|---|---|---|---|
| 1 | Confidencialidad | Cifrado simétrico autenticado (AES-GCM) con clave que la consultora no posee | 03-02, 03-07 |
| 2 | No repudio (+ autenticidad e integridad) | Firma digital con clave del cliente; un MAC no valdría | 03-03 |
| 3 | Autenticidad e integridad entre dos partes con clave compartida | HMAC-SHA256 | 03-04 |
| 4 | Forward secrecy | Intercambio efímero ECDHE | 03-03, 03-05 |
| 5 | Frescura / caducidad | HMAC sobre la ruta más una marca de caducidad firmada | 03-04, 03-07 |
| 6 | Unidireccionalidad y coste de cómputo | Argon2id con sal única por usuario | 03-04 |
Conclusión
Has abierto la caja negra. Ahora tienes el mapa que faltaba: cada propiedad de seguridad tiene su primitiva —confidencialidad al cifrado, integridad al hash y al MAC, autenticidad y no repudio a la firma, frescura al nonce, y el acuerdo de clave al intercambio— y también sabes lo que la criptografía no hace: no protege de quien tiene la clave, no oculta metadatos, no da disponibilidad y no sustituye al control de acceso. Distingues con precisión codificar, cifrar, hashear y ofuscar, y tienes una prueba de bolsillo para no volver a confundirlas: ¿puedo deshacerlo sin ningún secreto? Has visto en Base64 que codificar no protege nada y en un César roto en 25 intentos qué significa un espacio de claves insuficiente.
De la historia te llevas tres lecciones que siguen intactas: el espacio de claves debe ser astronómico, el material de clave no se reutiliza jamás —volverá con los nonces en la próxima lección— y el atacante ataca el procedimiento y la implementación, no el álgebra. Sobre esa base has recuperado Kerckhoffs: el algoritmo es público y el secreto es solo la clave, de donde sale la regla que gobierna el módulo entero —nunca implementes criptografía a mano—. Sabes situar a un adversario en su modelo, distinguir un ataque de fuerza bruta de uno de canal lateral, y por qué 2^128 pone la fuerza bruta fuera del universo, lo que traslada el problema real a las cinco causas de siempre: contraseñas débiles, claves mal guardadas, algoritmos obsoletos, nonces repetidos e implementaciones defectuosas. Y has interiorizado el cimiento invisible: secrets, nunca random, porque una mala fuente de aleatoriedad rompe cualquier algoritmo por perfecto que sea.
Por último has puesto números a la deuda: once elementos de Nimbus que llevan dos módulos funcionando gracias a criptografía sin explicar, desde el HMAC del webhook hasta las URLs firmadas de 120 segundos, y una fila final que resume la tesis del módulo: en el ransomware de 02-06 no falló ninguna primitiva, falló dónde vivía una clave.
En Criptografía Simétrica (03-02) empezamos por la primitiva más antigua y más usada: una única clave compartida que cifra y descifra. Verás por qué AES es el estándar, qué es un modo de operación y por qué ECB está prohibido, qué papel juega el nonce y por qué repetirlo es catastrófico, y cómo se cifra correctamente un adjunto de Nimbus con AES-GCM ligándolo a su tenant_id. Y terminarás con el problema que lo simétrico no puede resolver solo —cómo hacer llegar la clave al otro extremo—, que es exactamente lo que motiva la lección siguiente.
Curso de Fundamentos de Seguridad Informática
Módulo 1: Introducción a la Seguridad Informática
- Conceptos Básicos de Seguridad Informática
- Tipos de Amenazas y Vulnerabilidades
- Principios de la Seguridad Informática
- Activos, Superficie de Ataque y Actores de Amenaza
Módulo 2: Ciberseguridad
- Definición y Alcance de la Ciberseguridad
- Tipos de Ataques Cibernéticos
- Ingeniería Social y Phishing
- Medidas de Protección en Ciberseguridad
- Identidad, Autenticación y Control de Acceso
- Casos de Estudio de Incidentes de Ciberseguridad
Módulo 3: Criptografía
- Introducción a la Criptografía
- Criptografía Simétrica
- Criptografía Asimétrica
- Funciones Hash, HMAC y Almacenamiento de Contraseñas
- Protocolos Criptográficos
- Gestión de Claves, Certificados y PKI
- Aplicaciones de la Criptografía
Módulo 4: Gestión de Riesgos y Medidas de Protección
- Evaluación de Riesgos
- Políticas de Seguridad
- Controles de Seguridad
- Riesgo de Terceros y Cadena de Suministro
- Plan de Respuesta a Incidentes
- Recuperación ante Desastres y Continuidad de Negocio
Módulo 5: Herramientas y Técnicas de Seguridad
- Herramientas de Análisis de Vulnerabilidades
- Técnicas de Monitoreo y Detección
- Pruebas de Penetración
- Seguridad en Redes
- Seguridad en Aplicaciones
- Hardening de Sistemas y Seguridad del Endpoint
- Seguridad en la Nube y en Contenedores
Módulo 6: Buenas Prácticas y Normativas
- Buenas Prácticas en Seguridad Informática
- Normativas y Estándares de Seguridad
- Protección de Datos Personales y RGPD en la Práctica
- Cumplimiento y Auditoría
- Formación y Concienciación
- Ética, Aspectos Legales y Divulgación Responsable
