⏱️ Lectura: 13 min

Cada vez que abrís una página con HTTPS, tu navegador y el servidor negocian una conexión cifrada en una fracción de segundo: eso es el handshake TLS 1.3, y desde 2018 hace ese trabajo con la mitad de los viajes de red que necesitaba su predecesor. Antes, TLS 1.2 necesitaba dos vueltas completas solo para ponerse de acuerdo en las claves; TLS 1.3 lo resuelve en una, y en reconexiones puede arrancar en cero.

📑 En este artículo
  1. TL;DR
  2. Qué es y por qué importa el handshake TLS 1.3
  3. Cómo funciona el handshake TLS 1.3 en detalle
  4. Ejemplos prácticos
    1. Inspeccionar el handshake con openssl
    2. Confirmar la versión desde curl
    3. Forzar TLS 1.3 en un servidor Node.js
    4. Descifrar tráfico TLS 1.3 en Wireshark
  5. Cómo habilitar TLS 1.3 en producción
  6. Casos de uso reales
  7. Errores comunes y buenas prácticas
  8. Comparativa: TLS 1.2 vs TLS 1.3 vs mTLS vs QUIC
  9. Profundizando: el key schedule y la capa de registro
  10. Preguntas frecuentes
    1. ¿Necesito cambiar mi certificado para pasar a TLS 1.3?
    2. ¿Qué navegadores soportan TLS 1.3?
    3. ¿Por qué TLS 1.3 no permite RSA para el intercambio de claves?
    4. ¿0-RTT es inseguro por diseño?
    5. ¿Cómo se relaciona TLS 1.3 con HTTP/3 y QUIC?
  11. Referencias

El protocolo, estandarizado en la RFC 8446, no es un ajuste menor sobre TLS 1.2: reescribió el handshake desde cero. Eliminó el intercambio de claves estático con RSA, sacó cifrados con vulnerabilidades conocidas (RC4, 3DES, CBC sin autenticar) y redujo el handshake completo de dos round trips a uno solo.

TL;DR

  • Vas a entender por qué TLS 1.3 usa un solo round trip (1-RTT) en vez de los dos de TLS 1.2.
  • Vas a poder inspeccionar con openssl s_client qué versión y cifrado usa cualquier servidor HTTPS.
  • Vas a saber configurar 0-RTT en Nginx y por qué conviene limitarlo a peticiones idempotentes.
  • Vas a distinguir ECDHE de RSA como intercambio de claves y por qué solo ECDHE da forward secrecy.
  • Vas a poder generar un keylog de tu navegador y descifrar tráfico TLS 1.3 en Wireshark.
  • Vas a conocer los errores más comunes al migrar de TLS 1.2 a 1.3 en producción.

Qué es y por qué importa el handshake TLS 1.3

TLS (Transport Layer Security) es el protocolo que cifra casi todo el tráfico HTTPS de internet: sin él, cualquiera que intercepte el cable o el punto de acceso wifi podría leer contraseñas, cookies de sesión y números de tarjeta en texto plano. El handshake TLS 1.3 es la fase inicial de esa conexión, el intercambio de mensajes donde cliente y servidor se ponen de acuerdo en qué cifrado usar y derivan las claves simétricas que van a proteger el resto de la sesión.

La historia del protocolo viene de SSL en los años 90; TLS 1.0 y 1.1 quedaron formalmente obsoletos y hoy los navegadores modernos ya ni siquiera los ofrecen como opción. TLS 1.3 es la versión que corre por defecto en Chrome, Firefox, Safari y Edge desde 2018-2020.

Por qué importa esto en la práctica: cada round trip que se ahorra el handshake TLS 1.3 se traduce en latencia real para el usuario, sobre todo en redes móviles con 100-200ms de RTT hacia el servidor. Y al forzar que todo intercambio de claves use Diffie-Hellman efímero (ECDHE), TLS 1.3 garantiza forward secrecy: aunque alguien robe la clave privada del servidor el año que viene, no puede descifrar el tráfico capturado hoy, porque el secreto de sesión nunca viajó por la red ni se deriva solo de esa clave privada.

Diagrama del handshake TLS 1.3 entre cliente y servidor
El handshake completo cabe en un solo intercambio de ida y vuelta. Foto de FlyD en Unsplash

Cómo funciona el handshake TLS 1.3 en detalle

El handshake arranca cuando el cliente (tu navegador, o un curl) envía un mensaje ClientHello. A diferencia de TLS 1.2, este mensaje ya incluye una apuesta: la extensión key_share con una clave pública efímera para uno o más grupos de Diffie-Hellman, típicamente x25519 o secp256r1. El cliente adivina qué grupo va a aceptar el servidor y manda la clave de una vez, sin esperar confirmación previa.

Si el servidor soporta ese grupo, responde con un único mensaje combinado: ServerHello (con su propio key_share), EncryptedExtensions, Certificate, CertificateVerify y Finished. Con la clave pública del servidor y la propia, ambos lados calculan el mismo secreto compartido usando Diffie-Hellman, sin que ese secreto haya viajado nunca por la red. A partir de ahí, una función HKDF deriva las claves simétricas de sesión.

El cliente valida el certificado, calcula su propio Finished y, en el mismo paquete, ya puede mandar la primera petición HTTP cifrada. Eso es 1-RTT: un viaje de ida (ClientHello) y uno de vuelta (respuesta del servidor) antes de que fluyan datos de aplicación.

sequenceDiagram
    participant C as Cliente
    participant S as Servidor
    C->>S: ClientHello + key_share (ECDHE)
    S-->>C: ServerHello + key_share + Certificate + Finished
    Note over C,S: ambos derivan las claves de sesion con HKDF
    C->>S: Finished + datos de aplicacion cifrados
    Note over C,S: conexion establecida en 1 round trip

Cuando el cliente ya se conectó antes al mismo servidor y guardó un session ticket, puede saltarse el intercambio ECDHE inicial y mandar datos de aplicación cifrados en el mismo paquete del ClientHello: eso es 0-RTT. Es rápido, pero trae un problema de seguridad real que cubrimos en la sección de errores comunes.

Ejemplos prácticos

Antes de tocar la configuración de un servidor, conviene ver el handshake TLS 1.3 funcionando en vivo. Estos ejemplos van de la inspección más simple a la más completa.

Inspeccionar el handshake con openssl

El comando más directo para confirmar qué versión negoció un servidor es openssl s_client:

openssl s_client -connect example.com:443 -tls1_3 -brief

La salida muestra una línea Protocol version: TLSv1.3 y el cifrado negociado, por ejemplo TLS_AES_128_GCM_SHA256. Si el servidor no soporta TLS 1.3, la conexión falla directamente en vez de degradar en silencio, lo cual sirve para detectar configuraciones desactualizadas.

Confirmar la versión desde curl

curl -v --tlsv1.3 https://example.com/ 2>&1 | grep "SSL connection"

Con curl 7.52 o superior, esa línea imprime algo como SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384. Es el chequeo más rápido para un script de monitoreo que valide que ningún endpoint cayó de vuelta a TLS 1.2.

Forzar TLS 1.3 en un servidor Node.js

const https = require('node:https');
const fs = require('node:fs');

const server = https.createServer({
  key: fs.readFileSync('clave-privada.pem'),
  cert: fs.readFileSync('certificado.pem'),
  minVersion: 'TLSv1.3',
}, (req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('conexion cifrada con TLS 1.3');
});

server.listen(443);

El campo minVersion: 'TLSv1.3' hace que Node rechace cualquier cliente que intente negociar TLS 1.2 o anterior, en vez de aceptarlo silenciosamente. Es la forma más simple de auditar, en un entorno de staging, qué clientes viejos todavía dependen de versiones inseguras.

Descifrar tráfico TLS 1.3 en Wireshark

Para depurar un handshake real, Chrome y Firefox pueden volcar las claves de sesión a un archivo si existe la variable de entorno SSLKEYLOGFILE:

export SSLKEYLOGFILE=$HOME/tls-keys.log
google-chrome https://example.com

Después, en Wireshark: Edit > Preferences > Protocols > TLS > (Pre)-Master-Secret log filename, apuntando a ese archivo. Wireshark decodifica automáticamente los paquetes cifrados y muestra el ClientHello, el key_share y los datos de aplicación en texto plano, útil para confirmar que una API realmente está usando el cifrado que creés.

Terminal mostrando la inspección de un handshake TLS 1.3
openssl s_client confirma version y cifrado en una sola linea. Foto de Brecht Corbeel en Unsplash

Cómo habilitar TLS 1.3 en producción

Estos pasos sirven para un servidor Nginx con un certificado ya emitido, por ejemplo con Let’s Encrypt:

  1. Confirmá la versión de OpenSSL instalada: TLS 1.3 requiere OpenSSL 1.1.1 o superior, con openssl version.
  2. En el bloque server de Nginx, editá la directiva de protocolos: ssl_protocols TLSv1.2 TLSv1.3; (mantené 1.2 como respaldo mientras migrás clientes viejos).
  3. Fijá los grupos de intercambio de claves preferidos: ssl_ecdh_curve X25519:prime256v1;.
  4. Si querés permitir 0-RTT solo en endpoints idempotentes (GET, nunca en un POST de pagos): ssl_early_data on;.
  5. Recargá la configuración: nginx -t && systemctl reload nginx.
  6. Verificá con el comando de openssl de la sección anterior, apuntando al dominio real.
💡 Tip: mantené TLS 1.2 activo unas semanas más y revisá los logs de acceso por la variable $ssl_protocol en Nginx: si ya no aparecen conexiones en 1.2, podés desactivarlo por completo.

Casos de uso reales

El ahorro de un round trip en el handshake TLS 1.3 se nota más cuanto más lejos está el cliente del servidor. En un checkout de e-commerce con usuarios en redes móviles de 150ms de latencia, cada conexión nueva ahorra ese segundo round trip que costaba en TLS 1.2, antes de que empiece a cargar cualquier dato.

Las APIs móviles con reconexiones frecuentes (apps que entran y salen de background) se benefician del modo 0-RTT para retomar sesión sin esperar el handshake completo, siempre que la primera petición sea de solo lectura.

QUIC, el protocolo de transporte detrás de HTTP/3, no usa TLS como una capa aparte: incrusta el handshake TLS 1.3 directamente en su negociación de conexión, así que todo lo que aprendas acá aplica también a cómo arranca una conexión HTTP/3 sobre UDP.

Errores comunes y buenas prácticas

El error más citado en foros de seguridad es habilitar 0-RTT sin filtrar qué peticiones puede recibir. Como el ClientHello con datos 0-RTT puede ser reenviado por un atacante que capturó el paquete (un replay), cualquier endpoint no idempotente que reciba ese primer paquete corre el riesgo de ejecutarse dos veces. La RFC 8446 lo advierte explícitamente en su sección de consideraciones de seguridad.

⚠️ Ojo: nunca proceses con datos 0-RTT una petición que modifique estado (pagos, cambios de contraseña, cualquier POST no idempotente): un atacante puede repetir el mismo paquete capturado y disparar la acción dos veces.

Otro problema frecuente es el desfase de reloj entre cliente y servidor: los session tickets de TLS 1.3 incluyen una marca de tiempo ofuscada, y si el reloj del servidor está mal sincronizado, puede rechazar tickets válidos y forzar handshakes completos innecesarios en cada reconexión.

Middleboxes viejas (proxies corporativos, firewalls, algunos balanceadores de carga) a veces no reconocen las nuevas extensiones de TLS 1.3 y cortan la conexión. Por eso el estándar incluye un modo de compatibilidad: el servidor puede simular campos de TLS 1.2 en los mensajes para que esos equipos no bloqueen el tráfico.

📌 Nota: si migrás una API interna a mTLS sobre TLS 1.3, rotá los certificados de cliente con la misma disciplina que los del servidor: un certificado de cliente vencido corta la conexión igual que uno de servidor vencido.

Comparativa: TLS 1.2 vs TLS 1.3 vs mTLS vs QUIC

No todas las variantes sirven para el mismo escenario. Esta tabla resume cuándo conviene cada una:

OpciónCuándo usarlaVentajaLimitación
TLS 1.2Clientes legacy que no soportan 1.3 (algunos IoT, navegadores muy viejos)Compatibilidad amplia2 round trips, permite cifrados sin forward secrecy si se configura mal
TLS 1.3Cualquier servicio web nuevo o API pública1-RTT, forward secrecy obligatorio, menos superficie de ataque0-RTT requiere filtrar peticiones no idempotentes
mTLS (TLS 1.3 mutuo)Comunicación servicio a servicio dentro de una red interna o service meshAutentica al cliente además del servidorRequiere distribuir y rotar certificados de cliente
QUIC (HTTP/3)Apps con conexiones inestables o mucha pérdida de paquetesHandshake TLS 1.3 integrado al transporte, sin head-of-line blockingBloqueado por algunos firewalls que solo permiten TCP/443

Profundizando: el key schedule y la capa de registro

Debajo del handshake visible, TLS 1.3 deriva las claves con una cadena de llamadas a HKDF-Extract y HKDF-Expand conocida como el key schedule. Empieza con un Early Secret derivado de un valor constante (o de la Pre-Shared Key si hay reanudación de sesión), sigue con el Handshake Secret una vez que se conoce el secreto ECDHE, y termina en el Master Secret, del cual salen las claves de tráfico de aplicación.

flowchart TD
    A["ECDHE shared secret"] --> B["Handshake Secret (HKDF-Extract)"]
    B --> C["Claves de handshake (HKDF-Expand)"]
    B --> D["Master Secret (HKDF-Extract)"]
    D --> E["Claves de trafico de aplicacion"]
    subgraph "Derivacion de claves TLS 1.3"
    A
    B
    C
    D
    E
    end

Cada mensaje de aplicación viaja cifrado con AEAD (autenticación y cifrado combinados), normalmente AES-128-GCM, AES-256-GCM o ChaCha20-Poly1305 en dispositivos sin aceleración por hardware para AES. A diferencia de TLS 1.2, ya no existen modos CBC en el estándar: cada registro cifrado incluye su propio tag de autenticación, así que un atacante no puede alterar un solo byte sin que la conexión lo detecte y la corte.

Para reanudación de sesión, TLS 1.3 ofrece dos modos de Pre-Shared Key: psk_ke, que reutiliza el secreto anterior sin un nuevo ECDHE (más rápido, sin forward secrecy nueva), y psk_dhe_ke, que combina el ticket con un nuevo intercambio ECDHE para mantener forward secrecy incluso en la reconexión. La mayoría de los navegadores prefieren psk_dhe_ke por defecto.

Podés confirmar qué cifrado terminó usando una conexión real con openssl s_client -connect example.com:443 -tls1_3 | grep Cipher, o inspeccionando el socket en Node con tlsSocket.getCipher(), que devuelve algo como { name: 'TLS_AES_256_GCM_SHA384', version: 'TLSv1.3' }.

📖 Resumen en Telegram: Ver resumen

Tu próximo paso: corré openssl s_client -connect tu-dominio.com:443 -tls1_3 -brief contra tu propio servidor ahora mismo y confirmá si ya está negociando TLS 1.3 o si sigue cayendo a 1.2.

Preguntas frecuentes

¿Necesito cambiar mi certificado para pasar a TLS 1.3?

No. El certificado X.509 es el mismo para TLS 1.2 y 1.3; lo que cambia es el protocolo de negociación y las suites de cifrado, no la identidad criptográfica del servidor.

¿Qué navegadores soportan TLS 1.3?

Chrome, Firefox, Safari y Edge lo soportan por defecto desde 2018-2020; el riesgo real está en clientes embebidos viejos, SDKs móviles desactualizados o middleboxes corporativas.

¿Por qué TLS 1.3 no permite RSA para el intercambio de claves?

Con RSA estático, si alguien captura el tráfico cifrado hoy y roba la clave privada del servidor en el futuro, puede descifrar todo lo capturado. ECDHE genera una clave efímera distinta en cada conexión, así que no existe ese riesgo retroactivo.

¿0-RTT es inseguro por diseño?

No es inseguro en sí mismo, pero solo debe usarse para peticiones idempotentes. La RFC 8446 documenta el riesgo de replay y deja la mitigación (limitar a GET, usar tickets de un solo uso) en manos del servidor.

¿Cómo se relaciona TLS 1.3 con HTTP/3 y QUIC?

HTTP/3 corre sobre QUIC, y QUIC incrusta el handshake TLS 1.3 dentro de su propio establecimiento de conexión: no hay una capa TLS separada por encima de TCP, todo pasa en la misma negociación sobre UDP.

Referencias

📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.

Imagen destacada: Foto de FlyD en Unsplash

Categorías: SeguridadTutoriales

Clara Vásquez

Analista de ciberseguridad enfocada en vulnerabilidades críticas, zero-days y amenazas emergentes. Cubre CVEs de alto impacto, análisis de malware, incidentes de ransomware y tendencias de seguridad para LATAM.

0 Comentarios

Deja un comentario

Marcador de posición del avatar

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.