Rename the entire workspace:
- Crate packages: quicnprotochat-{core,proto,server,client,gui,p2p,mobile} -> quicproquo-*
- Binary names: quicnprotochat -> qpq, quicnprotochat-server -> qpq-server,
quicnprotochat-gui -> qpq-gui
- Default files: *-state.bin -> qpq-state.bin, *-server.toml -> qpq-server.toml,
*.db -> qpq.db
- Environment variable prefix: QUICNPROTOCHAT_* -> QPQ_*
- App identifier: chat.quicnproto.gui -> chat.quicproquo.gui
- Proto package: quicnprotochat.bench -> quicproquo.bench
- All documentation, Docker, CI, and script references updated
HKDF domain-separation strings and P2P ALPN remain unchanged for
backward compatibility with existing encrypted state and wire protocol.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
252 lines
11 KiB
Markdown
252 lines
11 KiB
Markdown
# Post-Quantum Readiness
|
|
|
|
quicproquo 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/quicproquo-core/src/hybrid_kem.rs`
|
|
|
|
## Current State
|
|
|
|
The hybrid KEM is **fully implemented and tested** in `quicproquo-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. quicproquo 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
|
|
|
|
```text
|
|
ikm = X25519_shared_secret(32 bytes) || ML-KEM_shared_secret(32 bytes)
|
|
key = HKDF-SHA256(salt=[], ikm, info="quicproquo-hybrid-v1", L=32)
|
|
nonce = HKDF-SHA256(salt=[], ikm, info="quicproquo-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:
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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 quicproquo's post-quantum protection:
|
|
|
|
```text
|
|
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, quicproquo
|
|
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 quicproquo'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.
|
|
|
|
## Related Pages
|
|
|
|
- [Cryptography Overview](overview.md) -- algorithm inventory including ML-KEM-768
|
|
- [Key Lifecycle and Zeroization](key-lifecycle.md) -- hybrid KEM key lifecycle
|
|
- [Forward Secrecy](forward-secrecy.md) -- how FS interacts with PQ protection
|
|
- [Threat Model](threat-model.md) -- harvest-now-decrypt-later in context
|
|
- [Hybrid KEM: X25519 + ML-KEM-768](../protocol-layers/hybrid-kem.md) -- protocol layer details
|