feat: add post-quantum hybrid KEM + SQLCipher persistence
Feature 1 — Post-Quantum Hybrid KEM (X25519 + ML-KEM-768): - Create hybrid_kem.rs with keygen, encrypt, decrypt + 11 unit tests - Wire format: version(1) | x25519_eph_pk(32) | mlkem_ct(1088) | nonce(12) | ct - Add uploadHybridKey/fetchHybridKey RPCs to node.capnp schema - Server: hybrid key storage in FileBackedStore + RPC handlers - Client: hybrid keypair in StoredState, auto-wrap/unwrap in send/recv/invite/join - demo-group runs full hybrid PQ envelope round-trip Feature 2 — SQLCipher Persistence: - Extract Store trait from FileBackedStore API - Create SqlStore (rusqlite + bundled-sqlcipher) with encrypted-at-rest SQLite - Schema: key_packages, deliveries, hybrid_keys tables with indexes - Server CLI: --store-backend=sql, --db-path, --db-key flags - 5 unit tests for SqlStore (FIFO, round-trip, upsert, channel isolation) Also includes: client lib.rs refactor, auth config, TOML config file support, mdBook documentation, and various cleanups by user. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
256
docs/src/cryptography/post-quantum-readiness.md
Normal file
256
docs/src/cryptography/post-quantum-readiness.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# Post-Quantum Readiness
|
||||
|
||||
quicnprotochat 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/quicnprotochat-core/src/hybrid_kem.rs`
|
||||
|
||||
## Current State
|
||||
|
||||
The hybrid KEM is **fully implemented and tested** in `quicnprotochat-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. quicnprotochat 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="quicnprotochat-hybrid-v1", L=32)
|
||||
nonce = HKDF-SHA256(salt=[], ikm, info="quicnprotochat-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 (ADR-006)
|
||||
|
||||
There is an important asymmetry in quicnprotochat's post-quantum protection:
|
||||
|
||||
```text
|
||||
Layer Classical Protection Post-Quantum Protection
|
||||
---------------------------------------------------------------------
|
||||
QUIC/TLS 1.3 Yes (ECDHE) No
|
||||
Noise_XX Yes (X25519) 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 cryptography. A quantum attacker who recorded the
|
||||
TLS/Noise 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, quicnprotochat
|
||||
will adopt it to close the PQ gap at the transport layer.
|
||||
|
||||
Similarly, post-quantum Noise patterns are an active research area but are not
|
||||
yet standardized. The `snow` crate does not currently support post-quantum DH
|
||||
primitives.
|
||||
|
||||
## 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 quicnprotochat'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/Noise
|
||||
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
|
||||
Reference in New Issue
Block a user