ADR-0007

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_x uniquement, sur ss_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=true en 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.