Décodage par syndrome et décodage générique, McEliece et Niederreiter, HQC : une sécurité très étudiée contre des clés publiques énormes.
Nous quittons les réseaux. La famille des codes correcteurs offre la plus vieille hypothèse post-quantique en service : McEliece date de 1978, la même année que RSA, et il n'a jamais été cassé. Ce chapitre explique pourquoi, et pourquoi cette solidité n'a pas suffi à en faire le standard.
Codes linéaires : le strict nécessaire
Un code linéaire sur est un sous-espace vectoriel de dimension dans . On y encode bits d'information en bits transmis, les bits supplémentaires servant à détecter et corriger les erreurs. La distance minimale est le plus petit poids d'un mot de code non nul ; un code corrige jusqu'à erreurs.
Deux matrices décrivent le code. La génératrice , de taille , encode : . La matrice de contrôle , de taille , vérifie : pour tout mot de code.
Le syndrome est la quantité centrale. Si l'on reçoit où est le vecteur d'erreur, alors
Le syndrome ne dépend que de l'erreur, jamais du message. Décoder, c'est retrouver à partir de — et c'est exactement le problème sur lequel toute la famille repose.
Le décodage générique est difficile
Voici la dissymétrie qui fonde la cryptographie à base de codes.
Si l'on connaît la structure du code — qu'il est un code de Hamming, de Reed-Solomon, de Goppa — le décodage est un algorithme polynomial, souvent très rapide. C'est même la raison d'être de ces codes en télécommunications.
Si l'on ne dispose que d'une matrice quelconque, sans structure apparente, le problème du décodage par syndrome est NP-difficile, résultat établi par Berlekamp, McEliece et van Tilborg dès 1978. Le meilleur algorithme connu reste le décodage par ensembles d'information (ISD), dont l'idée remonte à Prange en 1962 : deviner un ensemble de positions sans erreur et résoudre linéairement. Soixante ans de raffinements — Stern, MMT, BJMM — n'ont amélioré que la constante dans l'exposant. Le coût reste exponentiel, et sa variante quantique n'apporte qu'un gain modeste.
C'est cette stabilité qui fait la réputation de la famille : la courbe de progression des attaques est remarquablement plate depuis quatre décennies. Pour comparer, l'estimation de sécurité des réseaux, elle, a bougé plusieurs fois depuis 2016.
McEliece : brouiller la structure
La construction de McEliece tient en une phrase. On choisit un code structuré, dont on sait décoder efficacement — historiquement un code de Goppa binaire — et on publie une matrice génératrice brouillée qui décrit le même code sans en laisser voir la structure.
où est inversible et une permutation. Chiffrer, c'est encoder le message avec puis ajouter volontairement erreurs. Déchiffrer, c'est retirer la permutation, décoder avec l'algorithme de Goppa, et défaire .
C'est la trappe du chapitre 4 transposée : même objet, deux descriptions, une seule exploitable. Ici, la « bonne base » est la structure de Goppa.
La variante de Niederreiter utilise la matrice de contrôle plutôt que la génératrice : le message est encodé dans le vecteur d'erreur lui-même, et le chiffré est un simple syndrome. Elle est équivalente en sécurité et produit des chiffrés bien plus courts — c'est la forme employée par Classic McEliece.
Où se trouve la trappe dans McEliece ?
HQC : le retour de la structure
Classic McEliece a un défaut, un seul, et il est massif : sa clé publique est une matrice dense. Elle pèse 261 kilooctets au niveau de sécurité le plus bas et plus d'un mégaoctet au plus haut.
HQC — Hamming Quasi-Cyclic — répond à cela comme Ring-LWE a répondu à LWE plein : en introduisant de la structure. Les codes employés sont quasi-cycliques, donc décrits par un petit nombre de coefficients au lieu d'une matrice complète. Le gain est du même ordre : la clé publique tombe à quelques kilooctets.
HQC diffère de McEliece sur un point conceptuel important. Sa sécurité ne repose pas sur la dissimulation d'un code structuré, mais sur le décodage de codes aléatoires quasi-cycliques — le code servant à corriger est public, et le secret est ailleurs. Cela supprime une classe entière d'attaques, celles qui cherchent à distinguer un code masqué d'un code aléatoire.
Le prix, comme pour ML-KEM, est un taux d'échec de déchiffrement non nul qu'il faut dimensionner soigneusement.
Le NIST a retenu HQC en mars 2025 comme second mécanisme d'encapsulation, en secours de ML-KEM. La logique de ce choix mérite d'être explicitée en cours : il ne s'agit pas d'avoir deux schémas équivalents, mais deux schémas reposant sur des hypothèses de difficulté différentes. Si les réseaux tombaient, HQC resterait.
Le compromis, en chiffres
| Classic McEliece | HQC-128 | ML-KEM-768 | |
|---|---|---|---|
| clé publique | 261 kio à 1 Mio | 2249 o | 1184 o |
| chiffré | 96 à 208 o | 4433 o | 1088 o |
| hypothèse | décodage générique, 1978 | décodage quasi-cyclique | Module-LWE |
| statut NIST | non retenu (ISO en cours) | retenu 2025, en secours | FIPS 203 |
Regardez la ligne « chiffré » : McEliece produit les chiffrés les plus courts de tout le paysage post-quantique — 96 octets. Sa clé publique est énorme, mais elle peut être transmise une fois et réutilisée. Pour un tunnel VPN à long terme entre deux sites, où la clé s'échange à l'installation et les chiffrés circulent en permanence, McEliece est un excellent choix, et plusieurs agences européennes le recommandent explicitement pour les usages à long terme. Pour un handshake TLS où chaque connexion transporte la clé, il est inutilisable.
La bonne taille dépend de ce qui circule souvent. C'est la leçon d'ingénierie du chapitre, et elle revaudra au chapitre 13.
Pour quel usage Classic McEliece est-il un bon choix malgré sa clé d'un mégaoctet ?
À vous
Implémentez le décodage générique par énumération, puis comparez son coût à celui du décodage structuré — et lisez la dernière table.
// Décodage par syndrome sur le code de Hamming [7, 4, 3]. // Il corrige une erreur. Le point de l'exercice n'est pas de le décoder — // c'est de mesurer ce que coûte le décodage quand on ne connaît PAS la // structure du code. // Matrice de contrôle : les colonnes sont 1..7 écrits en binaire. const H = [ [0, 0, 0, 1, 1, 1, 1], [0, 1, 1, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0, 1], ]; const syndrome = (mot) => H.map((ligne) => ligne.reduce((s, h, i) => s ^ (h & mot[i]), 0)); // Décodage STRUCTURÉ : on connaît le code, le syndrome donne directement la // position de l'erreur (lue en binaire). function decoderAvecClef(recu) { const s = syndrome(recu); const position = s[0] * 4 + s[1] * 2 + s[2]; if (position === 0) return { mot: recu, erreur: null }; const corrige = recu.slice(); corrige[position - 1] ^= 1; return { mot: corrige, erreur: position - 1 }; } // Décodage GÉNÉRIQUE : on ne connaît que H. On énumère les motifs d'erreur // par poids croissant jusqu'à retomber sur un syndrome nul. let essais = 0; function decoderSansClef(recu, poidsMax) { essais = 0; const n = recu.length; const cible = syndrome(recu); // À COMPLÉTER — énumérer tous les motifs d'erreur de poids ≤ poidsMax et // renvoyer le premier dont le syndrome égale la cible. Incrémentez essais. return null; } const MOT = [1, 0, 1, 1, 0, 1, 0]; // mot de code valide const RECU = MOT.slice(); RECU[4] ^= 1; // une erreur en position 4 console.log("reçu :", RECU.join("")); console.log("syndrome :", syndrome(RECU).join("")); console.log("avec la clef :", JSON.stringify(decoderAvecClef(RECU))); console.log("sans la clef :", JSON.stringify(decoderSansClef(RECU, 1)), `(${essais} essais)`); // Le vrai sujet : combien de motifs faut-il énumérer en taille réelle ? const binom = (n, k) => { let r = 1; for (let i = 0; i < k; i++) r = (r * (n - i)) / (i + 1); return r; }; console.log("\nmotifs d'erreur à énumérer, C(n, t) :"); for (const [n, t] of [[7, 1], [1024, 38], [3488, 64], [6960, 119]]) { console.log(` n = ${String(n).padStart(4)}, t = ${String(t).padStart(3)} → 2^${Math.log2(binom(n, t)).toFixed(0)}`); }
À retenir
Vous avez parcouru les 7 sections.
Marquez-la terminée pour faire avancer votre parcours, ou revenez sur un point avant de passer à la suite.