En el módulo anterior construimos la mentalidad: objetivos, entropía, Kerckhoffs y las reglas de oro. Pero la deuda central de MediNube sigue abierta: los historiales de pacientes como Ana Pérez continúan sin cifrar en disco. En este módulo empezamos a saldarla, y lo hacemos por donde empieza casi toda la criptografía práctica: el cifrado simétrico, el que protege la mayor parte de los datos del mundo — discos, bases de datos, tráfico TLS, mensajería. En esta lección conocerás a los dos protagonistas modernos, AES y ChaCha20, entenderás la diferencia entre cifrar en bloque y cifrar en flujo, y darás el primer paso con la librería elegida en 01-04: pyca/cryptography. Cerraremos con un primer cifrado real de bytes en MediNube — y con una advertencia honesta: aún nos falta una pieza (los modos de operación) para hacerlo bien.

Contenido

  1. Qué es el cifrado simétrico
  2. Cifrado en bloque vs cifrado en flujo
  3. AES: el estándar mundial
  4. ChaCha20: el campeón del software
  5. AES vs ChaCha20: cuál elegir
  6. Primer contacto con pyca/cryptography y la capa hazmat
  7. Caso MediNube: primer cifrado de bytes con AES (y por qué aún no basta)

Qué es el cifrado simétrico

El cifrado simétrico es el esquema más antiguo e intuitivo: la misma clave sirve para cifrar y para descifrar. Quien tiene la clave puede hacer ambas cosas; quien no la tiene, ninguna.

             clave K                      clave K
                │                            │
  texto claro ──►  CIFRAR  ──► texto cifrado ──►  DESCIFRAR  ──► texto claro

Formalmente: descifrar(K, cifrar(K, mensaje)) == mensaje. Tres propiedades lo definen:

  • Una sola clave secreta. Toda la seguridad se concentra en ella (Kerckhoffs en estado puro: el algoritmo es público, la clave no).
  • Es muy rápido. Los algoritmos simétricos modernos cifran a velocidades de gigabytes por segundo. Por eso son la herramienta para volúmenes de datos: discos enteros, bases de datos, streams de vídeo.
  • La clave es corta y de máxima entropía. Para nuestro estándar del curso, 256 bits = 32 bytes salidos del CSPRNG (os.urandom(32)), como fijamos en 01-03. No es una contraseña: es ruido puro.

¿Y el punto débil? Que las dos partes necesitan la misma clave, y hacérsela llegar de forma segura a través de una red hostil es un problema nada trivial. Ese problema — el intercambio de claves — tiene soluciones elegantes que veremos en el módulo 4 (criptografía asimétrica y Diffie-Hellman). En este módulo lo aparcamos deliberadamente: nuestro caso de uso es MediNube cifrando sus propios datos en disco, donde el "otro extremo" somos nosotros mismos y no hay clave que transportar.

Cifrado en bloque vs cifrado en flujo

Los algoritmos simétricos se dividen en dos familias según cómo procesan los datos:

  • Un cifrado en bloque (block cipher) transforma bloques de tamaño fijo. AES trabaja con bloques de exactamente 16 bytes: le das 16 bytes, te devuelve 16 bytes cifrados. Es como una máquina troqueladora: piezas de tamaño exacto, una a una.
  • Un cifrado en flujo (stream cipher) genera a partir de la clave un chorro de bytes pseudoaleatorios (el keystream) y lo combina con el mensaje mediante XOR, byte a byte. Puede cifrar 1 byte o 1 GB sin preocuparse de tamaños: es como una manguera.
Característica Cifrado en bloque Cifrado en flujo
Unidad de trabajo Bloque fijo (AES: 16 bytes) Byte a byte (keystream XOR mensaje)
¿Mensajes de cualquier longitud? No directamente: necesita un modo de operación (lección 02-02) y a veces relleno Sí, de forma natural
Longitud del cifrado Múltiplo del bloque (con relleno) Idéntica a la del mensaje
Ejemplo moderno AES ChaCha20
Ejemplos históricos DES, 3DES (obsoletos) RC4 (roto, prohibido en TLS)
Riesgo característico Usar mal el modo de operación Reutilizar el nonce (catástrofe, lección 02-02)

Un matiz que veremos en la próxima lección: la frontera es menos rígida de lo que parece, porque ciertos modos de operación (como CTR) convierten un cifrado en bloque en un cifrado en flujo. Quédate con la idea estructural: AES por sí solo únicamente sabe cifrar 16 bytes exactos; todo lo demás lo aporta el modo.

AES: el estándar mundial

AES (Advanced Encryption Standard) es el cifrado simétrico estándar del planeta desde 2001, y su historia es el mejor ejemplo vivo del principio de Kerckhoffs que estudiamos en 01-04 — el anti-Mifare, el anti-CSS:

  1. En 1997, el NIST estadounidense convocó un concurso público internacional para sustituir al envejecido DES (clave de 56 bits, ya rompible por fuerza bruta).
  2. Se presentaron 15 candidatos de equipos de todo el mundo, con diseño completamente publicado. Durante tres años, los mejores criptoanalistas del planeta — incluidos los equipos rivales — los atacaron públicamente.
  3. En 2000 ganó Rijndael, de los belgas Joan Daemen y Vincent Rijmen. En 2001 se estandarizó como AES.

Veinticinco años de escrutinio mundial después, sigue sin conocerse ningún ataque práctico contra AES bien usado. Compara con el patrón de desastre de los algoritmos secretos de la lección anterior: esta asimetría de revisión es exactamente lo que compra el diseño abierto.

Datos técnicos que debes conocer:

  • Bloque fijo de 128 bits (16 bytes). Siempre, con cualquier tamaño de clave. Internamente, AES organiza esos 16 bytes en una matriz 4×4 y le aplica varias rondas de transformaciones (sustituciones, rotaciones de filas, mezclas de columnas y combinación con subclaves derivadas de la clave principal). No necesitas saber implementarlas — regla de oro nº 1 — pero sí entender la consecuencia: cada bloque de 16 bytes se transforma por completo; cambiar un solo bit de la entrada cambia impredeciblemente los 16 bytes de la salida.
  • Tres tamaños de clave: 128, 192 y 256 bits, con 10, 12 y 14 rondas respectivamente. Los tres son seguros hoy (2^128 ya es inalcanzable, como calculamos en 01-03). En este curso usamos AES-256, el estándar que fijamos: el sobrecoste es mínimo y da margen extra a largo plazo.
  • Aceleración por hardware: AES-NI. Desde ~2010, prácticamente todas las CPU de servidor y escritorio (Intel, AMD, y equivalentes en ARM) incluyen instrucciones nativas que ejecutan rondas de AES directamente en silicio. Resultado: velocidades de varios GB/s por núcleo y, de regalo, resistencia a los ataques de tiempo que vimos en 01-04 (la instrucción tarda siempre lo mismo). Es una de las razones por las que AES domina en servidores.

ChaCha20: el campeón del software

ChaCha20 es el gran cifrado en flujo moderno. Lo diseñó Daniel J. Bernstein (djb, una de las figuras más influyentes de la criptografía actual) en 2008, como evolución de su Salsa20. A diferencia de AES, no salió de un concurso de agencia, sino del escrutinio académico y de su adopción por la industria: hoy es estándar del IETF y lo usan TLS 1.3, WireGuard, SSH y Android.

Sus rasgos distintivos:

  • Clave de 256 bits, siempre. No hay variantes menores — encaja directamente con nuestro estándar del curso.
  • Es un cifrado en flujo: genera keystream en bloques internos de 64 bytes usando solo sumas, XOR y rotaciones sobre enteros de 32 bits — operaciones que cualquier CPU hace de forma nativa y en tiempo constante. Nada de tablas de sustitución en memoria, que es donde los cifrados implementados en software puro suelen filtrar información por la caché (canal lateral, 01-04).
  • Brilla donde no hay AES-NI: móviles de gama baja, dispositivos embebidos/IoT, routers, y cualquier software que deba correr rápido y seguro sin depender del hardware. Google lo impulsó en TLS precisamente para acelerar Android.
  • Necesita un nonce (number used once, número usado una sola vez): un valor público de 12 bytes (96 bits, en la variante IETF) que debe ser distinto en cada cifrado con la misma clave. El nonce es la posición inicial de la "manguera" de keystream: si repites la pareja clave+nonce, dos mensajes se cifran con el mismo keystream, y eso es una catástrofe que demostraremos con código en la próxima lección. De momento, graba la regla: nonce = nunca repetir con la misma clave.

AES vs ChaCha20: cuál elegir

AES-256 ChaCha20
Tipo Bloque (16 bytes) Flujo
Clave 128/192/256 bits (curso: 256) 256 bits
Origen Concurso público NIST (Rijndael, 1998-2001) D. J. Bernstein (2008), estándar IETF
Velocidad con AES-NI Excelente (GB/s por núcleo) Buena
Velocidad sin hardware dedicado Regular, y con riesgo de canal lateral si la implementación usa tablas Excelente y en tiempo constante por diseño
Dónde brilla Servidores y escritorio modernos, cumplimiento normativo (FIPS) Móvil, embebido, software puro, VPNs (WireGuard)
Estado Estándar mundial, sin ataques prácticos Estándar IETF, sin ataques prácticos

Criterio práctico para elegir — y adelanto importante: en la lección 02-03 verás que en el mundo real ninguno de los dos se usa "a pelo", sino en sus construcciones autenticadas (AES-GCM y ChaCha20-Poly1305), pero el criterio de elección es el mismo:

  • Servidor moderno con AES-NI (el caso de MediNube, que corre en cloud): AES-256. Máxima velocidad, máxima compatibilidad normativa.
  • Clientes heterogéneos, móvil, embebido, o software que no controla su hardware: ChaCha20.
  • ¿Te equivocas mucho eligiendo "mal"? No: ambos son seguros. La elección es de rendimiento y ecosistema, no de seguridad. Lo que sí rompe la seguridad es el uso incorrecto (modos, nonces, integridad) — de eso van las dos próximas lecciones.

Para MediNube fijamos por tanto: AES-256 como algoritmo del formato v1 prometido en 01-04.

Primer contacto con pyca/cryptography y la capa hazmat

En 01-04 elegimos pyca/cryptography como librería del curso. Se instala así:

pip install cryptography

Comprobación rápida de que todo está en su sitio:

import cryptography
print(cryptography.__version__)   # p. ej. 45.0.3 — cualquier versión reciente sirve

La librería está dividida en dos niveles, y entender esa división es entender su filosofía:

  • Recetas de alto nivel (cryptography.fernet): decisiones ya tomadas por expertos — algoritmo, modo, integridad, formato. Difíciles de usar mal. Las veremos en 02-03.
  • La capa hazmat (cryptography.hazmat): las primitivas criptográficas directas. El nombre es literal — hazardous materials, materiales peligrosos — y es una advertencia de los propios autores: aquí nadie te impide combinar las piezas de forma insegura. Cifrar con hazmat y hacerlo bien exige saber exactamente qué estás haciendo; por eso este módulo existe.

¿Por qué aprenderemos con hazmat en lugar de quedarnos en las recetas? Porque tu trabajo en MediNube lo exige: los formatos de datos reales (el v1 que diseñaremos, TLS, JWT, ficheros cifrados de terceros) están construidos con estas piezas, y para revisar código heredado — tu día a día en este curso — necesitas reconocer las piezas y sus trampas. La regla profesional queda así: usa la receta de alto nivel cuando te valga; baja a hazmat solo sabiendo lo que haces — que es exactamente lo que este módulo te va a enseñar.

Caso MediNube: primer cifrado de bytes con AES (y por qué aún no basta)

Vamos a cifrar nuestros primeros bytes de verdad. Para ver a AES en estado puro — la troqueladora de bloques, sin nada alrededor — le daremos exactamente un bloque de 16 bytes: un fragmento del historial de Ana Pérez.

import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

# 1. La clave: 32 bytes (256 bits) del CSPRNG, como manda la regla de oro nº 3.
clave = os.urandom(32)

# 2. Exactamente UN bloque: 16 bytes. (Dato ficticio, como todo en MediNube.)
bloque = "GRUPO SANGUIN:0+".encode("utf-8")
assert len(bloque) == 16

# 3. Construimos el cifrador AES.
#    La API nos OBLIGA a indicar un modo; usamos ECB, que significa
#    "aplica el cifrado en bloque directamente, sin nada más".
#    ⚠️ Solo es aceptable porque ciframos UN único bloque suelto:
#    en la próxima lección verás por qué ECB es un desastre para datos reales.
cifrador = Cipher(algorithms.AES(clave), modes.ECB()).encryptor()
cifrado = cifrador.update(bloque) + cifrador.finalize()

print(cifrado.hex())   # 16 bytes de aspecto aleatorio, p. ej. '3ad77bb40d7a3660...'

# 4. Descifrar: misma clave (simétrico), operación inversa.
descifrador = Cipher(algorithms.AES(clave), modes.ECB()).decryptor()
recuperado = descifrador.update(cifrado) + descifrador.finalize()

print(recuperado.decode("utf-8"))   # GRUPO SANGUIN:0+
assert recuperado == bloque

Desglose línea a línea:

  • os.urandom(32) — la clave nace del CSPRNG del sistema (01-03). Nunca de una contraseña escrita a mano ni de random. (¿Y cómo se convierte una contraseña en una clave cuando no hay más remedio? Ese es exactamente el tema de la lección 02-04.)
  • algorithms.AES(clave) — selecciona AES; el tamaño de la clave (32 bytes) determina automáticamente la variante (AES-256).
  • modes.ECB() — el "modo nulo": aplicar la primitiva a cada bloque de 16 bytes, tal cual. La API de hazmat no te deja ni instanciar un Cipher sin declarar un modo: la librería te está diciendo que un cifrado en bloque sin modo no es un sistema de cifrado completo.
  • encryptor() / update() / finalize() — el patrón de trabajo de hazmat: creas el operador, le vas pasando datos con update() (puede llamarse varias veces, útil para ficheros grandes), y cierras con finalize().
  • Todo son bytes, nunca str — la disciplina encode/decode de 01-02 aplica de principio a fin.
  • Observa que el cifrado son también 16 bytes indistinguibles de ruido: sin la clave, no hay nada que "leer" en ellos.

Funciona. Y sin embargo, esto no resuelve la deuda de MediNube, por dos razones que definen el resto del módulo:

  1. Un historial real no mide 16 bytes. ¿Cómo se cifran 40 KB con una máquina que solo procesa piezas de 16 bytes? Ese es el trabajo de los modos de operación, y elegir mal (por ejemplo, el ECB que acabamos de usar) filtra información aunque AES sea perfecto. Es la próxima lección, 02-02.
  2. Cifrar no impide manipular. Nada en este esquema detecta que un atacante altere los bytes cifrados. La solución es el cifrado autenticado (AEAD), lección 02-03.

Como siempre en MediNube: datos ficticios, y recuerda que un despliegue real con datos sanitarios exige revisión de seguridad y compliance (RGPD) por profesionales.

Errores Comunes y Consejos

  • Decir "encriptar". El término normativo en español es cifrar (y descifrar). En este curso, siempre.
  • Generar la clave con random o derivarla "a ojo" de un texto. La clave simétrica son 32 bytes del CSPRNG: os.urandom(32) o secrets.token_bytes(32). Una contraseña no es una clave (lección 02-04).
  • Quedarse en este ejemplo y cifrar datos reales con ECB. El ejemplo de hoy es didáctico y válido solo para un bloque suelto. En la próxima lección verás con tus propios ojos qué filtra ECB con datos reales.
  • Creer que AES-256 "es más seguro" que ChaCha20 (o viceversa). Ambos son seguros; la elección es de plataforma y rendimiento. La inseguridad casi siempre viene del uso, no del algoritmo.
  • Olvidar que la misma clave descifra. Es la definición de simétrico: cualquiera que obtenga la clave lee todo. Dónde y cómo guardar esa clave es un tema serio que abordaremos en el módulo 6; mientras tanto, jamás en el código fuente ni en el repositorio.
  • Confundir el nonce con un secreto. El nonce de ChaCha20 (y los IVs que vienen en la próxima lección) son públicos; su única obligación es no repetirse con la misma clave.

Ejercicios

  1. Clasifica. Para cada escenario de MediNube, razona si encaja mejor AES-256 o ChaCha20: (a) cifrar los historiales en los servidores cloud de MediNube (CPU Intel modernas); (b) una futura app móvil para pacientes que cifre notas localmente, incluyendo terminales Android antiguos; (c) un dispositivo IoT de telemetría médica con un microcontrolador ARM sin aceleración criptográfica.

  2. La troqueladora. Modifica el ejemplo de la lección para intentar cifrar "HISTORIAL DE ANA PEREZ" (22 bytes) con AES en modo ECB. ¿Qué ocurre y por qué? ¿Qué te dice el error sobre lo que falta por aprender?

  3. Ida y vuelta con la clave equivocada. Partiendo del ejemplo de la lección, descifra el bloque con una clave distinta (otro os.urandom(32)). ¿Da error o devuelve algo? ¿Qué devuelve, y qué te dice eso sobre si el cifrado "sabe" cuándo la clave es correcta?

Soluciones

  1. (a) AES-256: servidores modernos con AES-NI, máximo rendimiento — es la elección que fijamos para el formato v1 de MediNube. (b) ChaCha20: parque de dispositivos heterogéneo donde muchos no tienen aceleración AES; en software puro ChaCha20 es más rápido y en tiempo constante por diseño. (c) ChaCha20: es el caso arquetípico — CPU modesta, sin hardware criptográfico, y solo sumas/XOR/rotaciones que cualquier ARM ejecuta bien.

  2. cifrador.finalize() lanza ValueError: The length of the provided data is not a multiple of the block length. AES solo procesa bloques exactos de 16 bytes; 22 bytes no encajan. Falta lo que aporta un modo de operación completo: trocear, encadenar y, cuando toca, rellenar (padding) el último bloque — exactamente el temario de la lección 02-02.

  3. No da error: devuelve 16 bytes de basura pseudoaleatoria (que además probablemente ni siquiera se puedan decodificar como UTF-8). El cifrado en bloque "a pelo" no incluye ninguna verificación: descifrar con cualquier clave siempre "funciona" mecánicamente y produce algo. Detectar que el resultado es válido — y que nadie ha manipulado los datos — requiere autenticación, que es justo lo que añade el cifrado autenticado de la lección 02-03.

Conclusión

Ya tienes a los dos protagonistas del cifrado simétrico moderno y su reparto de papeles: AES-256 (cifrado en bloque de 16 bytes, nacido de un concurso público que es Kerckhoffs en acción, imbatible sobre hardware con AES-NI) y ChaCha20 (cifrado en flujo de Bernstein, clave de 256 bits, nonce irrepetible, el rey del software puro y el móvil). Sabes que la elección entre ellos es de plataforma, no de seguridad, y has escrito tu primer cifrado real con pyca/cryptography, conociendo la frontera entre las recetas de alto nivel y la capa hazmat — materiales peligrosos que este módulo te enseña a manejar. Pero el ejemplo final dejó dos cabos sueltos enormes: AES solo, a pelo, ni cifra mensajes de tamaño real ni detecta manipulaciones. El primer cabo lo atamos ya en la siguiente lección, 02-02: Modos de Operación — donde verás, con una demostración en Python sobre un historial de MediNube, por qué el modo ECB que hoy usamos "de juguete" es una filtración de datos esperando a ocurrir, y qué hacen bien CBC y CTR. Nos vemos allí.

© Copyright 2026. Todos los derechos reservados