Messagerie PQ hybride X25519+Kyber-768 (chantier M6)
source : docs/adr/0007-messaging-pq-hybrid-m6.md · versionné · MADR-lite
ADR-0007 — Messagerie PQ hybride X25519+Kyber-768 (chantier M6)
- Statut : Proposed (travaux en cours M6)
- Date : 2026-04-24
- Décideurs : Core team OmbrysWeb + audit crypto externe (à commander)
- Issues GitHub : #76 (impl), #70 (audit crypto)
- Références : ADR-0002 (crypto strategy), ADR-0006 (ECIES M5) ;
docs/security/crypto-spec.md § 1.5
Contexte
ADR-0006 a fixé pour M5 une messagerie ECIES per-message P-256 — forward secrecy côté sender, mais pas de résistance post-quantique. M6 upgrade vers un handshake hybride pour mitiger le vecteur « harvest now, decrypt later » mentionné dans le threat model (acteur A9).
Décision
Le handshake Alice → Bob devient hybride X25519 + Kyber-768 :
Alice (sender) :
1. Génère paire éphémère X25519 (eph_x_sk, eph_x_pk).
2. ECDH(eph_x_sk, bob_x_pk) = ss_x (32 octets)
3. Kyber.Encapsulate(bob_kyber_pk) = (kyber_ct, ss_k) (ss_k = 32 octets)
4. Combined = ss_x || ss_k (64 octets)
5. K = HKDF-SHA-384(
ikm = Combined,
salt = eph_x_pk_spki || kyber_ct,
info = "ombrys.msg/v2",
L = 32
)
6. Encrypt AES-256-GCM(K, nonce, plaintext, aad).
7. Envelope = { eph_x_pk_spki, kyber_ct, nonce, aad, ciphertext }.
8. Drop eph_x_sk.
Bob (recipient) :
1. ECDH(bob_x_sk, eph_x_pk) = ss_x
2. Kyber.Decapsulate(bob_kyber_sk, kyber_ct) = ss_k
3. Combined = ss_x || ss_k
4. K = HKDF-SHA-384( ... same params ... )
5. Decrypt AES-256-GCM.Bob publie deux clés publiques long-terme : - bob_x_pk : ECDH X25519 (SPKI). - bob_kyber_pk : Kyber-768 public key (raw bytes, ~1184 octets).
Les deux sont partagées hors-bande (fingerprint combiné) ou via le canal de provisioning M7.
Conséquences
Positives
- Résistance quantique : un attaquant quantique futur doit casser à la
fois X25519 ET Kyber-768 pour récupérer le shared secret. - Design hybride conservateur : si Kyber s'avère faible, X25519 protège encore ; si X25519 s'avère faible (peu probable avant 20+ ans), Kyber protège. - Format v2 (info = "ombrys.msg/v2") distinct → coexistence des anciens messages M5 v1 lisibles tant que la clé long-terme existe.
Négatives
- Taille envelope augmentée : Kyber-768 ciphertext = 1088 octets en
plus dans chaque envelope. Impact : un message de 100 caractères devient ~1.3 KB au lieu de ~200 octets. Acceptable pour du chat texte, à évaluer pour les transferts de fichiers (on peut garder v1 pour les chunks et v2 seulement pour les manifests). - Dépendance liboqs côté client (WASM) : ajoute ~400 KB au bundle frontend après compression. À évaluer si on le charge à la demande (lazy import sur /dashboard/messages) pour ne pas pénaliser les pages publiques. - Implémentation côté navigateur : WebCrypto ne fournit pas Kyber. Options évaluées : - liboqs-wasm (upstream Open Quantum Safe) : le plus portable, déjà utilisé en CI pour les tests Rust côté serveur. - Fork noble-post-quantum : lib pure-JS (~150 KB gzipped), en croissance mais pas encore mûre. - Compilation du crate ombrys-pq vers WASM via wasm-bindgen : contrôle total, ré-utilise exactement la logique serveur. Choix privilégié si la compilation est raisonnable (<200 KB). - Audit crypto obligatoire : ADR-0002 demandait déjà un audit crypto externe (issue #70). Cette upgrade ne peut pas être déployée en prod sans cet audit validant le format de combinaison et l'usage de HKDF.
Neutres
- Pas d'impact sur le double ratchet différé : cette ADR ne touche
pas le modèle de session (ECIES per-message reste dans M6). Le double ratchet complet est un chantier séparé qui étendra ce schéma hybride une fois audité.
Plan d'implémentation M6
Étape 1 — Spec gelée
- ADR-0007 Accepted après relecture par le cryptographe.
- Formatage binaire et HKDF params figés.
Étape 2 — ombrys-pq WASM
crates/ombrys-pq-wasm: crate thin wrapper qui expose
kyber_keypair(), kyber_encapsulate(), kyber_decapsulate() via wasm-bindgen. - Build reproductible : wasm-pack build --release --target web. - Taille target : < 200 KB gzippé.
Étape 3 — Client TS
apps/web/src/lib/client/messaging-v2.ts: nouveau chemin chiffrement
qui utilise le bundle WASM. - Coexistence v1 + v2 : le champ aad.v = 2 signale le nouveau format. Le client essaye v2 en priorité, retombe sur v1 pour les vieux messages. - Publication des clés Kyber : extension de identity-store.ts avec une seconde paire (Kyber-768 générée au premier login, wrappée K_master).
Étape 4 — Tests
- Vecteurs de test hybride : ronde-trip alice→bob→alice.
- Tampering detection : tampering sur
ss_xuniquement, surss_k
uniquement, sur les deux. - Interop Rust ↔ TS : même résultat des deux côtés.
Étape 5 — Audit externe
- Commande prestataire (cf.
docs/operations/procurement-crypto-audit.md). - Périmètre : handshake hybride + format binaire + HKDF + implémentation
WASM. - Délai estimé : 4 semaines.
Étape 6 — Rollout
- Flag
FEATURE_MESSAGING_PQ=trueen env. - Bascule progressive : un membre peut envoyer en v2 seulement si son
destinataire a déjà publié une paire Kyber. - Transparency report : date de bascule, audit report résumé publié.
Changelog
- 2026-04-24 : rédaction initiale, statut Proposed. Bloqué sur audit
crypto externe avant passage en Accepted.