Cifrado en reposo vs. en tránsito — ¿qué protege realmente cada uno, y cuál es el modo de falla si solo tienes uno?
TLS y el cifrado en reposo defienden contra dos atacantes completamente distintos, y tener solo uno deja abierto de par en par el ataque favorito del otro.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
Cifrado en reposo vs. en tránsito — ¿qué protege realmente cada uno, y cuál es el modo de falla si solo tienes uno?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#Dos atacantes distintos, dos defensas distintas
El cifrado en tránsito (TLS) protege los datos mientras se mueven a través de una red que no controlas por completo — entre un navegador y tu servidor, entre dos de tus propios servicios a través de la red interna de un proveedor de nube, entre tu aplicación y una API de terceros. El atacante contra el que defiende está en el cable: alguien capturando paquetes en un Wi-Fi público, un router comprometido, un balanceador de carga mal configurado en algún punto intermedio, un nodo de salida malicioso.
El cifrado en reposo protege los datos mientras están quietos — en un disco, en un archivo de base de datos, en un respaldo, en un snapshot. El atacante contra el que defiende obtuvo acceso al almacenamiento en lugar de a la red: una laptop robada, un bucket de almacenamiento mal configurado, un disco duro dado de baja que no fue borrado, un respaldo de base de datos que se filtró, un insider con acceso al sistema de archivos pero sin credenciales válidas de la aplicación.
Estos son modelos de amenaza genuinamente distintos, y ninguno sustituye al otro:
| Cifrado en tránsito (TLS) | Cifrado en reposo | |
|---|---|---|
| Protege los datos | Mientras se mueven por una red | Mientras están almacenados en disco/respaldo |
| Defiende contra | Espionaje de red, MITM | Almacenamiento robado, respaldos filtrados, acceso al sistema de archivos |
| No protege contra | Una base de datos vulnerada, un archivo de respaldo robado | Captura de tráfico, un proxy MITM |
| Mecanismo típico | Handshake TLS/HTTPS, certificados | Cifrado de disco/volumen, cifrado a nivel de campo, claves envueltas por un KMS |
#El modo de falla de "solo TLS"
Si cifras cada conexión pero almacenas los datos en texto plano, una brecha en la base de datos — inyección SQL, un respaldo filtrado, un disco robado — le entrega al atacante todo en texto claro sin trabajo adicional. Esta es la combinación real más común: los equipos habilitan HTTPS en todas partes (correctamente tratado como algo básico) y se detienen ahí, dejando el almacén de datos real como un volcado de texto plano sin cifrar en el momento en que alguien traspasa el perímetro de red. Cada gran brecha que involucra "millones de contraseñas o números de identificación en texto plano encontrados en una base de datos filtrada" es este modo de falla.
#El modo de falla de "solo en reposo"
Menos común en la práctica, pero igual de real: si la propia base de datos está completamente cifrada en reposo pero la conexión entre tu aplicación y esa base de datos (o entre tu API y sus clientes) es HTTP plano, cualquiera posicionado en la ruta de red lee cada credencial y cada fila mientras vuela, sin importar el cifrado en reposo — el dato ya estaba descifrado en el momento en que salió del almacenamiento para ser enviado por el cable. Esto aparece en configuraciones de red interna donde los equipos asumen "está dentro de nuestra red privada, así que es seguro" y se saltan TLS entre microservicios, una suposición peligrosa en el momento en que un atacante consigue un solo punto de apoyo dentro de esa red.
#Configuración real: TLS en tránsito
Una configuración TLS mínima y vigente para un servicio detrás de Nginx — los detalles que realmente importan en una auditoría real son el piso de protocolo y la lista de cifrados, no solo "HTTPS está activado":
server {listen 443 ssl;server_name api.example.com;ssl_certificate /etc/ssl/certs/api.example.com.fullchain.pem;ssl_certificate_key /etc/ssl/private/api.example.com.key;# Rechaza directamente las versiones de protocolo viejas y rotasssl_protocols TLSv1.2 TLSv1.3;# Prefiere cifrados AEAD con forward secrecy; descarta RC4, 3DES y el intercambio de claves RSA estáticossl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;ssl_prefer_server_ciphers on;# Le dice a los navegadores que nunca vuelvan a intentar HTTP plano para este hostadd_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;location / {proxy_pass http://127.0.0.1:8080;}}server {listen 80;server_name api.example.com;return 301 https://$host$request_uri; # redirige cualquier solicitud plana perdida}
Restringir a TLS 1.2/1.3 descarta las versiones de protocolo con debilidades criptográficas conocidas (POODLE contra SSLv3, problemas cercanos a BEAST/CRIME en TLS 1.0 antiguo). La lista de cifrados se limita específicamente al intercambio de claves ECDHE por el forward secrecy — si una clave privada llega a comprometerse en el futuro, el tráfico capturado previamente sigue sin poder descifrarse, porque cada sesión negoció su propia clave efímera. Strict-Transport-Security cierra la brecha donde la primera solicitud de un usuario podría todavía salir por HTTP plano antes de que se dispare una redirección.
#Configuración real: cifrado a nivel de campo en reposo
El cifrado completo de disco o a nivel de base de datos (cifrado transparente de datos en Postgres/MySQL, cifrado de volumen) protege contra un disco robado, pero descifra todo de forma transparente para cualquier query autenticada — no hace nada extra por una columna específica altamente sensible. El cifrado a nivel de campo resuelve eso cifrando el valor mismo antes de que se escriba en una fila, usando una clave gestionada por la aplicación (comúnmente envuelta a su vez por un KMS, de modo que la clave en crudo nunca esté en la configuración de la aplicación):
const crypto = require('crypto');// dataKey en sí se obtendría de un KMS al iniciar, no estaría hardcodeada —// esta función solo maneja el cifrado/descifrado del campo en sífunction encryptField(plaintext, dataKey) {const iv = crypto.randomBytes(12); // AES-GCM necesita un IV fresco y aleatorio por cada cifradoconst cipher = crypto.createCipheriv('aes-256-gcm', dataKey, iv);const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);const authTag = cipher.getAuthTag(); // la verificación de integridad de GCM — detecta manipulación al descifrar// guarda iv + authTag junto con el ciphertext; ninguno de estos tres es secreto por sí soloreturn Buffer.concat([iv, authTag, ciphertext]).toString('base64');}function decryptField(stored, dataKey) {const raw = Buffer.from(stored, 'base64');const iv = raw.subarray(0, 12);const authTag = raw.subarray(12, 28);const ciphertext = raw.subarray(28);const decipher = crypto.createDecipheriv('aes-256-gcm', dataKey, iv);decipher.setAuthTag(authTag);// lanza un error si authTag no coincide — el ciphertext fue manipulado o la clave es incorrectareturn Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');}// uso: solo la columna sensible se cifra antes de escribir la filaconst ssnCiphertext = encryptField(user.ssn, dataKey);await db.query('INSERT INTO users (id, name, ssn_encrypted) VALUES ($1, $2, $3)', [user.id,user.name,ssnCiphertext,]);
Se elige AES-256-GCM específicamente porque es cifrado autenticado — detecta si el ciphertext fue manipulado (decipher.final() lanza un error si la etiqueta de autenticación no coincide), algo que un cifrado AES-CBC simple no detectaría silenciosamente. El IV debe ser único por cada cifrado con GCM — reutilizar un IV con la misma clave es una falla catastrófica que puede filtrar el texto plano — por eso se genera fresco con crypto.randomBytes en cada llamada en lugar de reutilizarse o derivarse de forma predecible.
🧠 Check your understanding
0/3 · 0/3 answered1. Una empresa usa HTTPS para cada conexión de cliente pero almacena su base de datos de producción completamente sin cifrar. Un atacante explota un bug de inyección SQL para volcar la base de datos. ¿Qué falló?
2. ¿Por qué AES-256-GCM requiere un IV único y generado aleatoriamente para cada operación de cifrado con la misma clave?
3. Una base de datos tiene cifrado completo de disco habilitado, pero un atacante compromete el servidor de aplicación y puede ejecutar queries autenticadas con las credenciales normales de la app hacia la base de datos. ¿Qué hace el cifrado de disco en reposo por este atacante?