CS Fundamentals Trainer · Guides

TLS and HTTPS

How the TLS 1.3 and 1.2 handshakes work message by message, why forward secrecy and certificates matter, what resumption and 0-RTT cost, and the mistakes that cause outages.

13 min read

TLS (Transport Layer Security) is what puts the "s" in https. It does three jobs for a connection: encryption (nobody on the path can read the data), integrity (nobody can change it without being noticed), and authentication (you are really talking to the server you meant to). Almost every question about it comes down to how the two sides agree on a secret key while strangers listen, and why you can trust who you agreed it with. This guide follows a real handshake, message by message.

1. What problem TLS solves

HTTP over plain TCP sends everything in the clear. Anyone between you and the server (a Wi-Fi owner, an internet provider, a compromised router) can read it, and can also change it. TLS wraps the TCP connection so that:

What TLS does not hide: the server's IP address, the amount and timing of the traffic, and (usually) the server name sent in the first message. And it does not make the server honest: a phishing site can have a perfectly valid certificate for its own name.

2. The ideas it is built from

Three building blocks, and TLS is the recipe that combines them.

3. The handshake, message by message

I captured the real thing: a local server with a self-signed certificate, and openssl s_client -msg as the client. These are the handshake messages in the order they went over the wire, TLS 1.3 first.

TLS 1.3 (RFC 8446) [1]

client → server   ClientHello           versions, cipher suites, key share (X25519 public value), server name (SNI)
server → client   ServerHello           chosen cipher suite, the server's key share
                  ---- from here on everything is encrypted with keys both sides can now compute ----
server → client   EncryptedExtensions   extra settings (for example the application protocol, h2 or http/1.1)
server → client   Certificate           the certificate chain
server → client   CertificateVerify     a signature over the whole handshake so far, made with the certificate's private key
server → client   Finished              a MAC over the handshake: "this is what I saw"
client → server   ChangeCipherSpec      a leftover with no meaning in 1.3, sent so that middleboxes that expect TLS 1.2 do not drop the connection
client → server   Finished              the same, from the client
                  ---- the application data (the HTTP request) can start now ----

The client's summary line from the same run: TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384, Server Temp Key: X25519, Verification: OK.

Notice the trick that makes it fast: the client guesses the key exchange in its very first message (it already sends its X25519 value in ClientHello). The server answers with its own value in ServerHello, and from that moment both can compute the keys. A full handshake takes one round trip before the client can send its request.

TLS 1.2 (RFC 5246) for comparison

The same client forced to TLS 1.2 (-tls1_2):

client → server   ClientHello
server → client   ServerHello
server → client   Certificate           (sent in the clear)
server → client   ServerKeyExchange     the server's ECDHE value, signed
server → client   ServerHelloDone
client → server   ClientKeyExchange     the client's ECDHE value
client → server   ChangeCipherSpec
client → server   Finished
server → client   NewSessionTicket
server → client   ChangeCipherSpec
server → client   Finished

TLS 1.2 does not guess: the client first asks what is on offer, and only then sends its key value. That costs two round trips. The certificate also travels unencrypted, so an observer can see which certificate you were shown.

What the later versions changed

TLS 1.2TLS 1.3
Round trips before the request21
Certificate visible to observersyesno (encrypted)
RSA key exchange (no forward secrecy)allowedremoved
Weak ciphers and old features (CBC suites, compression, renegotiation)possibleremoved
Resumptionsession IDs and ticketspre-shared keys

TLS 1.0 and 1.1 are deprecated (RFC 8996). A server today should offer 1.3 and, for older clients, 1.2.

4. Certificates and the chain of trust

The certificate is a signed statement: "this public key belongs to these names", with validity dates. Who vouches for it?

Diagram: Chain of trust from the server to a root your device already has

5. The things around the handshake

6. What it costs

Latency is counted in round trips (RTT). Before the first byte of a response arrives on a new connection:

SetupRound tripsAt 100 ms RTT
TCP + TLS 1.2 + HTTP request1 (TCP) + 2 (TLS) + 1 (request) = 4400 ms
TCP + TLS 1.3 + HTTP request1 + 1 + 1 = 3300 ms
QUIC / HTTP/3 (transport and TLS 1.3 together)1 + 1 = 2200 ms
TCP + TLS 1.3, resumed, with 0-RTT (the request rides in the first flight)1 (TCP) + 1 (request and response) = 2200 ms
QUIC, resumed, with 0-RTT1100 ms

On my own machine, where the round trip is almost zero, curl reported tcp 0.0002 s, tls about 0.0035 s and first byte about 0.004 s (three runs: 3.3 to 4.1 ms for TLS): the public-key work in the handshake took about 3.5 ms, and the actual cost of TLS on a real network is the round trips, not the cryptography. That is why connection reuse (keep-alive, HTTP/2 multiplexing) and CDNs close to the user matter more than any cipher choice.

The tool to see this yourself:

curl -sS -o /dev/null -w 'dns %{time_namelookup}s  tcp %{time_connect}s  tls %{time_appconnect}s  first byte %{time_starttransfer}s\n' https://example.com/

Each number is cumulative: the TLS time includes the DNS and TCP time before it.

7. Trying it yourself

Make a certificate and a server, and watch the handshake:

# 1. a key and a self-signed certificate for localhost
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -nodes \
  -keyout k.pem -out c.pem -subj "/CN=localhost" -days 2 -addext "subjectAltName=DNS:localhost"

# 2. a test server
openssl s_server -accept 4443 -cert c.pem -key k.pem -www

# 3. in another terminal: the handshake messages, for TLS 1.3 and then TLS 1.2
openssl s_client -connect localhost:4443 -tls1_3 -msg -CAfile c.pem -servername localhost
openssl s_client -connect localhost:4443 -tls1_2 -msg -CAfile c.pem -servername localhost

Look for the lines starting <<< (from the server) and >>> (from the client). Then try -verify_hostname wrong.example and leave out -CAfile c.pem to see the two failures.

In code, the safe defaults are the whole point: use the language's standard HTTPS client, which verifies the chain and the name. For example (pick a language on the tabs; your choice is remembered):

import ssl
import urllib.request

ctx = ssl.create_default_context()                     # verifies the chain and the host name
ctx.minimum_version = ssl.TLSVersion.TLSv1_2           # refuse the old versions
with urllib.request.urlopen("https://example.com/", context=ctx, timeout=10) as r:
    print(r.status, r.headers.get("strict-transport-security"))
client := &http.Client{
	Timeout: 10 * time.Second,
	Transport: &http.Transport{
		TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12}, // verification is on by default
	},
}
resp, err := client.Get("https://example.com/")
// Node verifies the chain and the host name by default; never set rejectUnauthorized: false
const https = require("node:https");
https.get("https://example.com/", { minVersion: "TLSv1.2" }, (res) => {
  console.log(res.statusCode, res.headers["strict-transport-security"]);
});
var handler = new SocketsHttpHandler
{
    SslOptions = { EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13 }  // verification is on by default
};
using var http = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(10) };
var response = await http.GetAsync("https://example.com/");

8. Mistakes that come up again and again

9. Questions interviewers like to add

"Why not just use the server's public key to encrypt the key?"

You can (this was RSA key exchange in old TLS), but then anyone who later steals the server's private key can decrypt all recorded traffic. Ephemeral Diffie-Hellman gives forward secrecy, and TLS 1.3 made it mandatory.

"How does the client know the certificate is not forged?"

It checks the CA's signature on it, up a chain to a root it already trusts. The trust comes from the root store, which is part of your operating system or browser.

"What does a man in the middle need to defeat TLS?"

A certificate that the client trusts for that name. That is why compromised CAs, installed corporate root certificates, and users clicking through warnings are the real attacks, not breaking the encryption. Certificate Transparency logs and HSTS make some of these easier to detect and harder to exploit.

"What is the difference between TLS and HTTPS?"

HTTPS is HTTP carried over TLS. TLS also protects other protocols: SMTP, IMAP, databases, gRPC, MQTT.

"What changes with HTTP/3?"

It runs over QUIC (on UDP), which builds TLS 1.3 into the transport: one combined handshake, and no head-of-line blocking between streams. The cryptography is the same.

10. Things to mention in an interview

Quick recap

  1. TLS gives encryption, integrity, and server authentication on top of TCP.
  2. A fresh key exchange (ECDHE) per connection gives forward secrecy; a signature plus a certificate proves who you exchanged with.
  3. TLS 1.3 needs one round trip and encrypts the certificate; TLS 1.2 needs two.
  4. Certificates form a chain to a root in your trust store, and the name in the certificate must match the one you asked for.
  5. Resumption saves handshakes; 0-RTT saves a round trip but can be replayed, so use it only for safe requests.
  6. The real cost of TLS is round trips, not cryptography. Reuse connections.
  7. Never turn verification off; automate renewal; serve the whole chain.

Sources

  1. IETF, RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3. The handshake, key schedule, resumption, and 0-RTT.
  2. IETF, RFC 8470: Using Early Data in HTTP. How 0-RTT replay is handled in HTTP.
  3. IETF, RFC 5246: TLS Version 1.2. The older handshake used for comparison.
  4. IETF, RFC 6066: TLS Extensions (SNI) and RFC 7301: ALPN.
  5. IETF, RFC 6797: HTTP Strict Transport Security.
  6. IETF, RFC 8996: Deprecating TLS 1.0 and TLS 1.1.
  7. OpenSSL documentation, s_client and s_server. The tools used in the experiment.
  8. Let's Encrypt, How it works (automatic certificates with ACME) and Certificate lifetime rationale and plans (the 90-day lifetime and renewal).

Practise this topic: Networking Security

More guides