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

  1. Qué problema resuelve exactamente la criptografía
  2. Vocabulario preciso: cifrar, codificar, hashear y ofuscar no son lo mismo
  3. Historia como palanca conceptual: César, Vigenère y Enigma
  4. El principio de Kerckhoffs y por qué la seguridad vive en la clave
  5. Modelos de atacante y tipos de ataque criptográfico
  6. Qué significa «seguro» hoy: bits de seguridad y tamaños de clave
  7. Aleatoriedad criptográfica: el cimiento invisible
  8. El mapa del módulo 3
  9. La criptografía que Nimbus ya usa sin haberla explicado

  1. 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.example a 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.

  1. 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 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-014

Analicemos 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 alfabeto A-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.


  1. 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 nbgn

Qué 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 % 26 es lo que hace que el alfabeto sea circular: tras la z vuelve la a.
  • 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.


  1. 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ódulos hashlib, hmac y secrets de 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.


  1. 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.


  1. 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:

  1. La clave se derivó de una contraseña débil (03-04).
  2. La clave estaba guardada donde el atacante llegó (03-06).
  3. Se usó un modo o algoritmo obsoleto (ECB, RC4, MD5).
  4. Se repitió un nonce o se reutilizó material de clave (03-02).
  5. 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.


  1. 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: 048217

Explicación de cada línea:

  • random.choice usa 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 % 1000000 sobre un número aleatorio grande. El formateo :06d conserva los ceros a la izquierda: 048217 es 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.


  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.


  1. 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

  1. 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.
  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.
  3. 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).
  4. Pensar que un algoritmo secreto es más seguro. Kerckhoffs dice justo lo contrario y la experiencia de un siglo lo confirma.
  5. Obsesionarse con el tamaño de clave. Discutir AES-128 frente a AES-256 mientras la clave vive en un fichero .env del repositorio es optimizar la parte que no falla.
  6. 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

  1. Usar random para material de seguridad. Tokens, sales, nonces, claves: siempre secrets.
  2. Comparar secretos con ==. Abre un canal lateral de temporización. Se usa hmac.compare_digest.
  3. Escribir tu propia primitiva «porque es un XOR sencillo». No.
  4. 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.

  1. El token de sesión que la SPA guarda contiene el correo del usuario en Base64, «para que no se vea a simple vista».
  2. Rubén envía a un cliente un fichero ZIP protegido con contraseña y le manda la contraseña por el mismo correo.
  3. Iván guarda en la base de datos el resultado de sha256(contraseña).
  4. 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.
  5. Lucía publica junto a la copia de seguridad nocturna un fichero .sha256 con la huella del archivo.
  6. 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.

  1. Que la consultora externa (A-19) no pueda leer el contenido de las copias que custodia.
  2. Que Nimbus pueda demostrar ante un cliente, meses después, que fue ese cliente quien canceló una reserva.
  3. Que la API detecte si un webhook de la pasarela de pago fue fabricado por un tercero.
  4. 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.
  5. Que un adjunto descargado del bucket no pueda reutilizarse mañana con la misma URL.
  6. 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:

  1. 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.
  2. random.randint(100000, 999999) — dos fallos: usa random (predecible) y solo hay 900.000 valores posibles, unos 20 bits. Es fuerza bruta trivial incluso sin conocer la semilla.
  3. 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 token

Decisiones: 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

Módulo 2: Ciberseguridad

Módulo 3: Criptografía

Módulo 4: Gestión de Riesgos y Medidas de Protección

Módulo 5: Herramientas y Técnicas de Seguridad

Módulo 6: Buenas Prácticas y Normativas

Módulo 7: Proyecto Final

© Copyright 2026. Todos los derechos reservados