applied · level 6

TLS 1.3 vs 1.2

1-RTT, 0-RTT, no CBC, no RSA, ECH — what changed and why.

220 XP

TLS 1.3 vs 1.2

The TLS 1.3 spec is not an incremental upgrade. It's a deliberate teardown of two decades of accumulated cruft, replacing nearly the entire handshake and removing ~80% of the cipher suite registry. If you've spent any time triaging TLS 1.0/1.1/1.2 attacks (BEAST, Lucky13, POODLE, FREAK, Logjam, ROBOT, …), TLS 1.3 reads like a list of "things we deleted so we'd never have to fix them again."

What changed, in one table

TLS 1.2 TLS 1.3
Full-handshake RTT 2-RTT 1-RTT
Resumption RTT 1-RTT 0-RTT (optional, replay-vulnerable)
Forward secrecy Optional (*_DHE_* / *_ECDHE_*) Mandatory
Cipher suite count 300+ registered 5 mandatory + a couple optional
Bulk cipher AES-CBC + HMAC, AES-GCM, RC4, … AEAD only (AES-GCM, ChaCha20-Poly1305, AES-CCM)
MAC HMAC-SHA-256, HMAC-SHA-1, MD5, … No separate MAC (built into AEAD)
Key exchange RSA, DH, ECDH, ECDHE, DHE, … (EC)DHE only
Signature RSA, RSA-PSS, ECDSA, DSA RSA-PSS, ECDSA, EdDSA
Compression Optional (CRIME) Removed
Renegotiation Allowed Removed (key update instead)
SNI privacy Plaintext on the wire Encrypted (ECH, optional)

This isn't a list of bug fixes. It's a list of whole feature classes deleted.

1-RTT: the speed win

In TLS 1.2 the handshake looked like:

Client                          Server
ClientHello              -->
                         <--    ServerHello
                         <--    Certificate
                         <--    ServerKeyExchange
                         <--    ServerHelloDone
ClientKeyExchange        -->
ChangeCipherSpec         -->
Finished                 -->
                         <--    ChangeCipherSpec
                         <--    Finished
[Application data]       <-->   [Application data]

Two full round trips before any application data flows. In TLS 1.3, the client speculatively sends a key_share with its ClientHello — its ephemeral DH public key. The server can derive the shared secret immediately and start encrypting:

Client                          Server
ClientHello + key_share  -->
                         <--    ServerHello + key_share
                         <--    {EncryptedExtensions}
                         <--    {Certificate}
                         <--    {CertificateVerify}
                         <--    {Finished}
{Finished}               -->
[Application data]       <-->

{...} = encrypted under the handshake key. By ServerHello, both sides have the shared secret. One round trip total before app data.

0-RTT: faster but not free

For a repeat connection (the client has resumption material from a previous session), TLS 1.3 supports 0-RTT — the client sends application data inside its ClientHello, encrypted with a key derived from the previous session.

Client                          Server
ClientHello + key_share + early_data  -->
                                     <--    ServerHello + key_share
                                     <--    [accepted/rejected]
                                     <--    {Finished}
{Finished}                            -->
[normal application data]            <-->

Zero round trips before app data flows. Sounds amazing. It's also replay-vulnerable. The "early data" can be captured and replayed by an attacker — the server has no way to tell a fresh request from a replay until later in the handshake.

OpenSSL and Go default 0-RTT off. Cloudflare turned it on with careful server-side request deduplication. The rule: only use 0-RTT for idempotent requests where replays are harmless (a GET to a cacheable resource is fine; a POST is not).

Mandatory forward secrecy

TLS 1.2 had "static RSA" key transport: the client encrypted a pre-master secret with the server's long-term RSA public key, sent it over, and that pre-master derived the session keys. If the server's private key was compromised later, anyone who recorded the traffic could decrypt it retroactively. No forward secrecy.

TLS 1.2 also had (EC)DHE ephemeral cipher suites that gave forward secrecy — but they were optional, and many real-world deployments fell back to static RSA.

TLS 1.3 removed static RSA entirely. Every TLS 1.3 connection uses ephemeral (EC)DH. The server's long-term key is used only for authentication via CertificateVerify — never to encrypt session keys. Recording today's traffic and stealing the cert later gets you nothing.

What got deleted (and why)

  • CBC + MAC-then-encrypt. Source of POODLE (SSLv3), Lucky13 (TLS 1.2), BEAST (TLS 1.0). Removed in favour of AEAD.
  • RC4. Biased keystream → recovery attacks. Banned by RFC 7465.
  • 3DES. 64-bit block → Sweet32 birthday attack. Removed.
  • Compression. TLS-level compression made CRIME possible (extracting cookies via observed compression ratios). Off by default in TLS 1.2, removed in 1.3.
  • Renegotiation. Old TLS could re-handshake mid-connection. Source of CVE-2009-3555 ("renegotiation injection"). Replaced by KeyUpdate, which derives new keys without a fresh handshake.
  • MD5, SHA-1. Removed from TLS 1.3's signature_algorithms list. SHA-256/384 minimum.
  • Custom DH groups. Replaced by a fixed registry of named groups (X25519, secp256r1/384r1/521r1, x448, ffdhe2048/3072/4096/6144/8192). Logjam-class attacks (forced downgrade to weak DH groups) are no longer possible.
  • Static DH and DH_anon. Anonymous and non-FS variants gone.

The aggressive subtraction is itself the security improvement. Fewer code paths, fewer fallbacks, fewer attack surfaces.

Encrypted Client Hello (ECH)

The one thing the TLS handshake still leaks is the SNI (Server Name Indication) — which website you're connecting to, by name, in plaintext. Anyone on path can see "this client is connecting to news.example.com" even if they can't read the rest.

ECH (replacing the earlier ESNI proposal) wraps the SNI (and other sensitive ClientHello extensions) in an HPKE-encrypted "inner ClientHello", using a public key the server publishes via DNS HTTPS records. The middlebox sees outer.example.com; the actual destination stays encrypted.

ECH is in active rollout (Cloudflare, Firefox, Chrome). It's not yet universal — DNS records have to publish the ECH key, the server has to advertise support, the client has to ask.

What's next on the wire

  • HTTP/3 (QUIC) runs TLS 1.3 inside its own handshake, baked into QUIC. Same TLS 1.3 semantics, integrated into the transport layer.
  • post-quantum hybrid key exchange (e.g., X25519+Kyber768) is being added as a TLS 1.3 named group. Cloudflare and Chrome already support it.
  • MLS (Messaging Layer Security) reuses TLS 1.3's HKDF and AEAD design but for group messaging.

If you've been thinking of TLS as "the SSL replacement that's been around forever," TLS 1.3 deserves a fresh look. It's the most aggressive simplification the IETF has shipped, and it's the foundation every modern crypto protocol builds on.

Operational checklist

  • Enable TLS 1.3 minimum on every endpoint you control. Disable TLS 1.0 / 1.1 (most CDNs already do).
  • Track the percentage of connections still on 1.2. It should be falling. If it's not, find out who.
  • Disable 0-RTT for non-idempotent paths, or implement server-side replay defence.
  • Plan for ECH — it's coming, and the DNS-record + server-config setup is non-trivial.
  • Watch post-quantum. Hybrid X25519+Kyber will roll out in cipher suite negotiations over the next few years. Make sure your TLS libraries can be upgraded.

TLS 1.3 is the most worthwhile cryptographic protocol upgrade in a generation. If you're operating any service in 2024+, you should be on it everywhere.

Tools in the wild

4 tools
  • Test any TLS endpoint. `openssl s_client -tls1_3 -connect host:443` shows the negotiated protocol + cipher.

    cli
  • Wiresharkfree tier

    Set SSLKEYLOGFILE to dump session keys; Wireshark can decrypt full TLS 1.3 traffic offline for debugging.

    cli
  • ssllabs.comfree tier

    Comprehensive TLS scanner. Shows version support, cipher list, ECH/HSTS/HPKP status.

    service
  • Byte-by-byte walkthrough of a TLS 1.3 handshake. Best learning resource for the wire format.

    service