1. The exact problem: store-now-decrypt-later against TLS 1.3
The attack model is straightforward and not theoretical. An adversary with passive collection capability — state-level SIGINT, a compromised transit provider, a malicious cloud tenant on shared infrastructure — captures TLS 1.3 ciphertext today and stores it. The handshake performs an ephemeral ECDHE over X25519 (or secp256r1); the server authenticates with an RSA-2048 or ECDSA P-256 certificate; the session is sealed under AES-256-GCM or ChaCha20-Poly1305.
The confidentiality of every byte sent over that session reduces to the hardness of the elliptic-curve discrete log problem on a 255-bit Montgomery curve. A cryptographically-relevant quantum computer running Shor's algorithm recovers the ECDHE shared secret from the captured ClientHello/ServerHello key_share values — and decrypts the entire historical session. Authentication forgery is a separate, later problem (you can't retroactively impersonate a 2026 server in 2032). Confidentiality is the urgent problem because the ciphertext is already on disk somewhere.
Hybrid key exchange solves exactly this. The TLS 1.3 shared secret is derived from the concatenation of an X25519 ECDHE output and an ML-KEM-768 encapsulation output, fed into the HKDF-Extract step of the key schedule. To recover the session key the adversary must break both primitives. If ML-KEM holds, the session is safe against future quantum cryptanalysis. If a defect is found in ML-KEM tomorrow (lattice cryptanalysis is younger than ECC was in 2005), X25519 still protects you. This is belt-and-braces by design — IETF rough consensus is that no one is yet willing to bet a TLS session on a single post-quantum primitive.
2. The primitive: ML-KEM-768 in detail
ML-KEM is the standardised form of CRYSTALS-Kyber, finalised as NIST FIPS 203 on 13 August 2024. It is a key-encapsulation mechanism — not a key-agreement protocol — built on the Module Learning With Errors (MLWE) problem over the ring Z_q[X]/(X^256 + 1) with q = 3329.
The three parameter sets are ML-KEM-512, ML-KEM-768 and ML-KEM-1024, targeting NIST security categories 1, 3 and 5 respectively. For TLS 1.3 the IETF working consensus (and the codepoint in draft-ietf-tls-mlkem / X25519MLKEM768, IANA value 0x11EC) is ML-KEM-768, matching roughly the security of AES-192 against quantum adversaries.
Concretely:
- Public key: 1184 bytes
- Ciphertext: 1088 bytes
- Shared secret: 32 bytes
- Encapsulation: ~50–80 µs on a modern x86_64 core with AVX2
- Security claim: IND-CCA2 in the ROM via the Fujisaki–Okamoto transform applied to an IND-CPA secure underlying PKE
The IND-CCA2 framing matters because TLS reuses the server's KEM key only within a single handshake — but middleboxes, session resumption logic and 0-RTT change that assumption, so CCA security is mandatory.
The combiner used in TLS 1.3 is straightforward concatenation: shared_secret = X25519(client_priv, server_pub) || ML-KEM-768.Decaps(sk, ct), fed as (EC)DHE input into HKDF. The construction is proven secure as a hybrid KEM in the standard model provided at least one component is IND-CCA2 — see draft-ietf-tls-hybrid-design for the formal argument.
3. Reference implementations, mid-2026 state
- OpenSSL 3.5 (April 2025) ships native
X25519MLKEM768as a built-in TLS group. No provider plumbing required. This is what you deploy. - BoringSSL has had
X25519Kyber768Draft00since 2023; the FIPS-203 codepointX25519MLKEM768landed in early 2025 and is what Chrome 124+ negotiates. - liboqs (Open Quantum Safe) 0.10+ provides ML-KEM-{512,768,1024} with AVX2 and AArch64 NEON paths. Use it for protocols OpenSSL doesn't yet cover natively, via
oqs-provider. - OpenSSH 9.9 (September 2024) made
mlkem768x25519-sha256the default KEX. 10.0 removed the draft Streamlined NTRU Prime hybrid. - BIND 9.20 has experimental ML-DSA DNSSEC signing — not KEM-relevant but worth tracking on the signing side.
- Cloudflare has served PQ-hybrid TLS to opted-in origins since 2023; as of 2026 it's default-on for Workers and R2.
- AWS KMS exposes
pq-hybridmode on the KMS endpoint (TLS tokms.*.amazonaws.com). GCP Cloud KMS and Azure Key Vault negotiate PQ-hybrid on the front door but the HSM-backed key material is still classical — be precise about what you're claiming.
4. Deployment pattern
For an OpenSSL 3.5 / nginx 1.27 edge, you order the groups so the hybrid is preferred but classical fallback works for legacy clients:
ssl_protocols TLSv1.3;
ssl_ecdh_curve X25519MLKEM768:X25519:secp256r1;
ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
ssl_prefer_server_ciphers off;
For a Go service on crypto/tls 1.23+:
cfg := &tls.Config{
MinVersion: tls.VersionTLS13,
CurvePreferences: []tls.CurveID{
tls.X25519MLKEM768,
tls.X25519,
tls.CurveP256,
},
}
For an internal service mesh (Envoy 1.32+), set tls_params.ecdh_curves identically. Do not remove X25519 from the list — you will break every client that hasn't shipped FIPS-203 yet, which in mid-2026 still includes most embedded TLS stacks, anything statically linked against OpenSSL 1.1.1, and a long tail of Java 8/11 services using SunJSSE.
5. What breaks in production
- Handshake size. The
ClientHellojumps from ~520 bytes to ~1750 bytes because the ML-KEM-768 public key is 1184 bytes. This crosses the typical 1500-byte MTU and triggers TCP segmentation on the very first packet. Misconfigured firewalls that drop or reorder the second segment cause silent handshake failures. Test with a 1280-byte path-MTU. - Middlebox intolerance. Old TLS-inspecting proxies (Bluecoat, older Palo Alto, some F5 vintages) reject unknown
supported_groupsvalues with ahandshake_failureinstead of falling back. ECH and PQ both surface the same class of brittle middlebox. - QUIC 0-RTT and amplification. Server
ServerHellogrows by the 1088-byte ciphertext; QUIC's anti-amplification limit (3× client bytes) bites earlier. Servers must be willing to send a Retry, or clients must pad the Initial. - Session resumption. PSK-only resumption skips the KEM. If you rely on resumption for the bulk of your traffic — most CDNs do — you only get hybrid protection on the initial full handshake. Set
ssl_session_tickets offon flows where store-now-decrypt-later is the dominant threat, or accept the trade-off knowingly. - FIPS 140-3 boundaries. ML-KEM is in FIPS 203 but vendor 140-3 module certificates lag. A FIPS-mode OpenSSL build in mid-2026 may refuse
X25519MLKEM768entirely. Check your module's security policy before you assume parity. - HSM passthrough. The KEM is performed in software on the TLS terminator, not on the HSM. Your "all crypto on the HSM" compliance posture changes — document it.
6. Verifying it actually works
First, prove the server off