Quantum Security Programme · Technical brief

ML-KEM (FIPS 203) — Kyber lattice key-encapsulation, parameters and migration ordering

← Q-Security overview

1. The exact problem ML-KEM solves

The threat is not theoretical post-quantum cryptanalysis at some indeterminate future date. The threat is harvest-now-decrypt-later (HNDL): an adversary captures TLS, IPsec, or SSH ciphertext today, stores it, and decrypts it once a cryptographically-relevant quantum computer (CRQC) running Shor's algorithm becomes available. Any session whose confidentiality must outlive that horizon is already compromised the moment it crosses a hostile network.

The classical key-establishment primitives in the field — ECDHE over P-256, X25519, finite-field DH, and RSA key transport — all reduce to problems Shor solves in polynomial time. RSA-2048 falls to roughly 4099 logical qubits per Gidney–Ekerå (2019); X25519 falls to a few thousand. Symmetric primitives degrade only quadratically under Grover, so AES-256-GCM retains ~128-bit post-quantum security and is not the migration target. The migration target is key establishment and signatures.

ML-KEM (FIPS 203, finalised 13 August 2024) is the standardised key-encapsulation mechanism intended to replace (EC)DH for confidentiality of session keys. Its function in a hybrid TLS 1.3 handshake is unambiguous: produce a shared secret that is intractable for both classical and quantum adversaries, then HKDF-combine it with the X25519 secret so that compromise of either component does not compromise the session.

2. The primitive in detail

ML-KEM is the FIPS-203 derivative of CRYSTALS-Kyber. It is a lattice-based KEM whose security reduces to Module-LWE (Module Learning With Errors) over the ring R_q = Z_q[X]/(X^256 + 1) with q = 3329. The module rank k selects the parameter set:

  • ML-KEM-512k=2, NIST Category 1 (~AES-128 PQ), public key 800 B, ciphertext 768 B
  • ML-KEM-768k=3, NIST Category 3 (~AES-192 PQ), public key 1184 B, ciphertext 1088 B — the recommended default
  • ML-KEM-1024k=4, NIST Category 5 (~AES-256 PQ), public key 1568 B, ciphertext 1568 B

The construction is a Fujisaki–Okamoto transform of an IND-CPA Kyber PKE into an IND-CCA2 KEM. Encapsulation samples a 32-byte message m, derives randomness r = G(m, H(pk)), runs deterministic IND-CPA encryption to produce ciphertext c, and outputs shared secret K = KDF(m, c). Decapsulation re-encrypts and performs implicit rejection: on a malformed c, the decapsulator returns a pseudo-random secret derived from a per-key z, never an error. This is the property that defeats Bleichenbacher-style oracles.

Note ML-KEM is a KEM, not a signature. Authentication of the handshake still requires a signature scheme — ML-DSA (FIPS 204, EUF-CMA) or SLH-DSA (FIPS 205) — or, transitionally, classical Ed25519/ECDSA-P256 certificates while the WebPKI catches up. ML-KEM does not authenticate anything on its own.

3. Reference implementations, mid-2026

  • liboqs 0.12+ (Open Quantum Safe) — FIPS-203 final, constant-time AVX2 path, the upstream most other projects vendor. Production-suitable for KEM operations; not FIPS-validated as a module.
  • OpenSSL 3.5 — ships native ML-KEM-512/768/1024 via EVP_PKEY and the X25519MLKEM768 hybrid group for TLS 1.3. The oqs-provider remains for ML-DSA/SLH-DSA until those land natively.
  • BoringSSLX25519MLKEM768 (TLS group 0x11ec, RFC 9794-aligned codepoint) is the production hybrid in Chrome since v131 (Nov 2024); the earlier X25519Kyber768Draft00 (0x6399) is being retired.
  • OpenSSH 9.9+mlkem768x25519-sha256 is the default KEX as of OpenSSH 10.0 (April 2025). Host-key signatures remain Ed25519/RSA pending ML-DSA integration.
  • BIND 9.20 — experimental ML-DSA DNSSEC algorithm allocations; ML-KEM is not relevant to DNSSEC (signatures, not encryption). Mentioned only to disambiguate.
  • CloudflareX25519MLKEM768 served on edge since March 2024; ~35% of TLS handshakes by late 2025.
  • AWS KMSpq-hybrid TLS mode for KMS API endpoints (TLS_AES_256_GCM_SHA384 with X25519MLKEM768).
  • Azure Key Vault / GCP Cloud KMS — hybrid KEM on the control plane endpoints; HSM-backed ML-KEM private keys are not yet generally available.
  • strongSwan 6.0 — IKEv2 additional key exchanges per RFC 9370, with ML-KEM-768 as ADDKE1.

OpenVPN 2.7 inherits PQ via its OpenSSL backend; WireGuard has no upstream PQ KEX and remains a gap.

4. Deployment pattern — hybrid first, always

The non-negotiable rule for 2026 deployments is hybrid: never deploy ML-KEM standalone in a production handshake. Combine it with X25519 and feed both shared secrets through HKDF. If ML-KEM is later cryptanalysed (the lattice assumptions are younger than ECDLP), the X25519 component still provides classical security; if a CRQC arrives, the ML-KEM component carries confidentiality.

Nginx 1.27 against OpenSSL 3.5, accepting hybrid only:

ssl_protocols TLSv1.3;
ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
ssl_ecdh_curve X25519MLKEM768:X25519;
ssl_conf_command Groups X25519MLKEM768:X25519;
ssl_certificate /etc/ssl/ed25519.crt;
ssl_certificate_key /etc/ssl/ed25519.key;

OpenSSH server configuration pinning hybrid KEX:

KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512@openssh.com
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512
PubkeyAcceptedAlgorithms ssh-ed25519,rsa-sha2-512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com

Programmatic encapsulation against a peer public key with OpenSSL 3.5:

EVP_PKEY *pk = /* peer ML-KEM-768 public key, 1184 B SPKI */;
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pk, NULL);
EVP_PKEY_encapsulate_init(ctx, NULL);

size_t ct_len = 1088, ss_len = 32;
unsigned char ct[1088], ss[32];
EVP_PKEY_encapsulate(ctx, ct, &ct_len, ss, &ss_len);
/* Combine with X25519 shared secret via HKDF-Extract before use */

5. What goes wrong in production

  • ClientHello fragmentation. An X25519MLKEM768 key share is 1216 bytes. The ClientHello now exceeds the initial congestion window and frequently a single TCP segment. Middleboxes that assumed ClientHello fits one MTU drop or mangle the second segment. Symptom: handshake hangs at server hello. Fix: validate path with tcpdump and replace or bypass the offending appliance.
  • Codepoint drift. Early Chrome shipped X25519Kyber768Draft00 (0x6399); the standard is 0x11ec. Servers that pin only the draft codepoint reject post-2024 clients.
  • Schedule a sovereign-cryptography assessment

    Book a security call →