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:
- Confidentiality: the data is encrypted with a key that only the two ends know.
- Integrity: every record carries an authentication tag; a changed byte makes it fail.
- Server authentication: the server proves it holds a certificate for the name you asked for, issued by an authority your device trusts. (The client can be authenticated too, with a client certificate. That is called mutual TLS, or mTLS.)
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.
- Symmetric encryption (AES-GCM, ChaCha20-Poly1305): one shared key encrypts and decrypts. It is fast, so it protects all the real data. Its problem: both sides need the same key, and you cannot send the key in the clear.
- Key exchange (Diffie-Hellman, usually the elliptic-curve version, ECDHE): two parties who have never met each exchange public values and each compute the same secret. An eavesdropper sees the public values but cannot compute the secret. The keys are made fresh for every connection ("ephemeral"), which gives forward secrecy: if the server's long-term key is stolen next year, recorded traffic from today still cannot be decrypted.
- Digital signatures and certificates: key exchange alone does not tell you who you exchanged with. A man in the middle could run one exchange with you and another with the server. So the server signs the handshake with the private key matching its certificate, and the certificate says "this public key belongs to
example.com", signed by a certificate authority (CA).
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.2 | TLS 1.3 | |
|---|---|---|
| Round trips before the request | 2 | 1 |
| Certificate visible to observers | yes | no (encrypted) |
| RSA key exchange (no forward secrecy) | allowed | removed |
| Weak ciphers and old features (CBC suites, compression, renegotiation) | possible | removed |
| Resumption | session IDs and tickets | pre-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
- The server sends its leaf certificate and the intermediates. The client already has the roots in a trust store shipped with the operating system or browser.
- The client checks every signature up the chain, the dates, that the chain ends in a trusted root, that the certificate has not been revoked (via OCSP stapling or short lifetimes), and that the name you asked for is listed in the certificate (the Subject Alternative Name field). The name check is what stops a valid certificate for one site from being used for another.
- Two failures I produced on purpose: connecting to my server with
curland no trusted root givesSSL certificate problem: self-signed certificate, andopenssl s_client -verify_hostname wrong.examplegivesVerify return code: 62 (hostname mismatch). Never "fix" these by turning verification off (curl -k,verify=False): that gives you encryption without knowing who is at the other end, which is exactly the attack TLS exists to prevent. - Let's Encrypt and ACME made certificates free and automatic: the CA checks that you control the domain (by a file on your website or a DNS record), and your server renews the certificate without a human. Let's Encrypt certificates are valid for 90 days, and it recommends renewing well before that (every 60 days) [8]. Short lifetimes also reduce the damage of a stolen key.
5. The things around the handshake
- SNI (Server Name Indication, RFC 6066): one IP address may host hundreds of sites, so the client says in
ClientHellowhich name it wants, and the server picks the matching certificate. Normally this name is visible to observers. Encrypted Client Hello (ECH) is the newer fix. - ALPN (RFC 7301): in the same exchange the client lists the application protocols it speaks (
h2,http/1.1) and the server picks one. That is how HTTP/2 gets agreed on without an extra round trip. - Session resumption: after a first handshake the server gives the client a ticket. Next time the client presents it and skips the certificate and the expensive public-key work. In TLS 1.3 the client may even send its first request along with the
ClientHello(0-RTT early data). The price: an attacker who records that first flight can replay it. So 0-RTT is only safe for requests that can be repeated without harm (aGETof a public page), never for "pay" or "transfer"; servers can refuse early data, and HTTP has a status to say "try again after the handshake" (RFC 8470) [2]. - HSTS (RFC 6797): a header (
Strict-Transport-Security: max-age=31536000) telling browsers "only ever use HTTPS for this site". It closes the gap where a first request goes out over plainhttp://and could be hijacked. - mTLS: the server also asks the client for a certificate. Common between services inside a company (service meshes automate it), rare for browsers.
6. What it costs
Latency is counted in round trips (RTT). Before the first byte of a response arrives on a new connection:
| Setup | Round trips | At 100 ms RTT |
|---|---|---|
| TCP + TLS 1.2 + HTTP request | 1 (TCP) + 2 (TLS) + 1 (request) = 4 | 400 ms |
| TCP + TLS 1.3 + HTTP request | 1 + 1 + 1 = 3 | 300 ms |
| QUIC / HTTP/3 (transport and TLS 1.3 together) | 1 + 1 = 2 | 200 ms |
| TCP + TLS 1.3, resumed, with 0-RTT (the request rides in the first flight) | 1 (TCP) + 1 (request and response) = 2 | 200 ms |
| QUIC, resumed, with 0-RTT | 1 | 100 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
- Turning verification off to "make the error go away". Fix the cause: install the right root, fix the name, renew the certificate.
- An expired certificate. The most common outage of all. Automate renewal and alert on expiry well in advance.
- A missing intermediate. The site works in your browser (which caches intermediates) but fails in
curlor in a mobile app. Always serve the full chain. - Mixed content: an HTTPS page loading a script over HTTP. Browsers block it, and it would void the protection anyway.
- Wrong name: the certificate covers
example.combut notwww.example.com, or a wildcard*.example.comthat does not coverexample.comitself. - Terminating TLS at a load balancer and sending plain HTTP behind it, on a network you do not fully control. Encrypt inside too when the data warrants it (see the load balancing guide).
- Old protocol versions left on. Disable TLS 1.0 and 1.1; keep 1.2 only if you need old clients.
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
- Name the three jobs: encryption, integrity, authentication.
- Explain ECDHE and forward secrecy in one sentence, and why signatures are needed on top.
- Walk through the TLS 1.3 handshake and say it takes one round trip, against two in 1.2.
- Explain the chain of trust and the name check.
- Mention SNI, ALPN, resumption, and the replay risk of 0-RTT.
- Say what TLS does not protect (metadata, the server's honesty).
Quick recap
- TLS gives encryption, integrity, and server authentication on top of TCP.
- A fresh key exchange (ECDHE) per connection gives forward secrecy; a signature plus a certificate proves who you exchanged with.
- TLS 1.3 needs one round trip and encrypts the certificate; TLS 1.2 needs two.
- Certificates form a chain to a root in your trust store, and the name in the certificate must match the one you asked for.
- Resumption saves handshakes; 0-RTT saves a round trip but can be replayed, so use it only for safe requests.
- The real cost of TLS is round trips, not cryptography. Reuse connections.
- Never turn verification off; automate renewal; serve the whole chain.
Sources
- IETF, RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3. The handshake, key schedule, resumption, and 0-RTT.
- IETF, RFC 8470: Using Early Data in HTTP. How 0-RTT replay is handled in HTTP.
- IETF, RFC 5246: TLS Version 1.2. The older handshake used for comparison.
- IETF, RFC 6066: TLS Extensions (SNI) and RFC 7301: ALPN.
- IETF, RFC 6797: HTTP Strict Transport Security.
- IETF, RFC 8996: Deprecating TLS 1.0 and TLS 1.1.
- OpenSSL documentation, s_client and s_server. The tools used in the experiment.
- 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