Cerramos el módulo de fundamentos con la lección más importante de todas para tu práctica profesional: la que define cómo se piensa en criptografía. En 1883, el criptógrafo Auguste Kerckhoffs formuló un principio que sigue gobernando la disciplina: un sistema criptográfico debe ser seguro aunque el adversario conozca todo sobre él, excepto la clave. De este principio se derivan consecuencias muy prácticas: por qué jamás debes inventar tu propio cifrado, por qué la "seguridad por oscuridad" fracasa como única defensa (con cadáveres ilustres: DVD CSS, GSM A5, tarjetas Mifare), por qué hasta una comparación de cadenas puede filtrar un secreto (ataques de canal lateral), y qué librerías merecen tu confianza. La lección termina con las reglas de oro que iremos reforzando durante todo el curso.

Contenido

  1. El principio de Kerckhoffs: enunciado e implicaciones
  2. Seguridad por oscuridad: por qué falla como única defensa
  3. "No inventes tu propia criptografía": tres desastres históricos
  4. Ataques de canal lateral: cuando la implementación traiciona
  5. Caso MediNube: comparar secretos con secrets.compare_digest
  6. Criptoagilidad: diseñar para poder cambiar
  7. Librerías serias frente a primitivas a mano
  8. Las reglas de oro del curso

El principio de Kerckhoffs: enunciado e implicaciones

El enunciado moderno del principio de Kerckhoffs:

Un sistema criptográfico debe ser seguro incluso si todo sobre el sistema —salvo la clave— es de conocimiento público.

Claude Shannon, padre de la teoría de la información, lo reformuló décadas después de forma aún más tajante: "el enemigo conoce el sistema" (máxima de Shannon). Diseña siempre bajo esa suposición. ¿Por qué es una buena idea concentrar todo el secreto en la clave, en lugar de repartirlo también por el algoritmo?

  • Las claves son fáciles de proteger y reemplazar; los algoritmos, no. Una clave son 32 bytes: se guarda en un gestor de secretos, se rota en minutos si se filtra. Un algoritmo está en cada binario desplegado, en cada copia del código, en la memoria de cada desarrollador que ha trabajado en él. Cuando "se filtra el algoritmo" (y siempre acaba pasando: ingeniería inversa, empleados que se van, repositorios expuestos), no puedes "rotarlo" sin reconstruir el sistema entero.
  • Solo el escrutinio público da confianza. Un algoritmo secreto lo han revisado, con suerte, cinco personas de tu empresa. AES lleva desde 1998 siendo atacado por los mejores criptoanalistas del planeta, con reputación y carreras en juego por encontrarle grietas — y sigue en pie. La seguridad de un algoritmo no se demuestra: se sobrevive, y solo se sobrevive a los ataques que efectivamente se intentan.
  • Separa la seguridad del despliegue. Si la seguridad exige que el atacante no sepa "cómo funciona", entonces cada empleado, contratista, backup del repositorio y volcado de memoria es un punto de fallo catastrófico e irrecuperable. Si solo la clave es secreta, la superficie a proteger es mínima y está bien definida.

Fíjate en que el principio no dice que debas publicar tu diseño, ni que el secreto adicional estorbe. Dice que la seguridad no puede depender de ese secreto. Es una prueba mental que puedes aplicar a cualquier sistema: "Si mañana el atacante lee todo nuestro código y documentación, ¿sigue sin poder acceder a los datos?" Si la respuesta es no, el sistema está roto hoy; solo que aún no lo sabes.

Seguridad por oscuridad: por qué falla como única defensa

Seguridad por oscuridad es confiar en que el atacante no descubra cómo funciona algo: el algoritmo casero, el endpoint sin documentar, el parámetro secreto, el puerto no estándar. Falla como defensa principal por tres razones estructurales:

  1. La oscuridad no se puede medir. De una clave de 128 bits sabemos exactamente cuánto cuesta romperla. ¿Cuánto "cuesta" descubrir tu algoritmo casero? Quizá una tarde con un descompilador. No puedes razonar sobre lo que no puedes cuantificar.
  2. La oscuridad no se puede recuperar. Una clave filtrada se rota. Un secreto de diseño filtrado está filtrado para siempre.
  3. La oscuridad se erosiona sola. Cada nuevo empleado, cada proveedor, cada análisis de tráfico, cada binario distribuido reduce el secreto. El tiempo juega en contra.

Matiz profesional importante: la oscuridad como capa adicional sobre un diseño sólido no es mala (no publicar la topología interna de tu red añade fricción real al atacante y no cuesta nada). Lo indefendible es que sea la única barrera, o que sustituya a una barrera real. En la revisión de código de la lección 01-02 ya viste una seguridad por oscuridad de manual: Base64 como "protección" funciona exactamente hasta que alguien mira.

"No inventes tu propia criptografía": tres desastres históricos

La consecuencia práctica número uno del principio de Kerckhoffs para un desarrollador: no diseñes ni implementes tus propios algoritmos criptográficos para producción. No es una cuestión de humildad, sino de estadística: los algoritmos secretos y propietarios tienen un historial de derrotas casi perfecto. Tres casos famosos, los tres con el mismo patrón:

DVD CSS (Content Scramble System)

El sistema de cifrado de los DVD de vídeo (1996) usaba un algoritmo secreto con claves de 40 bits, protegido por acuerdos de confidencialidad. En 1999 fue objeto de ingeniería inversa (a partir de un reproductor de software) y se publicó DeCSS. El análisis reveló que el diseño era tan débil que el ataque efectivo costaba ~2^25 operaciones — segundos en un PC de la época. Rotó para siempre: no se puede "actualizar el algoritmo" de millones de discos y reproductores ya fabricados.

GSM A5/1 y A5/2

Los algoritmos de cifrado de las llamadas GSM (años 80-90) se diseñaron en secreto por comités del sector. Se filtraron y reconstruyeron por ingeniería inversa en los 90, y el criptoanálisis público los demolió: A5/2 (la variante de exportación, debilitada a propósito) se rompe en tiempo real; para A5/1 se publicaron tablas precalculadas que permiten descifrar llamadas con hardware doméstico. Miles de millones de teléfonos quedaron expuestos durante décadas por la inercia del despliegue.

Mifare Classic

La tarjeta sin contacto más vendida del mundo (transporte público, accesos a edificios) usaba CRYPTO1, un cifrado propietario y secreto con claves de 48 bits. En 2008, investigadores reconstruyeron el algoritmo analizando el chip al microscopio y encontraron debilidades estructurales: la clave se recupera en segundos con un lector de bolsillo. Sistemas de transporte y control de acceso de ciudades enteras tuvieron que migrarse.

Caso Secreto del diseño Cómo se descubrió Resultado Coste del error
DVD CSS Algoritmo + claves 40 bits Ingeniería inversa de un reproductor (1999) Roto en segundos Irreparable: hardware ya vendido
GSM A5/1-A5/2 Algoritmos de comité, secretos Filtraciones + ingeniería inversa (años 90) Descifrado de llamadas práctico Décadas de comunicaciones expuestas
Mifare Classic CRYPTO1, propietario, 48 bits Análisis del silicio del chip (2008) Clave en segundos Migración de ciudades enteras

El patrón se repite, punto por punto: (1) el diseño se mantiene en secreto → (2) se le hace ingeniería inversa, siempre → (3) al quedar expuesto, el criptoanálisis público encuentra en meses las debilidades que el diseño cerrado incubó durante años → (4) el coste de reemplazo es brutal porque el algoritmo está incrustado en el despliegue. Compáralo con AES: diseño publicado, concurso internacional (NIST, 1997-2000), quince finalistas destripándose mutuamente, y veinticinco años de escrutinio mundial posterior. Esa asimetría de revisión es toda la diferencia.

Y la versión cotidiana del error no es diseñar un cifrado nuevo, sino cosas como: "XOR con una contraseña repetida", "AES pero implementado a mano leyendo la especificación", "un hash casero mezclando MD5 dos veces". Todo eso es inventar criptografía propia. La regla incluye implementar primitivas conocidas: la especificación de AES cabe en unas páginas, pero implementarla sin fugas de canal lateral (siguiente apartado) es trabajo de especialistas.

Ataques de canal lateral: cuando la implementación traiciona

Hasta ahora hemos hablado de romper las matemáticas. Pero hay una familia de ataques que ignora las matemáticas y observa la ejecución física del sistema: cuánto tarda, cuánta energía consume, qué emite electromagnéticamente, cómo se comporta la caché de la CPU. Son los ataques de canal lateral (side-channel). Aquí solo necesitas el concepto y su ejemplo más accesible: el ataque de tiempo (timing attack) sobre una comparación no constante.

Piensa en cómo compara cadenas el operador == de Python: recorre los caracteres y se detiene en la primera diferencia. Eso significa que comparar "AAAA" == "BXXX" tarda un poquito menos que "AAAA" == "ABXX", porque la primera comparación falla en el carácter 1 y la segunda en el 2. La diferencia son nanosegundos... pero es medible estadísticamente repitiendo la petición miles de veces, especialmente en una red local o entre contenedores.

El ataque resultante contra un endpoint que compara un token secreto con ==:

  1. El atacante prueba A000..., B000..., C000... y mide tiempos. La variante cuyo primer carácter coincide con el secreto tarda sistemáticamente un pelín más (la comparación avanza un carácter más antes de fallar).
  2. Fijado el primer carácter, repite con el segundo. Y así sucesivamente.
  3. Resultado: en lugar de adivinar el token entero (imposible: 2^128), lo reconstruye carácter a carácter — de coste exponencial a coste lineal. Los 128 bits de entropía de la lección anterior quedan anulados por la implementación.

Este es el mensaje profundo del apartado: la seguridad teórica puede evaporarse en la implementación. Y es otra cara de "no inventes tu propia criptografía": las librerías serias implementan sus operaciones en tiempo constante (el tiempo no depende de los datos secretos), un arte lleno de trampas (los compiladores "optimizan" eliminando el tiempo constante, las cachés filtran accesos...). Otros canales laterales —consumo eléctrico, emisiones electromagnéticas, uso de caché— siguen la misma lógica y afectan sobre todo a hardware (tarjetas, HSMs); para ti, el canal que debes vigilar en el día a día es el tiempo.

Caso MediNube: comparar secretos con secrets.compare_digest

Las clínicas integran sus sistemas con MediNube a través de una API que autentica cada petición con una clave de API. En el código heredado encuentras esto:

# ─── VULNERABLE a un ataque de tiempo ───
CLAVES_API = {"clinica-sol": "tCq81vX2mZk4Nw9rLpAeYhSdF0uGjB3o"}

def autenticar_MAL(clinica: str, clave_recibida: str) -> bool:
    clave_correcta = CLAVES_API.get(clinica, "")
    return clave_recibida == clave_correcta        # == se detiene en la 1ª diferencia

El == compara carácter a carácter y corta al primer fallo: el tiempo de respuesta del endpoint depende de cuántos caracteres iniciales acertó el atacante. Con paciencia y estadística, la clave de la Clínica Sol se extrae carácter a carácter, como acabamos de ver. La corrección es una línea:

# ─── CORRECTO: comparación en tiempo constante ───
import secrets

def autenticar(clinica: str, clave_recibida: str) -> bool:
    clave_correcta = CLAVES_API.get(clinica, "")
    return secrets.compare_digest(clave_recibida, clave_correcta)

Desglose:

  • secrets.compare_digest(a, b) compara las dos cadenas completas siempre, tarde lo que tarde: el tiempo no revela en qué posición difieren. (Acepta dos str ASCII o dos bytes.)
  • El resto del razonamiento de la lección 01-03 sigue aplicando: la clave debe haber nacido de secrets.token_urlsafe(32), y el endpoint debe además limitar intentos y registrar fallos — defensa en profundidad.
  • Usa compare_digest siempre que compares un valor secreto recibido del exterior: claves de API, tokens de recuperación, firmas de webhooks (lo reutilizaremos con HMAC en el módulo 3). Para comparaciones sin secretos (un if estado == "activo"), el == normal es perfectamente correcto.

Nota: guardar las claves de API en un diccionario en el código, como en el ejemplo, es otra deuda de seguridad de MediNube — la gestión de secretos se aborda de lleno en el módulo 6. Y como siempre: un despliegue sanitario real exige revisión por profesionales de seguridad y compliance (RGPD).

Criptoagilidad: diseñar para poder cambiar

Los algoritmos criptográficos envejecen: MD5 y SHA-1 cayeron, DES quedó pequeño, y la computación cuántica obligará a migraciones masivas (módulo 6). La pregunta no es si tendrás que cambiar de algoritmo durante la vida de tu sistema, sino cuánto te costará. Criptoagilidad es diseñar para que ese cambio sea barato:

  • Etiqueta los datos con su algoritmo y versión. Cada dato cifrado o hasheado debe llevar metadatos de con qué se produjo (v1 = AES-256-GCM, v2 = lo que venga). Así conviven formatos durante una migración y el código sabe cómo tratar cada registro. Los formatos serios ya lo hacen: lo verás en los hashes de contraseñas ($argon2id$..., módulo 3) y en las cabeceras de JWT (módulo 6).
  • Centraliza la criptografía. Un único módulo/servicio interno (medinube.crypto) que el resto de la aplicación consume. Cambiar de algoritmo = cambiar un sitio, no cuarenta.
  • No sobredimensiones por miedo, pero deja holgura donde es gratis: campos de base de datos que admitan hashes más largos, protocolos con campo de versión desde el día uno.
  • El caso Mifare/GSM/CSS también es una lección de falta de criptoagilidad: su mayor coste no fue el criptoanálisis, sino que el algoritmo roto estaba fundido en hardware imposible de actualizar.

Librerías serias frente a primitivas a mano

Si no hay que implementar primitivas a mano, ¿qué se usa? Librerías mantenidas por especialistas, auditadas y con APIs difíciles de usar mal:

Ecosistema Librería recomendada Notas
Python cryptography (proyecto pyca) El estándar de facto; la usaremos desde el módulo 2. Expone recetas de alto nivel (Fernet) y primitivas (hazmat, "materiales peligrosos" — el nombre ya avisa)
Python (alternativa) PyNaCl (libsodium) API minimalista y opinada: pocas opciones, todas buenas
Multilenguaje / C libsodium Referencia moderna de "criptografía difícil de usar mal"
JavaScript/Node WebCrypto (crypto.subtle), libsodium.js Nativo del navegador y de Node
Java/JVM JCA/JCE con proveedores modernos, Tink (Google) Tink está diseñado explícitamente anti-mal-uso
Go crypto/* de la librería estándar Mantenida por el propio equipo de Go
.NET System.Security.Cryptography Nativa de la plataforma

Criterios para fiarte de una librería criptográfica (aplícalos a cualquier lenguaje):

  • Mantenimiento activo y equipo identificable; historial público de gestión de vulnerabilidades (CVEs corregidos con rapidez).
  • Auditorías externas publicadas, o uso masivo bajo escrutinio real.
  • API de alto nivel que elige por ti los parámetros seguros (la mejor API es la que no te deja elegir mal). Desconfía de librerías que te piden decidir todo.
  • Implementaciones en tiempo constante documentadas.

Y el antipatrón contrario: el fragmento de Stack Overflow con criptografía casera, el paquete crypto-utils-fast con 200 descargas, o "ya lo hago yo con XOR que es más rápido". En MediNube, la decisión de arquitectura que tomamos hoy es: Python + cryptography (pyca) para todo el curso, con secrets/os.urandom para la aleatoriedad.

Las reglas de oro del curso

Todo el módulo 1 se condensa en estas reglas. Las iremos reforzando (y ampliando con matices) durante el resto del curso; considéralas el contrato del criptógrafo aplicado:

  1. No inventes ni implementes tu propia criptografía. Usa algoritmos estándar a través de librerías serias (cryptography, libsodium). Esto incluye no "mejorar" ni combinar primitivas creativamente.
  2. Asume que el enemigo conoce el sistema (Kerckhoffs/Shannon). Toda la seguridad debe residir en las claves; la oscuridad, como mucho, es una capa extra, jamás la defensa.
  3. Toda aleatoriedad de seguridad sale del CSPRNG. En Python: secrets u os.urandom. Nunca random, nunca timestamps, nunca ingredientes caseros.
  4. Codificar no es cifrar. Base64, hex y compañía son transporte. Si no hay clave secreta, no hay confidencialidad.
  5. La entropía manda. Una clave vale los bits de entropía reales de su origen, no su longitud aparente. Mínimo serio hoy: 128 bits; nuestro estándar: 256.
  6. Compara secretos en tiempo constante (secrets.compare_digest). La implementación puede traicionar a las matemáticas.
  7. Identifica el objetivo antes que la herramienta. ¿Confidencialidad, integridad, autenticidad, no repudio? La respuesta elige el mecanismo, no al revés.
  8. Diseña con criptoagilidad. Versiona formatos, centraliza la criptografía, prepárate para migrar de algoritmo.
  9. La criptografía no sustituye al resto de la seguridad. Control de acceso, límites de intentos, auditoría, gestión de secretos y revisión profesional (más aún con datos sanitarios y RGPD) siguen siendo imprescindibles.

Errores Comunes y Consejos

  • Citar mal Kerckhoffs: "hay que publicar el algoritmo". El principio no obliga a publicar nada; exige que la seguridad no dependa del secreto del diseño. Puedes mantener tu arquitectura confidencial — pero tu sistema debe resistir como si fuera pública.
  • El extremo contrario: despreciar toda oscuridad. No exponer versiones de software o rutas internas es higiene razonable. El pecado es contarla como control de seguridad en lugar de como fricción adicional.
  • "Uso AES, así que cumplo la regla 1". Usar un algoritmo estándar mal (modo inadecuado, IV repetido, clave derivada de una contraseña floja) también es romper la regla. Por eso los módulos 2 y 6 dedican tanto espacio al cómo.
  • Sprinkle-crypto: llamadas criptográficas dispersas por todo el código, cada una con sus parámetros. Cuando toque migrar (regla 8), será arqueología. Centraliza desde el primer día.
  • Cambiar == por compare_digest en todas partes "por si acaso". Solo tiene sentido con valores secretos comparados contra entrada externa; usarlo para lógica normal solo oscurece el código. Entiende el porqué de cada regla, no el cargo-cult.
  • Consejo: convierte las reglas de oro en checklist de revisión de código de tu equipo. La mayoría de vulnerabilidades criptográficas reales (lo verás en la lección 06-04) son violaciones directas de una de estas nueve líneas.

Ejercicios

Ejercicio 1. Un proveedor ofrece a MediNube un módulo de "cifrado ultraseguro de historiales" con este argumento comercial: "Nuestro algoritmo es de diseño propio y confidencial, lo que añade una capa de seguridad: al no conocerlo, ningún hacker puede atacarlo. Ha superado nuestras pruebas internas de calidad durante 5 años sin ser roto." Redacta una respuesta técnica (4-6 frases) señalando los fallos del argumento, citando el principio de Kerckhoffs y al menos un caso histórico.

Ejercicio 2. Este endpoint de MediNube verifica el token que un webhook externo envía en una cabecera. Señala las dos violaciones de las reglas de oro y corrígelas:

import random, string

TOKEN_WEBHOOK = "".join(random.choice(string.ascii_letters) for _ in range(32))

def verificar_webhook(token_recibido: str) -> bool:
    return token_recibido == TOKEN_WEBHOOK

Ejercicio 3. Para cada decisión, indica si es un uso aceptable de la oscuridad como capa adicional o una violación del principio de Kerckhoffs, y por qué: (a) MediNube no documenta públicamente las rutas internas de su API de administración; (b) los historiales se "protegen" con un XOR cuya máscara está en el código fuente, y se confía en que nadie descompile la aplicación; (c) el equipo decide no publicar en el blog corporativo qué algoritmo de cifrado usan, aunque es AES-256-GCM con claves bien gestionadas; (d) el token de sesión se llama X-Custom-Data en lugar de Authorization "para despistar", y por lo demás es un token aleatorio de 256 bits bien verificado.

Soluciones

Solución 1. Respuesta modelo: "El argumento invierte el principio de Kerckhoffs: un sistema debe ser seguro suponiendo que el atacante conoce el algoritmo, porque el secreto del diseño siempre acaba perdiéndose (ingeniería inversa, filtraciones) y, a diferencia de una clave, no puede rotarse. Que no lo conozcan no impide atacarlo: DVD CSS, GSM A5 y Mifare Classic eran algoritmos propietarios y secretos, y los tres fueron reconstruidos por ingeniería inversa y rotos en cuanto recibieron análisis público. 'Cinco años sin romperse' en pruebas internas solo significa que nadie cualificado lo ha intentado: la confianza criptográfica nace del escrutinio público masivo, como el que ha recibido AES desde 1998. Solicitamos que el producto use algoritmos estándar (p. ej. AES-256-GCM) mediante librerías auditadas; un diseño confidencial es, en sí mismo, motivo de rechazo."

Solución 2. Violaciones: (1) regla 3 — el token se genera con random, un PRNG predecible (además, con solo letras ASCII: log2(52) ≈ 5,7 bits/carácter, y lo grave es la previsibilidad de Mersenne Twister); (2) regla 6 — la comparación con == no es de tiempo constante y permite extraer el token carácter a carácter con un ataque de tiempo. Corrección:

import secrets

TOKEN_WEBHOOK = secrets.token_urlsafe(32)   # 256 bits del CSPRNG

def verificar_webhook(token_recibido: str) -> bool:
    return secrets.compare_digest(token_recibido, TOKEN_WEBHOOK)

(Bonus: en producción el token no viviría en una variable del código sino en un gestor de secretos — módulo 6 —, y el mecanismo robusto para webhooks es firmar el cuerpo con HMAC, que construiremos en el módulo 3.)

Solución 3. (a) Aceptable: fricción extra sin coste; la seguridad real la dan la autenticación y autorización del endpoint, no el secreto de la ruta. (b) Violación clara: la única barrera es que nadie mire el código — no hay clave secreta gestionada como tal, y XOR con máscara fija ni siquiera es un cifrado serio; rompe las reglas 1, 2 y 4. (c) Aceptable: no publicar detalles es legítimo; el sistema resiste aunque se sepan (AES bien usado no depende del secreto del "qué"). Es exactamente la postura correcta ante Kerckhoffs: podría ser público sin riesgo. (d) Aceptable pero inútil y con coste: la seguridad la da el token de 256 bits bien verificado, no el nombre de la cabecera; el "despiste" apenas frena a nadie y complica la integración y el mantenimiento. No viola Kerckhoffs (la seguridad no depende del truco), pero es oscuridad de valor casi nulo.

Conclusión

Con esta lección se cierra el módulo de fundamentos, y ya tienes la mentalidad completa del criptógrafo aplicado. Sabes qué persigue la criptografía (confidencialidad, integridad, autenticidad y no repudio — lección 01-01), distingues con rigor codificar, ofuscar y cifrar (01-02), sabes de dónde sale la imprevisibilidad que lo sostiene todo y cómo generarla correctamente en Python (01-03), y ahora tienes el principio que gobierna el diseño: la seguridad reside en la clave, nunca en el secreto del algoritmo, con sus corolarios — no inventar criptografía propia, desconfiar de la oscuridad como defensa, vigilar los canales laterales con comparaciones en tiempo constante, diseñar con criptoagilidad y apoyarse en librerías serias. Las nueve reglas de oro condensan el módulo entero; vuelve a ellas cada vez que dudes, porque el resto del curso no hará más que darles profundidad.

En MediNube, el balance de este módulo es un buen punto de partida: hemos desterrado el Base64-como-protección, los tokens de recuperación nacen ya de secrets.token_urlsafe(32) y las claves de API se comparan en tiempo constante. Pero la necesidad central sigue sin resolver: los historiales de pacientes como Ana Pérez continúan sin cifrar en disco. En el Módulo 2: Criptografía Simétrica empezamos a construir la solución de verdad — la librería cryptography entra en escena y, con ella, los algoritmos que protegen la mayor parte de los datos del mundo: AES y ChaCha20, sus modos de operación, el cifrado autenticado (AEAD) y la derivación de claves. Nos vemos en la lección 02-01.

© Copyright 2026. Todos los derechos reservados