Escribes una dirección, pulsas Intro y, un instante después, aparece la página con un candado en la barra. En ese instante pasan muchas cosas: tu equipo busca una dirección IP, abre una conexión, comprueba que el servidor es quien dice ser, acuerda con él una clave secreta sin enviarla por el cable y, solo entonces, pide la página, que viaja envuelta en cifrado. Esta guía recorre ese camino paso a paso, con pruebas reales hechas con openssl, curl y una captura de paquetes, para que veas cada pieza en lugar de tener que creerla.
El problema: HTTP viaja a la vista de todos
HTTP, el protocolo con el que se piden las páginas, es texto. Entre tu equipo y el servidor hay routers, el wifi de la cafetería, tu proveedor de internet y otras máquinas que pasan los paquetes de mano en mano, y cualquiera de ellas puede leerlos. Para comprobarlo montamos dos servidores locales que sirven el mismo archivo con una «clave» inventada, uno por HTTP y otro por HTTPS, y capturamos los paquetes de cada uno. Esto es lo que contienen los paquetes del servidor HTTP, tal cual viajan:
captura de los paquetes TCP del servidor HTTP--- cliente → servidor, 90 bytes GET /secreto.txt HTTP/1.1 Host: miweb.test:8080 User-Agent: curl/8.14.1 Accept: */* --- servidor → cliente, 138 bytes HTTP/1.0 200 OK Server: BaseHTTP/0.6 Python/3.13.5 Date: Sun, 11 Oct 2026 14:34:06 GMT Content-Type: text/plain Content-Length: 36 --- servidor → cliente, 36 bytes usuario=ana&clave=Tr3s-Gatos-Azules
Cualquiera que vea pasar los paquetes lee la dirección pedida, tu navegador y, lo peor, la contraseña. Además podría cambiar el contenido sin que ninguno de los dos extremos se enterase, y no hay forma de saber si el servidor es el verdadero o un impostor que se hizo pasar por él. HTTPS existe para arreglar esas tres cosas.
Qué promete HTTPS
HTTPS no es otro protocolo: es el mismo HTTP, pero dentro de una conexión protegida por TLS (Transport Layer Security, el sucesor de SSL). TLS aporta tres garantías distintas, cada una con su propia pieza:
| Garantía | Qué significa | Qué la consigue |
|---|---|---|
| Confidencialidad | Quien escucha el cable no puede leer el contenido | Cifrado simétrico con una clave que solo conocen los dos extremos |
| Integridad | Nadie puede cambiar un solo bit sin que se note | Una etiqueta de autenticación en cada registro |
| Autenticidad | Hablas con el servidor del dominio que pediste, no con un impostor | El certificado del servidor y una firma con su clave privada |
El viaje completo: de la dirección a la página
Antes de que viaje ni un byte de la página hay varios pasos previos, y se pueden medir. Esto es lo que tardó una conexión nueva a decodigo.com desde mi equipo, con curl:
curl -so /dev/null -w 'dns %{time_namelookup} tcp %{time_connect} tls %{time_appconnect} primer_byte %{time_starttransfer} total %{time_total}\n' https://decodigo.com/cinco conexiones nuevas, tiempos acumulados en segundosdns 0.023287 tcp 0.095844 tls 0.183346 primer_byte 0.515360 total 0.646221 dns 0.020266 tcp 0.092938 tls 0.177870 primer_byte 0.706273 total 0.846151 dns 0.018106 tcp 0.085600 tls 0.170987 primer_byte 0.571914 total 0.710526 dns 0.029140 tcp 0.097581 tls 0.173287 primer_byte 0.666224 total 0.800713 dns 0.018113 tcp 0.090188 tls 0.173125 primer_byte 1.257789 total 1.394926
Los números de curl son acumulados: cada columna incluye las anteriores. Restando, en la primera conexión: 23 ms para averiguar la IP, 73 ms para abrir la conexión TCP, 88 ms para el apretón de manos de TLS, 332 ms hasta que el servidor responde con el primer byte y 131 ms más hasta recibir todo.
- DNS. El navegador traduce
decodigo.comen una dirección IP. Aquí todavía no hay cifrado, y por eso existe DNSSEC; lo contamos en el artículo sobre el cambio de clave raíz del DNS. - TCP. Abre una conexión con la IP, puerto 443, con el saludo de tres pasos de TCP. El tiempo es casi todo el viaje de ida y vuelta por la red.
- TLS. El apretón de manos que veremos enseguida. Con TLS 1.3 cuesta aproximadamente una ida y vuelta más.
- HTTP. Por fin se pide la página, ya por dentro del canal cifrado.
Lo que varía de una conexión a otra es el tiempo hasta el primer byte, de 515 a 1,258 ms en estas cinco pruebas: lo que tarda el servidor en generar la página (aquí es WordPress con PHP). El apretón de manos, por el contrario, es estable, y ahora verás que no es un coste enorme.
Una pregunta típica: ¿viaja algo sin cifrar si escribo http://decodigo.com? El servidor responde con una redirección 301 a la versión https, pero ese primer pedido sí viajó sin cifrar. Más adelante vemos cómo se evita con HSTS.
El apretón de manos de TLS 1.3, mensaje a mensaje
Veamos el diálogo real con decodigo.com. Con curl -v se ve cada mensaje por su nombre, y con openssl s_client, los parámetros que se eligieron:
curl -v https://decodigo.com/ -o /dev/null
Cada mensaje tiene una función concreta:
- ClientHello. El navegador dice qué versiones de TLS y qué cifrados entiende, a qué dominio quiere llegar (el campo SNI, para que un mismo servidor pueda alojar muchos sitios), qué protocolos de aplicación admite (ALPN:
h2para HTTP/2 yhttp/1.1) y, ya de entrada, su mitad de un intercambio de claves. - ServerHello. El servidor elige una versión y un cifrado y devuelve su mitad del intercambio de claves. Con las dos mitades, cada lado puede calcular el mismo secreto, y a partir de ahí todo va cifrado. En TLS 1.3 esto ocurre en una sola ida y vuelta, algo que en TLS 1.2 necesitaba dos.
- EncryptedExtensions. Detalles como el protocolo de aplicación acordado (ALPN).
- Certificate. La cadena de certificados del servidor, que ya viaja cifrada. Es una de las novedades de TLS 1.3: un espía ya no ve ni el certificado.
- CertificateVerify. Una firma, hecha con la clave privada del sitio, sobre todo lo dicho hasta ahora. Demuestra que quien envió el certificado tiene la clave privada que le corresponde.
- Finished (los dos lados). Un resumen criptográfico de todo el diálogo. Si alguien hubiera alterado un mensaje por el camino, los resúmenes no coincidirían.
Los parámetros elegidos con decodigo.com fueron estos:
líneas de openssl s_client -alpn h2,http/1.1 contra decodigo.comPeer signature type: rsa_pss_rsae_sha256 Verification: OK New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 Protocol: TLSv1.3 Verify return code: 0 (ok) ALPN protocol: h2
El intercambio de claves usado fue X25519, y el cifrado, AES-256 en modo GCM con SHA-384.
El servidor acepta TLS 1.2 y 1.3, y rechaza 1.0 y 1.1 (responde con una alerta de «versión de protocolo»), que están abandonados por inseguros. Que ALPN termine en h2 significa que la conversación siguiente usa HTTP/2, que el servidor tiene activo.
Cómo se acuerda una clave sin enviarla
Esta es la parte que más desconcierta: ¿cómo pueden dos extremos que nunca se han visto ponerse de acuerdo en una clave secreta por un cable que todos vigilan? Con un intercambio de claves de tipo Diffie-Hellman. La idea cabe en un ejemplo con números pequeños: ambos acuerdan públicamente dos números, p = 23 y g = 5; cada uno elige un número secreto y envía el resultado de una operación con él.
Diffie-Hellman con números pequeñosAna envía A = g^a mod p = 5^6 mod 23 = 8
Beto envía B = g^b mod p = 5^15 mod 23 = 19
Ana calcula B^a mod p = 2
Beto calcula A^b mod p = 2 <- el mismo número, sin haberlo enviado
lo que ve un espía: {'p': 23, 'g': 5, 'A': 8, 'B': 19}Ana usa su secreto (6) y el valor que envió Beto (19); Beto usa el suyo (15) y el de Ana (8); y los dos llegan a 2. El espía ve 23, 5, 8 y 19, pero para llegar a 2 necesitaría uno de los dos secretos, y recuperarlos a partir de lo público es el problema difícil. Con números tan pequeños se resuelve a mano, pero con los tamaños reales, no.
TLS 1.3 usa una variante moderna sobre curvas elípticas, X25519. Esto es lo mismo con claves de verdad, hecho con openssl: cada lado genera una clave privada aleatoria y su clave pública de 32 bytes, se intercambian las públicas, y los dos terminan con idéntico secreto.
X25519 real; las claves son aleatorias, en tu equipo saldrá otro valorAna calcula (su clave privada + la pública de Beto): da6d10f790492d8af1fb7bfba21df898f02781e9c16ab2168c453bd066a5160f Beto calcula (su clave privada + la pública de Ana): da6d10f790492d8af1fb7bfba21df898f02781e9c16ab2168c453bd066a5160f tamaño de cada clave pública (bytes): 32
De ese secreto compartido salen las claves de cifrado que se usarán en la conexión. Como las claves privadas se crean para cada conexión y se tiran, aunque alguien guardara hoy una captura y robase mañana la clave del servidor, no podría descifrarla. Esa propiedad se llama secreto hacia adelante (forward secrecy).
X25519MLKEM768 y no X25519. Es un intercambio híbrido que añade un algoritmo resistente a ordenadores cuánticos (ML-KEM) al de curva elíptica. El cliente de mi equipo lo ofrece también a decodigo.com, pero ese servidor eligió X25519. Lo que hay en juego es que alguien guarde hoy tráfico cifrado para descifrarlo en el futuro con un ordenador cuántico.El certificado: qué demuestra y qué no
El intercambio de claves tiene un fallo: no dice con quién hablas. Si un impostor se interpone, acordaría una clave con cada lado y leería todo. La autenticidad la aporta el certificado: un documento que dice «esta clave pública pertenece a este dominio» y que está firmado por una autoridad certificadora (CA) de confianza. Así es el de decodigo.com, extraído del apretón de manos real:
openssl x509 sobre el certificado que envía decodigo.comsubject=CN=decodigo.com
issuer=C=US, O=GoDaddy.com, CN=GoDaddy TLS Intermediate CA DV - R1v1
notBefore=Sep 15 05:12:48 2026 GMT
notAfter=Apr 1 05:12:48 2027 GMT
serial=C79BE2AEA7234CFA
sha256 Fingerprint=88:38:F0:4A:2C:6A:C5:06:A0:84:22:35:4D:F5:C6:56:10:D6:E3:86:D8:1F:03:92:0D:45:F0:30:6E:1C:30:72
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Subject Alternative Name:
DNS:decodigo.com, DNS:www.decodigo.com| Campo | Qué dice |
|---|---|
subject, Subject Alternative Name | A quién se emitió: decodigo.com y www.decodigo.com. Es lo que el navegador compara con la dirección que escribiste. |
issuer | Quién lo firmó: una CA intermedia de GoDaddy. |
notBefore / notAfter | Periodo de validez: del 15 de septiembre de 2026 al 1 de abril de 2027. |
| Clave pública | Una clave RSA de 2048 bits, la pareja de la clave privada que guarda el servidor. Con ella se comprueba la firma de CertificateVerify. |
CA:FALSE | Es un certificado de sitio: no puede usarse para firmar otros certificados. |
Extended Key Usage | Solo vale para autenticar servidores web (serverAuth). |
| Firma de la CA | El sello que hace que todo lo anterior sea creíble: si cambia un solo byte, la firma deja de cuadrar. |
Fíjate en lo que no dice. Este certificado es de validación de dominio: la CA comprobó que quien lo pidió controla decodigo.com, y nada más. No dice que el sitio sea honesto, ni seguro, ni de una empresa concreta. Una página de estafa puede tener un candado perfecto: lo que garantiza es que hablas con ese dominio y que nadie lo escucha por el camino, no que debas fiarte de él.
La cadena de confianza
Si la CA firma, ¿quién garantiza a la CA? Otra CA, y así hasta llegar a una raíz. Las raíces no están firmadas por nadie más: tu sistema operativo o tu navegador las traen de fábrica en su almacén de confianza (mi equipo tiene 121), y esa es la base de todo. El servidor de decodigo.com envía tres certificados y openssl reconstruye la cadena entera:
openssl verify -show_chain sobre los certificados recibidoscert1.pem: OK Chain: depth=0: CN=decodigo.com (untrusted) depth=1: C=US, O=GoDaddy.com, CN=GoDaddy TLS Intermediate CA DV - R1v1 (untrusted) depth=2: C=US, O=GoDaddy.com, CN=GoDaddy TLS Root CA - R1 (untrusted) depth=3: C=US, ST=Arizona, L=Scottsdale, O=GoDaddy.com, Inc., CN=Go Daddy Root Certificate Authority - G2
El servidor envía los tres primeros y no la raíz, porque esa ya la tiene el navegador, y mandarla sería inútil: lo que importa es que viene de tu almacén, no del servidor. La intermedia existe por seguridad: la clave de la raíz se guarda fuera de línea y solo se usa para firmar unas pocas CA intermedias, que son las que firman a diario los certificados de los sitios. Si una intermedia se compromete, se revoca sin tocar la raíz. Según el certificado, la intermedia de decodigo.com tiene CA:TRUE, pathlen:0: puede firmar sitios, pero no otras CA.
Cómo falla una verificación: cinco casos reales
Para ver los errores de verdad creamos una autoridad certificadora de juguete y un certificado para miweb.test, y le pedimos a openssl que lo verifique en cinco situaciones:
openssl verify con una autoridad de prueba propia=== 1. verificar SIN conocer la autoridad CN=miweb.test error 20 at 0 depth lookup: unable to get local issuer certificate === 2. verificar CONOCIÉNDOLA miweb.crt: OK === 3. el nombre no coincide CN=miweb.test error 62 at 0 depth lookup: hostname mismatch === 4. caducado (dentro de 100 días, el certificado dura 90) CN=miweb.test error 10 at 0 depth lookup: certificate has expired === 5. alguien cambia un byte del certificado CN=liweb.test error 7 at 0 depth lookup: certificate signature failure
| Caso | Qué pasó | Qué verías en el navegador |
|---|---|---|
| 1 | La CA no está en el almacén de confianza | Aviso de «conexión no privada» (autoridad desconocida o certificado autofirmado) |
| 2 | La CA se conoce y la firma cuadra | Candado, sin avisos |
| 3 | El certificado es de otro nombre | Aviso de que el certificado no es válido para ese dominio |
| 4 | Fuera de su periodo de validez | Aviso de certificado caducado |
| 5 | Cambiamos miweb por liweb: la firma ya no cuadra | Aviso de certificado no válido: nadie puede retocarlo sin que se note |
En el último caso basta cambiar una letra para que la firma falle: la CA firmó un resumen de todo el contenido del certificado, y cualquier cambio altera el resumen. Por eso un impostor no puede presentarte un certificado «de decodigo.com» con su propia clave pública: tendría que fabricar la firma de una CA, y no puede.
El envoltorio: cómo viaja el contenido cifrado
Tras el apretón de manos, el navegador y el servidor tienen claves de cifrado. TLS divide la conversación en registros y cifra cada uno con AES-256-GCM, el cifrado que eligieron ambos. GCM cifra y, a la vez, calcula una etiqueta de 16 bytes que depende de todo el contenido: sin la clave no se puede ni leer el mensaje ni fabricar una etiqueta válida. El registro viaja dentro de un segmento TCP, dentro de un paquete IP:
Los tamaños de la captura lo confirman al byte. Cada registro mide su contenido más 17 bytes: 1 del tipo de contenido (que también va cifrado) y 16 de la etiqueta. La petición de 90 bytes se convirtió en un registro de 107; la cabecera de la respuesta, de 138, en 155; el cuerpo, de 36, en 53. Esta es la lista de registros de la captura HTTPS, en orden. Todos son del tipo 23, «Application Data», excepto el saludo inicial y el ChangeCipherSpec, un resto de compatibilidad con versiones antiguas que no hace nada:
registros TLS de la captura HTTPS, con nuestra anotación entre paréntesiscliente → servidor 22 Handshake longitud 1565 cliente → servidor 20 ChangeCipherSpec longitud 1 cliente → servidor 23 Application Data longitud 69 (Finished) cliente → servidor 23 Application Data longitud 107 (la petición: 90 + 17) cliente → servidor 23 Application Data longitud 19 (aviso de cierre) servidor → cliente 22 Handshake longitud 1210 (ServerHello) servidor → cliente 20 ChangeCipherSpec longitud 1 servidor → cliente 23 Application Data longitud 23 (EncryptedExtensions) servidor → cliente 23 Application Data longitud 887 (Certificate: 857 bytes + 30) servidor → cliente 23 Application Data longitud 281 (CertificateVerify) servidor → cliente 23 Application Data longitud 69 (Finished) servidor → cliente 23 Application Data longitud 250 (NewSessionTicket) servidor → cliente 23 Application Data longitud 250 (NewSessionTicket) servidor → cliente 23 Application Data longitud 155 (cabecera de respuesta: 138 + 17) servidor → cliente 23 Application Data longitud 53 (cuerpo: 36 + 17)
Las anotaciones las hemos añadido nosotros cuadrando tamaños, porque desde fuera no se ven. La cabecera de cada registro, que sí viaja en claro, solo dice su tipo, la versión y el largo (17 03 03 00 6b es «datos de aplicación, 107 bytes»).
La etiqueta de integridad, probada
Para verlo funcionar, cifré con AES-256-GCM el mismo texto de 90 bytes (con una clave fija, solo para que el resultado sea repetible; las reales son aleatorias):
AES-256-GCM con la biblioteca cryptography de Pythonmensaje: 90 bytes cifrado: 106 bytes (+16 de etiqueta de integridad) mismo mensaje, número distinto -> cifrados distintos cifrado 1: 00 47 82 3b ea 96 a7 78 ff 24 e3 e4 9f 9d 00 19 a3 9e d3 60 a0 54 6e 52 … cifrado 2: 42 af 0e f5 c3 e7 95 e5 3e c7 17 28 3e 67 92 5c 62 0b b5 aa c7 42 61 eb … descifrar con la clave buena -> b'GET /secreto.txt HTTP/1.1' descifrar con 1 bit alterado -> InvalidTag: se rechaza entero, sin dar ni un byte descifrar con otra clave -> InvalidTag
Tres cosas a destacar. Con otro número de uso único (nonce), el mismo texto da un cifrado totalmente distinto, así que un espía no puede ni saber si repites una petición. Con la clave correcta se recupera el texto. Y si se altera un solo bit o se usa otra clave, el descifrado se rechaza por completo: no sale un mensaje corrompido, sale un error. Por eso un intermediario que intente modificar la página no puede hacerlo sin que se note: lo máximo que consigue es cortar la conexión.
Qué ve un espía
La captura HTTPS del mismo servidor, con la misma contraseña, deja este resultado:
búsqueda de texto en las dos capturas¿aparece el secreto en cada captura? (veces que sale 'clave=') http : 1 https: 0 y el nombre del sitio, o el certificado, ¿se ven en la captura HTTPS? 'miweb.test' (nombre pedido, SNI): 1 vez/veces 'Autoridad de prueba' (el certificado): 0 'GET /secreto.txt': 0
Lo que un espía no ve: la dirección completa pedida (/secreto.txt), las cabeceras, el contenido, las cookies, las contraseñas y, con TLS 1.3, tampoco el certificado. Lo que sí ve:
- Las direcciones IP de origen y destino, y el puerto.
- El nombre del sitio,
miweb.testen la prueba, porque el campo SNI delClientHellova en claro. Sabe que visitasdecodigo.com, pero no qué artículo lees. Existe una extensión (ECH, Encrypted Client Hello) que lo oculta, aunque todavía no está generalizada. - El tamaño de cada registro y cuándo se envían. Con los tamaños, un observador paciente puede deducir bastante, por ejemplo qué página de un sitio conocido se descargó.
Lo que HTTPS no hace
- No te protege de un sitio malicioso. Cifra la conexión con quien tengas en frente, sea quien sea, como vimos con la validación de dominio.
- No protege el equipo de los extremos. Si tu ordenador tiene un virus, o el servidor está comprometido, lo que ve cada uno no depende del cifrado.
- No esconde con quién hablas, como acabamos de ver (IP y SNI).
- No obliga por sí solo a usar HTTPS. Si escribes
http://decodigo.com, la primera petición va en claro y un atacante en medio podría impedir la redirección. La defensa es HSTS: una cabecera,Strict-Transport-Security, con la que el servidor dice «durante los próximos N segundos, usa siempre HTTPS conmigo». El navegador la recuerda y, a partir de ahí, nunca vuelve a intentar HTTP en ese sitio. decodigo.com la envía desde el 11 de octubre de 2026, de momento conmax-age=300(cinco minutos) para ir subiéndolo poco a poco: un valor largo y un certificado caducado dejarían el sitio inaccesible. SinincludeSubDomainsnipreload.
Compruébalo tú
Con el candado del navegador puedes ver el certificado, pero los comandos enseñan más. Sustituye decodigo.com por cualquier sitio:
# el apretón de manos, mensaje a mensaje curl -v https://decodigo.com/ -o /dev/null # versión, cifrado y cadena de certificados que envía el servidor openssl s_client -connect decodigo.com:443 -servername decodigo.com -showcerts </dev/null # el certificado del sitio, legible openssl s_client -connect decodigo.com:443 -servername decodigo.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName # ¿acepta TLS 1.1? Hay que bajar el nivel de seguridad de tu propio openssl para que llegue a intentarlo openssl s_client -connect decodigo.com:443 -servername decodigo.com -tls1_1 -cipher 'ALL:@SECLEVEL=0' </dev/null # ¿envía HSTS? (decodigo.com responde: strict-transport-security: max-age=300) curl -sI https://decodigo.com/ | grep -i strict-transport
Y si te interesa mirar paquetes, tcpdump y Wireshark muestran los registros TLS tal como los hemos visto aquí. Para probarlo sin riesgos, hazlo con un servidor tuyo en local, como en esta guía, y no con tráfico de otras personas.
Resumen
| Paso | Qué ocurre | Qué aporta |
|---|---|---|
| DNS | Se averigua la IP del dominio | Saber a dónde conectar |
| TCP | Se abre la conexión al puerto 443 | Un canal fiable, todavía sin protección |
| ClientHello / ServerHello | Se eligen la versión y el cifrado, y se intercambian las mitades de la clave | Una clave compartida que nunca viajó por el cable |
| Certificate / CertificateVerify | El servidor presenta su cadena y firma con su clave privada | Autenticidad: el dominio es de quien lo pidió |
| Finished | Los dos lados comprueban un resumen del diálogo | Nadie alteró el apretón de manos |
| HTTP en registros TLS | Cada registro se cifra con AES-GCM y lleva una etiqueta de 16 bytes | Confidencialidad e integridad |
La lección práctica es que cada garantía tiene su pieza y ninguna sobra: sin el intercambio de claves, nadie puede cifrar; sin el certificado, el cifrado sería con un impostor; sin la etiqueta, nadie notaría una alteración. Y el coste real es pequeño: una ida y vuelta de más antes de la primera petición.
Todo lo medido se ejecutó el 11 de octubre de 2026 desde un equipo con openssl 3.5 y curl 8.14, contra decodigo.com y contra servidores locales; las salidas están copiadas de esas ejecuciones. Los estándares: RFC 8446 (TLS 1.3), RFC 5280 (certificados X.509) y RFC 6797 (HSTS).
Fuente: IETF, RFC 8446, «The Transport Layer Security (TLS) Protocol Version 1.3»
