Files
quicproquo/docs/src/cryptography/post-quantum-readiness.md
Christian Nennemann 2e081ead8e chore: rename quicproquo → quicprochat in docs, Docker, CI, and packaging
Rename all project references from quicproquo/qpq to quicprochat/qpc
across documentation, Docker configuration, CI workflows, packaging
scripts, operational configs, and build tooling.

- Docker: crate paths, binary names, user/group, data dirs, env vars
- CI: workflow crate references, binary names, artifact names
- Docs: all markdown files under docs/, SDK READMEs, book.toml
- Packaging: OpenWrt Makefile, init script, UCI config (file renames)
- Scripts: justfile, dev-shell, screenshot, cross-compile, ai_team
- Operations: Prometheus config, alert rules, Grafana dashboard
- Config: .env.example (QPQ_* → QPC_*), CODEOWNERS paths
- Top-level: README, CONTRIBUTING, ROADMAP, CLAUDE.md
2026-03-21 19:14:06 +01:00

11 KiB

Post-Quantum Readiness

quicprochat includes a fully implemented and tested hybrid key encapsulation mechanism (KEM) combining X25519 (classical) with ML-KEM-768 (post-quantum). This page describes the current implementation, the integration plan, the security rationale, and the known gaps.

Source: crates/quicprochat-core/src/hybrid_kem.rs

Current State

The hybrid KEM is fully implemented and tested in quicprochat-core. The implementation provides:

  • HybridKeypair::generate() -- generate a combined X25519 + ML-KEM-768 keypair
  • hybrid_encrypt(recipient_pk, plaintext) -- encrypt to a hybrid public key
  • hybrid_decrypt(keypair, envelope) -- decrypt with the hybrid private key
  • Serialization/deserialization for both keypairs and public keys
  • Comprehensive test coverage: round-trip, wrong-key rejection, tampering detection, version validation, large payload handling

The test suite in hybrid_kem.rs includes 10 tests covering:

  • Basic encrypt/decrypt round-trip
  • Wrong key decryption failure
  • Tampered AEAD ciphertext detection
  • Tampered ML-KEM ciphertext detection
  • Tampered X25519 ephemeral public key detection
  • Unsupported version rejection
  • Envelope-too-short rejection
  • Keypair serialization round-trip
  • Public key serialization round-trip
  • Large payload (50 KB) round-trip

ML-KEM-768 (FIPS 203)

ML-KEM (Module-Lattice-Based Key Encapsulation Mechanism) is the NIST-standardized post-quantum KEM, published as FIPS 203. quicprochat uses ML-KEM-768, the middle parameter set:

Parameter Set NIST Level Security (PQ) EK Size CT Size SS Size
ML-KEM-512 1 ~128-bit 800 B 768 B 32 B
ML-KEM-768 3 ~192-bit 1184 B 1088 B 32 B
ML-KEM-1024 5 ~256-bit 1568 B 1568 B 32 B

Why ML-KEM-768 (not 512 or 1024)?

  • Level 3 provides a strong security margin. The ~192-bit post-quantum security level exceeds the 128-bit classical security of the rest of the system, providing headroom against future cryptanalytic advances.
  • Moderate key and ciphertext sizes. The encapsulation key (1184 bytes) and ciphertext (1088 bytes) are large compared to X25519 (32 bytes each) but manageable for a messaging protocol. ML-KEM-1024 would add ~400 bytes to each with diminishing returns.
  • Consistent with industry practice. Signal, Google Chrome, and Cloudflare have all deployed ML-KEM-768 (or its predecessor Kyber-768) in production.

The ml-kem 0.2 crate provides a pure-Rust implementation of FIPS 203 with all three parameter sets compiled in by default.

Hybrid Construction

The hybrid KEM follows the combiner approach described in draft-ietf-tls-hybrid-design. Both a classical and a post-quantum KEM are executed, and their shared secrets are combined through a KDF:

Key Derivation

ikm  = X25519_shared_secret(32 bytes) || ML-KEM_shared_secret(32 bytes)
key  = HKDF-SHA256(salt=[], ikm, info="quicprochat-hybrid-v1", L=32)
nonce = HKDF-SHA256(salt=[], ikm, info="quicprochat-hybrid-nonce-v1", L=12)

The combined IKM (input key material) is wrapped in Zeroizing<Vec<u8>> and cleared after HKDF expansion.

Why a Hybrid?

A pure ML-KEM deployment would be vulnerable if lattice-based cryptography is broken (which, while considered unlikely, cannot be ruled out for a newly standardized algorithm). A pure X25519 deployment provides no post-quantum protection. The hybrid approach provides a "belt and suspenders" guarantee:

  • If ML-KEM-768 is broken, X25519 still provides 128-bit classical security.
  • If X25519 is broken (by a quantum computer), ML-KEM-768 still provides ~192-bit post-quantum security.
  • Both must be broken simultaneously to compromise the shared secret.

Wire Format

The hybrid envelope uses a versioned binary format:

Offset  Length  Field
------  ------  -----
0       1       Version byte (0x01)
1       32      X25519 ephemeral public key
33      1088    ML-KEM-768 ciphertext
1121    12      ChaCha20-Poly1305 nonce
1133    var     ChaCha20-Poly1305 AEAD ciphertext (plaintext + 16-byte tag)

Total overhead: 1133 bytes of header + 16 bytes of AEAD tag = 1149 bytes per message (in addition to the plaintext length).

The version byte enables future algorithm agility. Version 0x01 denotes X25519 + ML-KEM-768 + HKDF-SHA256 + ChaCha20-Poly1305.

Encryption Flow

Sender                                              Recipient
------                                              ---------
                                                    HybridKeypair::generate()
                                                    ├── x25519_sk, x25519_pk
                                                    └── mlkem_dk, mlkem_ek
                                                         │
                                              public_key()│
                                                         │
    ┌─── HybridPublicKey ◄──────────────────────────────┘
    │    (x25519_pk: 32B, mlkem_ek: 1184B)
    │
    │  hybrid_encrypt(pk, plaintext):
    │  1. eph_sk = EphemeralSecret::random()
    │  2. eph_pk = PublicKey::from(&eph_sk)
    │  3. x25519_ss = eph_sk.diffie_hellman(pk.x25519_pk)
    │  4. (mlkem_ct, mlkem_ss) = pk.mlkem_ek.encapsulate()
    │  5. ikm = x25519_ss || mlkem_ss
    │  6. (key, nonce) = HKDF(ikm, info)
    │  7. ct = ChaCha20Poly1305::encrypt(key, nonce, plaintext)
    │  8. envelope = ver || eph_pk || mlkem_ct || nonce || ct
    │
    └──────────── envelope ──────────────────────────────►
                                                         │
                                      hybrid_decrypt(kp, envelope):
                                      1. Parse ver, eph_pk, mlkem_ct, nonce, ct
                                      2. x25519_ss = kp.x25519_sk.dh(eph_pk)
                                      3. mlkem_ss = kp.mlkem_dk.decapsulate(mlkem_ct)
                                      4. ikm = x25519_ss || mlkem_ss
                                      5. (key, _) = HKDF(ikm, info)
                                      6. plaintext = ChaCha20Poly1305::decrypt(key, nonce, ct)

Integration Plan (M5)

The hybrid KEM is currently a standalone module. Milestone M5 will integrate it into the MLS pipeline by creating a custom OpenMlsCryptoProvider that uses the hybrid KEM for HPKE init key exchange:

  1. Custom crypto provider: Wrap the existing StoreCrypto with a hybrid KEM layer that intercepts HPKE operations and replaces the classical X25519 DHKEM with the hybrid X25519 + ML-KEM-768 KEM.

  2. KeyPackage extension: Store the hybrid public key (1216 bytes: 32B X25519 + 1184B ML-KEM) in a custom MLS extension within the KeyPackage.

  3. Welcome encryption: When creating a Welcome message, the hybrid KEM encrypts the group secrets instead of (or in addition to) the standard HPKE.

  4. Backward compatibility: Groups can negotiate whether to use hybrid KEM via the MLS group context extensions. Classical-only clients can still participate in groups that do not require PQ protection.

The PQ Gap

There is an important asymmetry in quicprochat's post-quantum protection:

Layer                  Classical Protection    Post-Quantum Protection
---------------------------------------------------------------------
QUIC/TLS 1.3           Yes (ECDHE)             No
MLS content (M5+)      Yes (X25519 DHKEM)      Yes (hybrid KEM)

What this means:

  • Message content (the MLS application data) is protected against quantum adversaries from M5 onward. An attacker with a quantum computer cannot decrypt the message payload.

  • Transport metadata (who connects to the server, when, message sizes) is protected only by classical ECDHE. A quantum attacker who recorded the TLS handshake transcripts could, in theory, recover the transport session keys and observe the metadata.

This is the PQ gap: content is safe, but metadata is not.

Why not PQ transport?

Post-quantum TLS (via ML-KEM in the TLS 1.3 handshake) is being standardized by the IETF and is supported by some TLS libraries, but rustls does not yet support it in a stable release. When rustls adds ML-KEM support, quicprochat will adopt it to close the PQ gap at the transport layer.

Harvest-Now, Decrypt-Later Risk

The "harvest-now, decrypt-later" (HNDL) threat model assumes an adversary who:

  1. Records all encrypted traffic today (inexpensive storage).
  2. Waits for a sufficiently powerful quantum computer (years or decades).
  3. Decrypts the recorded traffic retroactively.

In quicprochat's case:

  • Content is safe from M5 onward. The hybrid KEM wrapping MLS content uses ML-KEM-768, which resists quantum attacks. Even if the recorded traffic is decrypted at the transport layer, the MLS ciphertext inside is still protected.

  • Transport metadata is at risk. An HNDL attacker who records TLS handshakes today could, with a future quantum computer, recover the transport session keys and observe:

    • Which clients connected to the server and when.
    • Message sizes and timing patterns.
    • The encrypted MLS blobs (which they still cannot decrypt if hybrid KEM is active).
  • Content before M5 is at risk. Messages sent before the hybrid KEM integration (M5) use classical-only MLS encryption. If the HPKE init key exchange used X25519-only DHKEM, a quantum attacker could recover the HPKE shared secret and decrypt the Welcome message, gaining access to the group state.

This risk is the primary motivation for deploying the hybrid KEM as early as possible.

Key Sizes and Performance

Component Key/Ciphertext Size
X25519 public key x25519_pk 32 bytes
X25519 shared secret DH result 32 bytes
ML-KEM-768 encapsulation key mlkem_ek 1,184 bytes
ML-KEM-768 decapsulation key mlkem_dk 2,400 bytes
ML-KEM-768 ciphertext mlkem_ct 1,088 bytes
ML-KEM-768 shared secret KEM result 32 bytes
Hybrid public key x25519_pk + mlkem_ek 1,216 bytes
Hybrid envelope overhead Header + AEAD tag 1,149 bytes

The ML-KEM-768 operations (keygen, encapsulate, decapsulate) are fast in software -- typically sub-millisecond on modern hardware. The primary cost is bandwidth, not computation.