What TLS adds to a TCP connection
A plain HTTP request travels as readable text: every router, Wi-Fi access point and ISP on the path can read it and change it (see How an HTTP connection is established). HTTPS is the same HTTP inside a TLS connection, which adds three things:
- Confidentiality: nobody on the path can read the request or the page.
- Integrity: nobody can change a byte without the receiver noticing.
- Authentication: the browser knows it talks to the real
example.comand not to someone in between.
All three need keys that only the two ends know. The whole handshake exists to agree on those keys over a network that anyone can listen to, and to prove who is at the other end. The animation starts after the TCP handshake (drawn as one collapsed band) and shows every TLS message, what each side computes from it in the key panels at the top, and what a passive observer on the wire can read in the column on the right.
Symmetric and asymmetric cryptography: why both
Symmetric ciphers such as AES-GCM and ChaCha20-Poly1305 use one key for both directions of the operation. They are fast (gigabytes per second with hardware support) and also authenticate every record (AEAD: a record that was changed fails to decrypt). But both sides must already have the key. Asymmetric cryptography (key pairs: a private key and a public key) solves exactly that problem, and signatures, but it is thousands of times slower. TLS therefore uses asymmetric cryptography only during the handshake, to agree on keys and to authenticate the server, and then encrypts all data with symmetric keys derived from the agreement.
Diffie-Hellman: agreeing on a key without sending it
Each side picks a random private number and sends only a public value computed from it. With the toy numbers of the animation (tick toy numbers): everyone knows p = 23 and g = 5. The client picks a = 6 and sends A = 56 mod 23 = 8; the server picks b = 15 and sends B = 515 mod 23 = 19. The client computes Ba = 196 mod 23 = 2, the server computes Ab = 815 mod 23 = 2: the same number, because both equal 5ab mod 23. An eavesdropper sees 23, 5, 8 and 19, and would have to find a from 5a mod 23 = 8: trivial for 23, infeasible for the numbers real TLS uses.
TLS 1.3 uses the elliptic-curve version, ECDHE, usually on the curve x25519: private key c, public share C = c·G (a point on the curve), shared secret c·S = s·C = cs·G. The E at the end means ephemeral: a fresh key pair for every connection, thrown away afterwards. That gives forward secrecy: stealing the server's long-term key later does not decrypt recorded old traffic.
Diffie-Hellman alone does not say whom you share the secret with. Tick man-in-the-middle: the attacker replaces both key shares with its own and ends up with one secret shared with the client and another shared with the server (different colours in the key panels). Without a certificate (Demo: why authentication matters, first connection) it can then decrypt, read and re-encrypt everything. That is why key agreement must be authenticated.
Certificates, CAs, chains and the trust store
A certificate binds a name (example.com, in the subjectAltName) to a public key, for a limited time, and is signed by a certificate authority (CA). The server sends a chain: its own leaf certificate, signed by an intermediate (here Let's Encrypt R3), signed by a root (ISRG Root X1). Roots are not sent: they are already in the browser's or operating system's trust store. The client checks, as in the chain panel of the animation:
- each signature in the chain with the issuer's public key;
- that the chain ends in a root in the trust store (else
unknown_ca,NET::ERR_CERT_AUTHORITY_INVALID); - that the certificate names the host the user asked for (else
NET::ERR_CERT_COMMON_NAME_INVALID); - that today lies in the validity period (else
certificate_expired,NET::ERR_CERT_DATE_INVALID); - that it is not revoked, usually from an OCSP answer the server staples to the handshake.
A valid certificate alone proves nothing: it is public, anyone can copy it. The proof is CertificateVerify, a signature over the hash of the whole handshake so far, made with the certificate's private key. A man-in-the-middle can forward the real certificate, but it cannot sign the transcript the client saw (which contains the attacker's key share), so the client aborts. Run Demo: why authentication matters to see both halves.
Mutual TLS (client certificates) works the same way in the other direction: the server sends CertificateRequest, the client answers with its own Certificate and CertificateVerify. It is common between services, rare for browsers, and not simulated here.
The TLS 1.3 handshake, message by message
| Message | Direction | Protection | What it does |
|---|---|---|---|
| ClientHello | C → S | plaintext | random, cipher suites, supported_versions, a key_share (the client guesses the group), SNI, ALPN |
| ServerHello | S → C | plaintext | chosen suite and the server's key_share: now both sides can compute the shared secret |
| EncryptedExtensions | S → C | handshake keys | the rest of the server's choices, such as ALPN h2 |
| Certificate | S → C | handshake keys | the certificate chain |
| CertificateVerify | S → C | handshake keys | signature over the transcript hash with the certificate's private key |
| Finished | S → C | handshake keys | HMAC over the transcript: nobody changed any message |
| Finished (+ request) | C → S | handshake / application keys | the client's Finished, and in the same flight the first HTTP request |
| NewSessionTicket | S → C | application keys | a ticket for resumption next time |
The client sends its key share in the very first message, so the server can answer with its share and everything else in one flight: one round trip of TLS. The first byte of the page arrives after 3 RTT: TCP, TLS, then the request. If the client guessed a group the server does not accept, the server answers with a HelloRetryRequest and the client must send a second ClientHello: one extra round trip (the TLS 1.3 HelloRetryRequest tab). Browsers avoid this by sending shares for the most common groups.
The key schedule: which key encrypts what
TLS 1.3 derives all keys with HKDF (an HMAC-based key derivation function), in three stages, each mixing in the hash of the messages so far:
PSK (or zeros) ──HKDF──▶ early secret ──▶ client early traffic key (0-RTT data)
+ ECDHE shared secret ──HKDF──▶ handshake secret ──▶ client / server handshake traffic keys
+ nothing new ──HKDF──▶ master secret ──▶ client / server application traffic keys
──▶ resumption secret (for tickets)
Each direction has its own key. In the diagram, purple arrows are plaintext, orange ones are encrypted with the handshake keys (the rest of the handshake, including the certificate), green ones with the application keys (HTTP), and teal ones with the early-data key. A record is only drawn with a key once the receiver can hold that key too.
What TLS 1.2 did differently
- Two round trips: the ClientHello carries no key share; the server sends its certificate and a signed ECDHE share (ServerKeyExchange), the client answers with its share (ClientKeyExchange), ChangeCipherSpec and Finished, and the server with ChangeCipherSpec and Finished. Only then may the request go: 4 RTT to the first byte. (False Start let browsers send the request with the client Finished, saving one RTT.)
- The certificate travels in clear: an observer learns exactly which certificate the site uses. TLS 1.3 encrypts it.
- RSA key exchange: 1.2 also allowed the client to encrypt the secret with the server's RSA key. If that key leaked later, every recorded session could be decrypted: no forward secrecy. TLS 1.3 removed it; only (EC)DHE remains, along with fewer, safer cipher suites (AEAD only).
Resumption and 0-RTT
After the handshake the server sends a NewSessionTicket: the resumption secret, encrypted with a key only the server knows, so the server stores nothing. On the next visit the client offers the ticket as a pre-shared key (PSK) with a binder, an HMAC that proves it knows the secret. The server needs no certificate or signature: knowing the PSK proves it issued the ticket. It is still one round trip, but less data and less CPU. A fresh key share is normally sent as well, so the new keys stay forward secret.
With 0-RTT, the client derives an early key from the PSK alone and sends the request together with the ClientHello: the answer comes after one round trip of TLS and HTTP together (2 RTT to the first byte, counting TCP). The price: the early data is not protected against replay. An attacker can record the first flight and send it again, and the server may process it twice. Servers therefore accept 0-RTT only for safe, idempotent requests such as GET, and answer 425 Too Early otherwise.
What stays visible on the wire
Even with TLS 1.3, an observer sees the IP addresses and ports, the timing and sizes of records, and the whole ClientHello, including the SNI (the host name, needed so a server with many sites can pick the right certificate) and the ALPN list. Encrypted Client Hello (ECH) encrypts the sensitive part of the ClientHello with a public key published in DNS, leaving only a shared front-end name visible. DNS itself can be hidden with DNS over HTTPS.
HTTP/2 via ALPN, and HTTP/3
The client lists the application protocols it speaks in ALPN (h2, http/1.1), and the server picks one in EncryptedExtensions, so HTTP/2 needs no extra round trip. HTTP/3 runs over QUIC on UDP, which carries the TLS 1.3 handshake inside its own: transport and crypto handshakes are merged into a single round trip (2 RTT to the first byte, or 1 with 0-RTT). This is described, not simulated.