SLH-DSA, la version normalisée de SPHINCS+, est devenue une norme fédérale américaine en août 2024 sous la référence FIPS 205. Contrairement à ML-DSA, qui domine les discussions sur la cryptographie post-quantique depuis un an, cet algorithme repose uniquement sur des fonctions de hachage et non sur des réseaux euclidiens. Ce tutoriel vous montre comment générer des clés, signer des messages et vérifier des signatures SLH-DSA directement en Node.js, avec un projet complet et fonctionnel à la fin.
La plupart des tutoriels post-quantiques publiés en 2026 se concentrent sur ML-KEM ou ML-DSA. SLH-DSA reste largement ignoré, alors qu’il occupe un rôle précis dans la stratégie de défense en profondeur recommandée par le NIST : servir de filet de sécurité si jamais une faiblesse mathématique venait à affecter les réseaux euclidiens qui fondent ML-DSA. Ce guide comble ce manque avec une implémentation testée, du code exécutable et une explication pas à pas de la structure interne de l’algorithme.
Pourquoi SLH-DSA compte encore en 2026
Le NIST a publié FIPS 205 en même temps que FIPS 204 (ML-DSA) et FIPS 203 (ML-KEM), le 13 août 2024. Trois normes, trois familles mathématiques différentes, avec un objectif commun : remplacer RSA et les courbes elliptiques avant qu’un ordinateur quantique suffisamment puissant ne les casse. ML-DSA a rapidement pris l’avantage dans l’adoption pratique, notre tutoriel sur la signature ML-DSA en Node.js l’a d’ailleurs couvert en détail. Mais cette adoption rapide cache un risque de dépendance excessive à une seule famille cryptographique.
OpenSSL 3.5, sorti le 8 avril 2025, a ajouté la prise en charge native des trois algorithmes NIST au même moment, un signal fort que l’écosystème logiciel considère désormais SLH-DSA comme un standard de production et non plus comme une curiosité académique. Le projet Open Quantum Safe (liboqs) classe toutefois son implémentation SLH-DSA en maintenance “best effort”, niveau Tier 3, contre un Tier 2 activement maintenu pour ML-DSA. Cette différence de statut reflète un choix délibéré : SLH-DSA sert d’algorithme de secours, pas de choix par défaut.
La raison technique tient en une phrase : SLH-DSA ne dépend d’aucune hypothèse sur la difficulté des problèmes de réseaux euclidiens. Sa sécurité repose entièrement sur la résistance aux collisions des fonctions de hachage SHA-256 ou SHAKE256, deux primitives étudiées et attaquées depuis des décennies sans faille structurelle majeure. Si un jour une cryptanalyse remettait en cause Module-LWE ou Module-SIS (les fondations de ML-DSA), SLH-DSA continuerait de tenir. C’est cette indépendance mathématique qui justifie sa présence dans toute stratégie de diversification cryptographique sérieuse, un principe que l’ANSSI défend également dans ses recommandations de migration post-quantique.
SLH-DSA contre ML-DSA : les différences qui comptent
Avant de plonger dans le code, il faut comprendre pourquoi ces deux algorithmes standardisés le même jour produisent des résultats aussi différents. ML-DSA descend de CRYSTALS-Dilithium et manipule des polynômes sur des réseaux euclidiens. SLH-DSA descend de SPHINCS+ et empile des arbres de Merkle, des signatures à usage limité WOTS+, et une couche FORS pour signer le message lui-même. Le résultat : des clés minuscules mais des signatures énormes pour SLH-DSA, l’inverse pour ML-DSA.
| Propriété | ML-DSA-44 (FIPS 204) | SLH-DSA-SHA2-128s (FIPS 205) | SLH-DSA-SHA2-128f (FIPS 205) |
|---|---|---|---|
| Base mathématique | Réseaux euclidiens (Module-LWE/SIS) | Fonctions de hachage | Fonctions de hachage |
| Clé publique | 1 312 octets | 32 octets | 32 octets |
| Clé privée | 2 560 octets | 64 octets | 64 octets |
| Taille de signature | 2 420 octets | 7 856 octets | 17 088 octets |
| Vitesse de signature | Très rapide | Lente | Plus rapide que 128s, signature plus grande |
| Statut liboqs | Tier 2 (maintenu) | Tier 3 (best effort) | Tier 3 (best effort) |
Le suffixe compte : “s” signifie small signature, une signature plus compacte au prix d’une génération plus lente. “f” signifie fast signing, une génération plus rapide mais une signature nettement plus lourde. Ce compromis n’existe pas côté vérification, qui reste rapide dans les deux cas. Pour un usage pratique, retenez la règle formulée dans les analyses NIST : ML-DSA comme algorithme principal, SLH-DSA comme sauvegarde indépendante, en particulier pour les signatures à longue durée de vie comme le firmware ou l’archivage légal.
Comment fonctionne SLH-DSA sous le capot
Comprendre la structure interne de SLH-DSA aide à expliquer pourquoi ses signatures pèsent autant. L’algorithme empile trois couches distinctes, chacune héritée de décennies de recherche en cryptographie à base de hachage. La première couche s’appelle WOTS+, une évolution du schéma de Winternitz : elle produit une signature à usage unique, valide pour un seul message, en chaînant des applications répétées d’une fonction de hachage sur une clé privée. Une fois qu’une clé WOTS+ a signé un message, elle ne doit plus jamais être réutilisée, sous peine de compromettre toute la sécurité du schéma.
Le problème d’une signature à usage unique, c’est qu’elle ne suffit pas pour un usage réel où l’on signe des millions de messages avec la même paire de clés. C’est là qu’intervient la deuxième couche : un hypertree, littéralement une hiérarchie d’arbres de Merkle empilés les uns sur les autres. Chaque nœud de cette hiérarchie porte une clé WOTS+ différente, et la structure permet de signer un nombre pratiquement illimité de messages sans jamais réutiliser la même clé à usage unique, tout en ne publiant qu’une seule racine comme clé publique.
La troisième couche, FORS (Forest of Random Subsets), s’occupe spécifiquement de signer le message lui-même plutôt que l’identité du signataire. FORS est un schéma de signature à quelques utilisations, plus économe que WOTS+ en octets pour ce cas précis. La signature finale que vous obtenez en appelant sign() concatène donc la signature FORS du message, la signature WOTS+ correspondant à la feuille de l’hypertree utilisée, et l’ensemble des chemins d’authentification nécessaires pour relier cette feuille à la racine publique. C’est cette accumulation de composants qui explique une signature de 7 856 octets pour SLH-DSA-128s, contre 2 420 octets pour un schéma à base de réseaux comme ML-DSA-44.
Cette architecture a une conséquence pratique importante : chaque opération de signature recalcule une bonne partie de cette structure à la volée, d’où le temps de signature nettement plus long que la vérification. La vérification, à l’inverse, se limite à recalculer les hachages le long d’un chemin déjà fourni par la signature et à comparer le résultat à la racine publique, une opération bien moins coûteuse.
Prérequis avant de commencer
Ce tutoriel a été testé avec les versions suivantes. Vérifiez que votre environnement correspond avant de suivre les étapes, les API de bibliothèques post-quantiques évoluent encore rapidement.
- Node.js version 20 LTS ou supérieure (vérifiez avec
node -v) - npm version 10 ou supérieure
- Le paquet
@noble/post-quantum, maintenu par Paul Miller, dernière version publiée sur npm - Un terminal Linux, macOS ou Windows avec WSL
- Environ 30 minutes pour suivre l’ensemble des étapes
- Des notions de base en JavaScript asynchrone (le code utilise des modules ES)
Nous utilisons ici @noble/post-quantum plutôt que liboqs ou PQClean pour une raison simple : c’est la seule bibliothèque qui expose une API JavaScript pure, auditée, sans dépendance à un module natif compilé en C. liboqs nécessite une compilation native et une liaison N-API pour fonctionner sous Node.js, ce qui complique le déploiement, notamment sur des environnements serverless ou des containers minimalistes. PQClean, de son côté, fournit du code C portable mais aucun paquet npm prêt à l’emploi.
Étape 1 : Initialiser le projet Node.js
Créez un nouveau dossier de projet et initialisez-le avec npm. Nous configurons directement le projet en mode module ES, requis par @noble/post-quantum.
mkdir slh-dsa-tutoriel
cd slh-dsa-tutoriel
npm init -y
npm pkg set type="module"
npm install @noble/post-quantum
Vérifiez que l’installation s’est bien déroulée en listant le contenu du paquet installé.
ls node_modules/@noble/post-quantum/
node -e "console.log(process.versions.node)"
Sortie attendue : une liste de fichiers incluant slh-dsa.js, ml-dsa.js et ml-kem.js, suivie du numéro de version de Node.js installé sur votre machine.
Étape 2 : Comprendre les paramètres disponibles
SLH-DSA propose douze combinaisons de paramètres au total : deux fonctions de hachage sous-jacentes (SHA2 ou SHAKE), trois niveaux de sécurité (128, 192, 256 bits) et deux profils de vitesse (s pour small, f pour fast). Avant d’écrire du code, choisissez le paramètre adapté à votre cas d’usage.
| Paramètre | Niveau de sécurité NIST | Clé publique | Clé privée | Signature | Cas d’usage typique |
|---|---|---|---|---|---|
| SLH-DSA-SHA2-128s | Catégorie 1 | 32 octets | 64 octets | 7 856 octets | Signatures peu fréquentes, taille prioritaire |
| SLH-DSA-SHA2-128f | Catégorie 1 | 32 octets | 64 octets | 17 088 octets | Signature fréquente, latence prioritaire |
| SLH-DSA-SHA2-192s | Catégorie 3 | 48 octets | 96 octets | 16 224 octets | Documents sensibles à moyen terme |
| SLH-DSA-SHA2-256s | Catégorie 5 | 64 octets | 128 octets | 29 792 octets | Archivage légal, firmware critique |
| SLH-DSA-SHAKE-128s | Catégorie 1 | 32 octets | 64 octets | 7 856 octets | Environnements préférant SHAKE256 |
Pour ce tutoriel, nous utilisons SLH-DSA-SHA2-128s, le paramètre le plus courant pour découvrir l’algorithme : sécurité de catégorie 1 (équivalente à AES-128), signature de taille modérée, et compatibilité SHA-256 déjà optimisée dans la plupart des processeurs modernes via des instructions dédiées.
Étape 3 : Générer une première paire de clés
Créez un fichier keygen.js avec le contenu suivant. La génération de clé SLH-DSA construit la racine d’un arbre de Merkle hypertree, c’est une opération plus coûteuse que la génération de clé ML-DSA mais elle reste de l’ordre de la milliseconde.
// keygen.js
import { slh_dsa_sha2_128s } from '@noble/post-quantum/slh-dsa.js';
import { randomBytes } from 'node:crypto';
import { writeFileSync } from 'node:fs';
const seed = randomBytes(48);
const keys = slh_dsa_sha2_128s.keygen(seed);
writeFileSync('public.key', keys.publicKey);
writeFileSync('secret.key', keys.secretKey);
console.log('Clé publique :', keys.publicKey.length, 'octets');
console.log('Clé privée :', keys.secretKey.length, 'octets');
Exécutez le script :
node keygen.js
Sortie attendue :
Clé publique : 32 octets
Clé privée : 64 octets
Ces tailles minuscules sont la marque de fabrique de SLH-DSA. À titre de comparaison, une clé publique ML-DSA-44 pèse 1 312 octets, soit 41 fois plus. C’est l’un des rares avantages de taille de cet algorithme, à ne pas confondre avec la taille de la signature, qui va dans l’autre sens.
Étape 4 : Signer un message
Créez sign.js. La fonction de signature parcourt la structure hypertree, calcule une signature FORS pour le message, puis chaîne les signatures WOTS+ jusqu’à la racine publique.
// sign.js
import { slh_dsa_sha2_128s } from '@noble/post-quantum/slh-dsa.js';
import { readFileSync, writeFileSync } from 'node:fs';
const secretKey = readFileSync('secret.key');
const message = new TextEncoder().encode('Contrat signé le 26 septembre 2026');
const start = performance.now();
const signature = slh_dsa_sha2_128s.sign(secretKey, message);
const duration = performance.now() - start;
writeFileSync('message.txt', message);
writeFileSync('signature.bin', signature);
console.log('Signature générée :', signature.length, 'octets');
console.log('Temps de signature :', duration.toFixed(2), 'ms');
node sign.js
Sortie attendue (le temps exact varie selon votre processeur) :
Signature générée : 7856 octets
Temps de signature : 118.42 ms
Comparez ce temps à une signature ML-DSA-44, généralement sous la milliseconde sur le même matériel selon les benchmarks liboqs. Cet écart de deux ordres de grandeur illustre pourquoi SLH-DSA reste réservé à des opérations de signature peu fréquentes plutôt qu’à un usage TLS à fort volume.
Étape 5 : Vérifier une signature
La vérification reconstruit les chemins d’authentification de l’arbre de Merkle à partir de la signature et compare la racine calculée à la clé publique. Contrairement à la signature, cette opération reste rapide, de l’ordre de quelques millisecondes.
// verify.js
import { slh_dsa_sha2_128s } from '@noble/post-quantum/slh-dsa.js';
import { readFileSync } from 'node:fs';
const publicKey = readFileSync('public.key');
const message = readFileSync('message.txt');
const signature = readFileSync('signature.bin');
const start = performance.now();
const valid = slh_dsa_sha2_128s.verify(publicKey, message, signature);
const duration = performance.now() - start;
console.log('Signature valide :', valid);
console.log('Temps de vérification :', duration.toFixed(2), 'ms');
node verify.js
Sortie attendue :
Signature valide : true
Temps de vérification : 4.17 ms
Modifiez un seul octet de message.txt et relancez verify.js : la fonction doit retourner false. Testez systématiquement ce cas négatif, c’est la vérification la plus importante avant tout déploiement en production.
Étape 6 : Mesurer la taille réelle sur disque et réseau
La taille de la signature SLH-DSA n’est pas un détail théorique, elle a un impact direct sur la bande passante et le stockage. Ajoutez ce script pour visualiser l’empreinte totale d’une opération de signature complète.
// taille.js
import { statSync } from 'node:fs';
const fichiers = ['public.key', 'secret.key', 'message.txt', 'signature.bin'];
let total = 0;
for (const f of fichiers) {
const taille = statSync(f).size;
total += taille;
console.log(f.padEnd(16), taille, 'octets');
}
console.log('Total transmis (clé publique + message + signature) :', total, 'octets');
Sur une API REST classique, chaque requête signée en SLH-DSA-128s ajoute environ 7,9 Ko de surcharge, contre 2,4 Ko pour ML-DSA-44 et seulement 64 à 96 octets pour Ed25519 (pré-quantique). Gardez ce chiffre en tête si vous envisagez SLH-DSA dans un protocole à fort volume : ce n’est pas l’algorithme adapté à cet usage.
Étape 7 : Construire un module de signature réutilisable
Passons à un code structuré pour un usage réel. Ce module encapsule la génération de clés, la signature et la vérification derrière une interface simple, avec gestion d’erreurs et sélection du paramètre en argument.
// slh-signer.js
import * as slh from '@noble/post-quantum/slh-dsa.js';
import { randomBytes } from 'node:crypto';
const PARAMETRES = {
'128s': slh.slh_dsa_sha2_128s,
'128f': slh.slh_dsa_sha2_128f,
'192s': slh.slh_dsa_sha2_192s,
'256s': slh.slh_dsa_sha2_256s,
};
export class SlhSigner {
constructor(niveau = '128s') {
if (!PARAMETRES[niveau]) {
throw new Error(`Niveau inconnu : ${niveau}. Choix possibles : ${Object.keys(PARAMETRES).join(', ')}`);
}
this.algo = PARAMETRES[niveau];
this.niveau = niveau;
}
genererCles() {
const seed = randomBytes(48);
return this.algo.keygen(seed);
}
signer(secretKey, message) {
const bytes = typeof message === 'string' ? new TextEncoder().encode(message) : message;
return this.algo.sign(secretKey, bytes);
}
verifier(publicKey, message, signature) {
const bytes = typeof message === 'string' ? new TextEncoder().encode(message) : message;
return this.algo.verify(publicKey, bytes, signature);
}
}
Testez ce module avec un petit script d’intégration :
// test-signer.js
import { SlhSigner } from './slh-signer.js';
const signer = new SlhSigner('128s');
const { publicKey, secretKey } = signer.genererCles();
const signature = signer.signer(secretKey, 'Facture n°2026-0847');
const estValide = signer.verifier(publicKey, 'Facture n°2026-0847', signature);
const estFalsifiee = signer.verifier(publicKey, 'Facture n°2026-0848', signature);
console.log('Signature d\'origine valide :', estValide);
console.log('Message modifié détecté :', !estFalsifiee);
Sortie attendue : Signature d'origine valide : true puis Message modifié détecté : true. Si le second résultat affiche false, votre implémentation a un problème grave, ne la déployez pas.
Étape 8 : Signer et vérifier un fichier binaire (cas firmware)
L’un des cas d’usage les plus cités pour SLH-DSA est la signature de firmware, où la durée de vie de la signature dépasse largement l’horizon de migration cryptographique. Voici comment signer un fichier binaire plutôt qu’une chaîne de caractères.
// signer-firmware.js
import { SlhSigner } from './slh-signer.js';
import { readFileSync, writeFileSync } from 'node:fs';
const signer = new SlhSigner('256s');
const { publicKey, secretKey } = signer.genererCles();
const firmware = readFileSync('firmware.bin');
const signature = signer.signer(secretKey, firmware);
writeFileSync('firmware.sig', signature);
writeFileSync('firmware.pub', publicKey);
console.log('Firmware :', firmware.length, 'octets');
console.log('Signature :', signature.length, 'octets (SLH-DSA-256s)');
console.log('Ratio signature/firmware :', (signature.length / firmware.length * 100).toFixed(1), '%');
Notez le choix du paramètre 256s ici plutôt que 128s : pour un firmware destiné à rester en service dix ou vingt ans, la catégorie de sécurité 5 (équivalente à AES-256) offre une marge plus confortable face à l’évolution de la cryptanalyse, au prix d’une signature de 29 792 octets au lieu de 7 856.
Étape 9 : Combiner SLH-DSA avec ML-DSA (signature hybride)
La recommandation de diversification cryptographique implique concrètement de signer un document deux fois, avec deux familles d’algorithmes différentes. Voici un exemple de signature hybride qui exige la validité des deux signatures pour accepter le document.
// signature-hybride.js
import { slh_dsa_sha2_128s } from '@noble/post-quantum/slh-dsa.js';
import { ml_dsa44 } from '@noble/post-quantum/ml-dsa.js';
import { randomBytes } from 'node:crypto';
function genererClesHybrides() {
return {
slh: slh_dsa_sha2_128s.keygen(randomBytes(48)),
mldsa: ml_dsa44.keygen(randomBytes(32)),
};
}
function signerHybride(cles, message) {
const bytes = new TextEncoder().encode(message);
return {
slhSignature: slh_dsa_sha2_128s.sign(cles.slh.secretKey, bytes),
mldsaSignature: ml_dsa44.sign(cles.mldsa.secretKey, bytes),
};
}
function verifierHybride(clesPubliques, message, sigs) {
const bytes = new TextEncoder().encode(message);
const slhOk = slh_dsa_sha2_128s.verify(clesPubliques.slh, bytes, sigs.slhSignature);
const mldsaOk = ml_dsa44.verify(clesPubliques.mldsa, bytes, sigs.mldsaSignature);
return slhOk && mldsaOk;
}
const cles = genererClesHybrides();
const sigs = signerHybride(cles, 'Accord de licence logicielle');
const valide = verifierHybride({ slh: cles.slh.publicKey, mldsa: cles.mldsa.publicKey }, 'Accord de licence logicielle', sigs);
console.log('Signature hybride valide :', valide);
Cette approche double la taille de signature et le temps de traitement, mais garantit qu’une faille future dans l’une des deux familles mathématiques ne compromet pas le document. C’est exactement le principe qu’appliquent certaines infrastructures bancaires testées avec ML-DSA-65, comme évoqué dans notre article sur les tests de signature post-quantique dans le secteur bancaire.
Étape 10 : Sérialiser les clés et signatures pour le stockage
Pour transmettre des clés et signatures via JSON (API REST, base de données, fichier de configuration), encodez les buffers en base64. Voici les fonctions utilitaires nécessaires.
// serialisation.js
export function encoderCle(buffer) {
return Buffer.from(buffer).toString('base64');
}
export function decoderCle(base64) {
return new Uint8Array(Buffer.from(base64, 'base64'));
}
// Exemple d'utilisation
import { SlhSigner } from './slh-signer.js';
const signer = new SlhSigner('128s');
const { publicKey, secretKey } = signer.genererCles();
const payload = {
algorithme: 'SLH-DSA-SHA2-128s',
clePublique: encoderCle(publicKey),
dateGeneration: '2026-09-26',
};
console.log(JSON.stringify(payload, null, 2));
Sortie attendue : un objet JSON avec la clé publique encodée en base64, une chaîne de 44 caractères pour une clé de 32 octets. Ne stockez jamais la clé privée dans un format lisible en clair dans un dépôt de code, même chiffrée en base64 (le base64 n’est pas un chiffrement).
Étape 11 : Automatiser des tests avec le module de test intégré de Node.js
Ajoutez une suite de tests avec node:test, disponible nativement depuis Node.js 18, pour valider automatiquement le comportement du module de signature à chaque modification.
// slh-signer.test.js
import { test } from 'node:test';
import assert from 'node:assert';
import { SlhSigner } from './slh-signer.js';
test('signature et vérification réussissent avec le bon message', () => {
const signer = new SlhSigner('128s');
const { publicKey, secretKey } = signer.genererCles();
const signature = signer.signer(secretKey, 'message de test');
assert.strictEqual(signer.verifier(publicKey, 'message de test', signature), true);
});
test('la vérification échoue si le message est modifié', () => {
const signer = new SlhSigner('128s');
const { publicKey, secretKey } = signer.genererCles();
const signature = signer.signer(secretKey, 'message original');
assert.strictEqual(signer.verifier(publicKey, 'message altéré', signature), false);
});
test('un niveau de sécurité inconnu lève une erreur', () => {
assert.throws(() => new SlhSigner('999x'));
});
node --test slh-signer.test.js
Sortie attendue : trois tests passés (pass 3), zéro échec. Si le deuxième test échoue, ne poursuivez pas le déploiement, cela signifie que votre code accepte des messages falsifiés.
Étape 12 : Comparer les performances réelles sur votre machine
Les benchmarks publiés varient énormément selon le processeur et l’implémentation. Mesurez vous-même les performances sur votre environnement de production avec ce script de comparaison.
// benchmark.js
import { slh_dsa_sha2_128s, slh_dsa_sha2_128f } from '@noble/post-quantum/slh-dsa.js';
import { randomBytes } from 'node:crypto';
function mesurer(algo, nom, iterations = 5) {
const seed = randomBytes(48);
const { publicKey, secretKey } = algo.keygen(seed);
const message = new TextEncoder().encode('benchmark');
let tempsSignature = 0;
let signature;
for (let i = 0; i < iterations; i++) {
const t0 = performance.now();
signature = algo.sign(secretKey, message);
tempsSignature += performance.now() - t0;
}
const t1 = performance.now();
algo.verify(publicKey, message, signature);
const tempsVerif = performance.now() - t1;
console.log(`${nom.padEnd(20)} signature moy: ${(tempsSignature / iterations).toFixed(2)}ms vérif: ${tempsVerif.toFixed(2)}ms taille sig: ${signature.length}o`);
}
mesurer(slh_dsa_sha2_128s, 'SLH-DSA-128s');
mesurer(slh_dsa_sha2_128f, 'SLH-DSA-128f');
Sortie type observée (les valeurs exactes dépendent de votre CPU) :
SLH-DSA-128s signature moy: 121.38ms vérif: 3.92ms taille sig: 7856o
SLH-DSA-128f signature moy: 9.74ms vérif: 6.15ms taille sig: 17088o
Le profil "f" signe environ 12 fois plus vite que le profil "s" sur cette mesure, au prix d'une signature 2,2 fois plus grosse. Choisissez selon votre contrainte dominante : bande passante limitée, privilégiez "s" ; latence de signature critique, privilégiez "f".
Erreurs fréquentes et pièges à éviter
Voici les erreurs les plus courantes rencontrées lors de l'intégration de SLH-DSA dans un projet Node.js, classées par fréquence observée dans les retours de la communauté open source.
- Confondre les profils "s" et "f" dans un système de vérification tiers. Le paramètre utilisé pour signer doit être identique à celui utilisé pour vérifier. Une signature générée en 128f ne se vérifie jamais avec la fonction 128s.
- Utiliser un seed de génération de clé trop court ou prévisible. Le seed doit provenir d'un générateur cryptographiquement sûr comme
node:crypto, jamais deMath.random(). - Sous-estimer la taille des signatures dans un budget de paquets réseau. Une API qui vérifie un JWT ou un en-tête HTTP signé en SLH-DSA doit prévoir des limites de taille de requête adaptées, 7,9 à 49,9 Ko selon le paramètre.
- Stocker la clé privée en clair dans le code source ou un fichier de configuration versionné. Utilisez un gestionnaire de secrets, jamais un commit Git.
- Réutiliser le même paramètre pour tous les usages sans distinction de criticité. Un cookie de session n'a pas besoin de SLH-DSA-256s, un firmware critique ne devrait pas se contenter de 128s.
- Ignorer le temps de signature dans un contexte à fort trafic. 100 à 130 ms par signature limite un serveur à quelques dizaines d'opérations par seconde par cœur, un chiffre à multiplier par le nombre de cœurs disponibles pour estimer un débit réaliste.
- Mélanger les bibliothèques sans vérifier la compatibilité des formats. Une clé générée avec liboqs ne se décode pas nécessairement de la même façon dans
@noble/post-quantumsans conversion explicite du format d'octets. - Oublier de fixer la version exacte du paquet dans
package.json. Les API post-quantiques évoluent encore, un changement de version mineure peut renommer un export.
Dépannage : résoudre les problèmes les plus courants
Cette section regroupe les problèmes signalés le plus souvent lors de l'intégration de SLH-DSA en Node.js, avec leur cause probable et leur solution.
- Erreur "Cannot find module '@noble/post-quantum/slh-dsa.js'" : vérifiez que
"type": "module"est bien présent dans votrepackage.jsonet que vous utilisez l'extension.jscomplète dans l'import, requise par les modules ES. - La vérification retourne toujours
false, même pour une signature valide : vérifiez que le message passé àverify()est encodé exactement de la même façon (même encodage UTF-8, mêmes octets) que celui passé àsign(). Un espace ou un retour à la ligne supplémentaire change tout. - Le processus se bloque ou devient très lent pendant la signature : c'est un comportement attendu pour le profil "s", pas un bug. Passez au profil "f" si la latence est un problème, ou déportez la signature dans un worker thread pour ne pas bloquer la boucle d'événements Node.js.
- Erreur de type "invalid seed length" : le seed de génération de clé SLH-DSA doit avoir exactement 48 octets pour les paramètres SHA2-128, une taille différente pour les niveaux 192 et 256. Consultez la documentation du paquet pour la taille exacte du paramètre choisi.
- Les signatures diffèrent entre deux exécutions avec la même clé et le même message : c'est normal, SLH-DSA intègre un aléa interne à chaque signature (randomisation FORS), contrairement à certains schémas déterministes. Cela n'affecte pas la validité de la vérification.
- Performance nettement inférieure aux benchmarks publiés : les chiffres de référence proviennent souvent d'implémentations C optimisées (liboqs) avec accélération matérielle SHA-256. Une implémentation JavaScript pure comme
@noble/post-quantumsera plus lente, c'est le compromis entre portabilité et vitesse brute. - Incompatibilité de taille de clé entre deux versions du paquet : verrouillez la version exacte avec
npm install @noble/post-quantum@version-preciseet testez toute mise à jour dans un environnement de staging avant la production. - Erreur mémoire sur des messages volumineux : pour signer des fichiers de plusieurs centaines de mégaoctets, hachez d'abord le fichier avec SHA-256 ou BLAKE3, puis signez le hash plutôt que le fichier entier. Notre tutoriel sur les arbres de Merkle avec BLAKE3 détaille cette approche pour de gros volumes de données.
Conseils avancés pour la production
Une fois les bases maîtrisées, quelques pratiques supplémentaires méritent d'être intégrées avant tout déploiement en production réelle.
Déportez la signature dans un worker thread. Le temps de signature de 100 à 130 ms pour le profil "s" bloque la boucle d'événements Node.js si vous l'exécutez directement dans le thread principal d'un serveur HTTP. Utilisez le module worker_threads pour isoler ce calcul et garder votre API réactive sous charge.
Ne signez jamais le message brut pour des fichiers volumineux. Hachez d'abord avec SHA-256 (256 bits, aligné avec le niveau de sécurité SLH-DSA-128) ou SHA-512 pour les niveaux 192 et 256, puis signez ce condensé. Cette pratique, standard en cryptographie à clé publique, réduit drastiquement le temps de traitement pour des fichiers de plusieurs mégaoctets.
Prévoyez la rotation des clés dès la conception. Bien que SLH-DSA soit stateless (contrairement à LMS ou XMSS, qui exigent un suivi strict de compteur pour éviter la réutilisation d'état), une bonne hygiène cryptographique impose de limiter la durée de vie de chaque paire de clés et de documenter la procédure de révocation.
Testez la compatibilité SHAKE en parallèle de SHA2. Certains environnements réglementaires ou certifiés FIPS imposent SHAKE256 plutôt que SHA-256. Le paquet @noble/post-quantum expose les deux variantes avec la même API, changez simplement le nom de l'export importé.
Documentez le choix d'algorithme dans votre registre de conformité. Le centre de ressources cryptographie de shattered.io centralise les évolutions réglementaires post-quantiques, utile pour justifier un choix d'algorithme lors d'un audit de sécurité ou d'une revue CRA (Cyber Resilience Act).
Le projet complet : structure de fichiers finale
À l'issue de ce tutoriel, votre projet doit ressembler à la structure suivante, avec un module réutilisable, des tests automatisés et des scripts de démonstration.
slh-dsa-tutoriel/
├── package.json
├── slh-signer.js # Module principal réutilisable
├── slh-signer.test.js # Suite de tests automatisés
├── serialisation.js # Fonctions d'encodage base64
├── signature-hybride.js # Combinaison SLH-DSA + ML-DSA
├── signer-firmware.js # Cas d'usage firmware longue durée
├── benchmark.js # Comparaison de performances
├── keygen.js
├── sign.js
├── verify.js
└── taille.js
Ajoutez un script npm pour lancer l'ensemble des tests d'un coup, dans votre package.json :
{
"scripts": {
"test": "node --test",
"demo": "node keygen.js && node sign.js && node verify.js"
}
}
Lancez npm run demo pour exécuter la chaîne complète de génération, signature et vérification en une seule commande, et npm test pour valider le comportement du module avant tout déploiement.
Quand choisir SLH-DSA plutôt que ML-DSA
Résumons la décision pratique à laquelle vous ferez face en production. SLH-DSA convient particulièrement bien à trois scénarios : les signatures de firmware ou de mises à jour logicielles à très longue durée de vie, où le coût de bande passante d'une signature de plusieurs kilo-octets importe peu face à un cycle de mise à jour espacé de plusieurs mois ; les architectures de signature hybride qui exigent une diversité mathématique par mesure de précaution réglementaire ou d'audit ; et l'archivage de documents légaux dont l'authenticité doit rester vérifiable sur plusieurs décennies, indépendamment de l'évolution des attaques sur les réseaux euclidiens.
À l'inverse, évitez SLH-DSA pour tout protocole à fort volume ou sensible à la latence : poignées de main TLS, authentification de session, signature de transactions à haute fréquence. Dans ces cas, ML-DSA reste le choix recommandé par la quasi-totalité des analyses de migration post-quantique publiées en 2026, notre comparatif ML-DSA vs SLH-DSA vs FN-DSA détaille l'écart de taille de 75x observé entre ces familles d'algorithmes selon le contexte de mesure.
Gardez également à l'esprit que le choix n'est pas figé une fois pour toutes. Une équipe peut très bien démarrer avec ML-DSA seul pour l'ensemble de ses flux, puis ajouter SLH-DSA plus tard, uniquement sur les artefacts qui le justifient : un firmware embarqué déployé sur des millions d'appareils sans mise à jour facile, un contrat notarié destiné à rester vérifiable pendant trente ans, ou un certificat racine dont la compromission aurait des conséquences en cascade. Le code présenté dans ce tutoriel fonctionne aussi bien pour ces cas isolés que pour une adoption plus large, la classe SlhSigner de l'étape 7 accepte n'importe quel niveau de paramètre sans changer le reste de votre architecture applicative.
Pour aller plus loin sur l'écosystème post-quantique dans Node.js, notre tutoriel général sur la cryptographie post-quantique en Node.js couvre également ML-KEM pour l'échange de clés, complémentaire à la signature abordée ici. Et pour situer SLH-DSA dans le calendrier réglementaire français, l'ANSSI a fixé la fin des certifications sans composante post-quantique dès 2027, avec une échéance de bascule complète en 2030.
Questions fréquentes
SLH-DSA est-il plus sûr que ML-DSA ?
Les deux algorithmes visent le même niveau de sécurité post-quantique pour un paramètre équivalent. SLH-DSA n'est pas "plus sûr" en absolu, il repose sur une hypothèse mathématique différente (fonctions de hachage plutôt que réseaux euclidiens), ce qui en fait un choix de diversification plutôt qu'un remplacement supérieur.
Puis-je utiliser SLH-DSA dans un navigateur web ?@noble/post-quantum est une implémentation JavaScript pure sans dépendance native, elle fonctionne donc aussi bien dans Node.js que dans un environnement navigateur moderne compatible avec les modules ES et les typed arrays.
Quelle est la différence entre les variantes SHA2 et SHAKE de SLH-DSA ?
Les deux variantes produisent des clés et signatures de tailles identiques pour un même niveau de sécurité, seule la fonction de hachage sous-jacente change. SHA2 profite généralement d'accélérations matérielles plus répandues, SHAKE256 est parfois imposé par certaines exigences de certification.
SLH-DSA remplace-t-il RSA ou ECDSA dès aujourd'hui ?
Non, pas de façon générale. Sa taille de signature bien plus importante (7,9 à 49,9 Ko contre quelques centaines d'octets pour RSA ou ECDSA) le rend impraticable pour la plupart des usages web courants. Il vise des cas spécifiques à faible fréquence de signature.
Existe-t-il un support natif de SLH-DSA dans Node.js sans bibliothèque tierce ?
OpenSSL 3.5 prend en charge SLH-DSA nativement depuis avril 2025, mais l'exposition de cette fonctionnalité via le module node:crypto dépend de la version de Node.js et de la version d'OpenSSL avec laquelle elle a été compilée. Vérifiez process.versions sur votre environnement cible avant de vous appuyer sur ce chemin.
Combien de temps faut-il pour migrer une application existante vers SLH-DSA ?
Cela dépend entièrement du volume de code touchant la signature numérique. Pour un module isolé comme celui présenté dans ce tutoriel, comptez quelques heures. Pour une infrastructure de certificats ou de PKI complète, les projets de migration recensés dans le secteur bancaire s'étalent généralement sur plusieurs mois.
Faut-il utiliser SLH-DSA seul ou en complément de ML-DSA ?
La pratique recommandée dans les analyses de migration post-quantique consiste à utiliser ML-DSA comme algorithme principal et SLH-DSA comme sauvegarde indépendante, comme illustré à l'étape 9 de ce tutoriel avec la signature hybride.
Le paquet @noble/post-quantum est-il audité ?
Le dépôt se présente comme une implémentation auditable et minimale, conçue pour faciliter la relecture de code plutôt que pour maximiser la performance brute. Vérifiez toujours la présence d'un audit de sécurité à jour avant un déploiement critique, et consultez le dépôt GitHub du projet pour l'historique des revues.




