{"id":296,"date":"2026-06-20T20:41:39","date_gmt":"2026-06-20T20:41:39","guid":{"rendered":"https:\/\/shattered.io\/fr\/2026\/06\/20\/ecdsa-ed25519-nodejs\/"},"modified":"2026-06-20T20:41:39","modified_gmt":"2026-06-20T20:41:39","slug":"ecdsa-ed25519-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/fr\/2026\/06\/20\/ecdsa-ed25519-nodejs\/","title":{"rendered":"ECDSA et Ed25519 dans Node.js : 12 \u00c9tapes [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Le module <code>crypto<\/code> de Node.js 22 prend en charge ECDSA et Ed25519 nativement, sans aucune d\u00e9pendance externe. Une cl\u00e9 ECDSA P-256 offre le m\u00eame niveau de s\u00e9curit\u00e9 qu&#8217;une cl\u00e9 RSA de 3 072 bits, avec une taille 12 fois plus petite. Ed25519 g\u00e9n\u00e8re environ 120 000 signatures par seconde contre 2 000 pour RSA-2048, soit un facteur 60. Ce tutoriel construit pas \u00e0 pas un service de signature num\u00e9rique complet, de la g\u00e9n\u00e9ration des cl\u00e9s jusqu&#8217;au d\u00e9ploiement d&#8217;une API REST s\u00e9curis\u00e9e.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"pourquoi-ecdsa-et-ed25519-supplantent-rsa-en-2026\">Pourquoi ECDSA et Ed25519 supplantent RSA en 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">RSA a domin\u00e9 la cryptographie asym\u00e9trique pendant 45 ans. En 2026, il reste pr\u00e9sent dans les syst\u00e8mes legacy, mais trois tendances l&#8217;ont contraint \u00e0 reculer. TLS 1.3 (RFC 8446) a supprim\u00e9 les suites de chiffrement RSA sans forward secrecy. GitHub exige depuis 2022 les cl\u00e9s Ed25519 pour les nouveaux comptes SSH. Le standard FIDO2\/WebAuthn utilise ECDSA P-256 comme algorithme primaire pour l&#8217;authentification sans mot de passe.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La cryptographie sur courbes elliptiques (ECC) repose sur un probl\u00e8me math\u00e9matique distinct du factoring RSA : le probl\u00e8me du logarithme discret sur une courbe elliptique (ECDLP). Aucun algorithme classique connu ne r\u00e9sout ce probl\u00e8me en temps polynomial pour les courbes standardis\u00e9es. Une cl\u00e9 ECC de 256 bits offre 128 bits de s\u00e9curit\u00e9, identique \u00e0 RSA-3072 avec une taille 12 fois inf\u00e9rieure. Selon le rapport NIST SP 800-57 mis \u00e0 jour en 2025, RSA-2048 fournit seulement 112 bits de s\u00e9curit\u00e9 et devra \u00eatre retir\u00e9 des syst\u00e8mes f\u00e9d\u00e9raux am\u00e9ricains d&#8217;ici 2030.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Algorithme<\/th><th>Taille de cl\u00e9<\/th><th>S\u00e9curit\u00e9 (bits)<\/th><th>Signatures\/s (Intel Xeon)<\/th><th>Cas d&#8217;usage typique<\/th><\/tr><\/thead><tbody><tr><td>RSA-2048<\/td><td>2 048 bits<\/td><td>112 bits<\/td><td>~2 000<\/td><td>Legacy TLS, PGP ancien<\/td><\/tr><tr><td>RSA-4096<\/td><td>4 096 bits<\/td><td>140 bits<\/td><td>~400<\/td><td>Certificats CA racine<\/td><\/tr><tr><td>ECDSA P-256<\/td><td>256 bits<\/td><td>128 bits<\/td><td>~35 000<\/td><td>TLS 1.3, JWT, FIDO2, code signing<\/td><\/tr><tr><td>ECDSA P-384<\/td><td>384 bits<\/td><td>192 bits<\/td><td>~18 000<\/td><td>Secteur d\u00e9fense, gouvernement<\/td><\/tr><tr><td><strong>Ed25519<\/strong><\/td><td>256 bits<\/td><td>128 bits<\/td><td>~120 000<\/td><td>SSH, WireGuard, GPG, npm provenance<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">ECDSA (Elliptic Curve Digital Signature Algorithm) et EdDSA (Edwards-curve Digital Signature Algorithm) sont deux familles distinctes. ECDSA P-256 utilise la courbe NIST P-256 (aussi appel\u00e9e secp256r1 ou prime256v1) et produit des signatures DER encod\u00e9es. Ed25519 utilise la courbe Edwards Curve 25519 con\u00e7ue par Daniel Bernstein, avec une r\u00e9sistance prouv\u00e9e contre les attaques par canaux auxiliaires. Le standard <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc8032\" target=\"_blank\" rel=\"noopener\">RFC 8032<\/a> d\u00e9finit Ed25519 comme une impl\u00e9mentation EdDSA sur Curve25519 avec SHA-512. Ces deux algorithmes sont d\u00e9sormais recommand\u00e9s par l&#8217;ANSSI pour les applications civiles fran\u00e7aises, tandis que P-384 reste requis pour les informations classifi\u00e9es.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"prerequis\">Pr\u00e9requis<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ce tutoriel requiert un environnement \u00e0 jour. Aucune biblioth\u00e8que externe n&#8217;est n\u00e9cessaire pour les 10 premi\u00e8res \u00e9tapes : Node.js int\u00e8gre OpenSSL 3.x depuis la version 18. Express est utilis\u00e9 uniquement pour le service REST de l&#8217;\u00e9tape 11.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Composant<\/th><th>Version minimale<\/th><th>Version recommand\u00e9e<\/th><th>V\u00e9rification<\/th><\/tr><\/thead><tbody><tr><td>Node.js<\/td><td>18.0.0<\/td><td>22.14.x LTS<\/td><td><code>node --version<\/code><\/td><\/tr><tr><td>npm<\/td><td>9.0.0<\/td><td>10.9.x<\/td><td><code>npm --version<\/code><\/td><\/tr><tr><td>OpenSSL (inclus)<\/td><td>1.1.1<\/td><td>3.0.x<\/td><td><code>node -e \"console.log(process.versions.openssl)\"<\/code><\/td><\/tr><tr><td>Express (optionnel)<\/td><td>4.18.x<\/td><td>5.x<\/td><td><code>npm list express<\/code><\/td><\/tr><tr><td>Connaissances<\/td><td>Hash cryptographique<\/td><td>Bases crypto asym\u00e9trique<\/td><td>Voir articles pr\u00e9requis ci-dessous<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Pour consolider les bases th\u00e9oriques avant de commencer, lire les articles <a href=\"\/fr\/signatures-numeriques\/\">Signatures num\u00e9riques : comment le hachage et les cl\u00e9s garantissent l&#8217;authenticit\u00e9<\/a> et <a href=\"\/fr\/hmac-sha256-nodejs\/\">HMAC-SHA256 en Node.js : signer une API en 12 \u00e9tapes [2026]<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-1-initialiser-le-projet\">\u00c9tape 1 : Initialiser le projet<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Cr\u00e9er la structure de r\u00e9pertoires et initialiser le projet npm. L&#8217;ensemble de l&#8217;\u00e9tapes 1 \u00e0 10 n&#8217;utilise que le module <code>crypto<\/code> int\u00e9gr\u00e9 \u00e0 Node.js. Express est ajout\u00e9 en option pour le service REST de l&#8217;\u00e9tape 11.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>mkdir ecdsa-ed25519-tutorial\ncd ecdsa-ed25519-tutorial\nnpm init -y\nmkdir keys scripts\n\n# Optionnel : installer Express pour le service REST (\u00e9tape 11)\nnpm install express\n\n# V\u00e9rifier la version d'OpenSSL et les courbes disponibles\nnode -e \"const c = require('crypto'); console.log('OpenSSL:', process.versions.openssl); console.log('Courbes P-256:', c.getCurves().filter(x => x.includes('256')))\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sortie attendue :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>OpenSSL: 3.0.15\nCourbes P-256: [ 'prime256v1', 'secp256r1' ]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Point de contr\u00f4le :<\/strong> Si la sortie ne contient pas <code>prime256v1<\/code>, Node.js a \u00e9t\u00e9 compil\u00e9 sans support ECC complet. Mettre \u00e0 jour vers Node.js 22 LTS via <code>nvm install 22<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-2-comprendre-ecdsa-et-eddsa-avant-decrire-du-code\">\u00c9tape 2 : Comprendre ECDSA et EdDSA avant d&#8217;\u00e9crire du code<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Deux familles de courbes elliptiques sont utilis\u00e9es dans ce tutoriel, et leurs diff\u00e9rences techniques ont des cons\u00e9quences directes sur l&#8217;API Node.js.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>NIST P-256 (secp256r1 \/ prime256v1) :<\/strong> Courbe de Weierstrass approuv\u00e9e dans <a href=\"https:\/\/csrc.nist.gov\/publications\/detail\/fips\/186\/5\/final\" target=\"_blank\" rel=\"noopener\">FIPS 186-5<\/a>. Les signatures ECDSA produites sur P-256 sont encod\u00e9es au format DER (ASN.1 binaire), de taille variable entre 70 et 72 octets. Chaque signature ECDSA n\u00e9cessite un nonce al\u00e9atoire k unique. Si k est r\u00e9utilis\u00e9 pour deux signatures diff\u00e9rentes avec la m\u00eame cl\u00e9 priv\u00e9e, la cl\u00e9 priv\u00e9e peut \u00eatre reconstitu\u00e9e math\u00e9matiquement (la PlayStation 3 de Sony a \u00e9t\u00e9 compromise de cette fa\u00e7on en 2010). Node.js utilise OpenSSL pour g\u00e9n\u00e9rer des k s\u00e9curis\u00e9s.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Curve25519 \/ Ed25519 :<\/strong> Courbe tordue d&#8217;Edwards con\u00e7ue par Daniel Bernstein en 2006. Ed25519 est l&#8217;algorithme de signature EdDSA sur cette courbe, avec SHA-512 int\u00e9gr\u00e9. Les signatures font toujours exactement 64 octets. Ed25519 est r\u00e9sistant par construction aux attaques par canaux auxiliaires (timing attacks) car les op\u00e9rations ne d\u00e9pendent pas de donn\u00e9es secr\u00e8tes. Diff\u00e9rence critique dans l&#8217;API Node.js : ECDSA requiert de sp\u00e9cifier l&#8217;algorithme de hachage (<code>'SHA256'<\/code>), tandis qu&#8217;Ed25519 le g\u00e8re en interne et re\u00e7oit <code>null<\/code> comme algorithme.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Note importante :<\/strong> Ne pas confondre P-256 (secp256r1, NIST) avec secp256k1 (courbe Koblitz utilis\u00e9e par Bitcoin et Ethereum). Ce sont deux courbes distinctes avec des param\u00e8tres diff\u00e9rents. <code>namedCurve: 'P-256'<\/code> et <code>namedCurve: 'prime256v1'<\/code> d\u00e9signent la m\u00eame courbe NIST. <code>namedCurve: 'secp256k1'<\/code> est diff\u00e9rente et ne convient pas pour TLS ou JWT.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-3-generer-une-paire-de-cles-ecdsa-p-256\">\u00c9tape 3 : G\u00e9n\u00e9rer une paire de cl\u00e9s ECDSA P-256<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La g\u00e9n\u00e9ration utilise <code>crypto.generateKeyPairSync()<\/code> avec le type <code>'ec'<\/code>. La cl\u00e9 priv\u00e9e doit toujours \u00eatre chiffr\u00e9e avec AES-256-CBC et une passphrase robuste avant d&#8217;\u00eatre persist\u00e9e sur disque.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ scripts\/generate-ecdsa.js\nconst crypto = require('crypto');\nconst fs = require('fs');\nconst path = require('path');\n\nfunction generateECDSAKeyPair(passphrase) {\n  const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {\n    namedCurve: 'P-256',                    \/\/ NIST P-256 = secp256r1 = prime256v1\n    publicKeyEncoding: {\n      type: 'spki',                          \/\/ SubjectPublicKeyInfo (standard X.509)\n      format: 'pem'\n    },\n    privateKeyEncoding: {\n      type: 'pkcs8',                         \/\/ PKCS#8 PrivateKeyInfo\n      format: 'pem',\n      cipher: 'aes-256-cbc',               \/\/ Chiffrement de la cl\u00e9 priv\u00e9e\n      passphrase: passphrase\n    }\n  });\n  return { privateKey, publicKey };\n}\n\nconst PASSPHRASE = process.env.KEY_PASSPHRASE;\nif (!PASSPHRASE) {\n  console.error('Erreur : d\u00e9finir la variable KEY_PASSPHRASE avant de g\u00e9n\u00e9rer les cl\u00e9s');\n  process.exit(1);\n}\n\nconst { privateKey, publicKey } = generateECDSAKeyPair(PASSPHRASE);\n\nconst keysDir = path.join(__dirname, '..', 'keys');\nfs.mkdirSync(keysDir, { recursive: true });\n\nfs.writeFileSync(path.join(keysDir, 'ecdsa-private.pem'), privateKey, { mode: 0o600 });\nfs.writeFileSync(path.join(keysDir, 'ecdsa-public.pem'), publicKey, { mode: 0o644 });\n\nconsole.log('Cl\u00e9s ECDSA P-256 g\u00e9n\u00e9r\u00e9es avec succ\u00e8s');\nconsole.log('\\nCl\u00e9 publique (\u00e0 distribuer librement) :');\nconsole.log(publicKey);\nconsole.log('Taille cl\u00e9 publique PEM :', publicKey.length, 'caract\u00e8res (~119 octets encod\u00e9s)');<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ex\u00e9cuter avec :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>KEY_PASSPHRASE=mon-secret-fort node scripts\/generate-ecdsa.js\n\n# Sortie attendue :\nCl\u00e9s ECDSA P-256 g\u00e9n\u00e9r\u00e9es avec succ\u00e8s\n\nCl\u00e9 publique (\u00e0 distribuer librement) :\n-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3h7U...\n-----END PUBLIC KEY-----\nTaille cl\u00e9 publique PEM : 178 caract\u00e8res (~119 octets encod\u00e9s)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Une cl\u00e9 publique ECDSA P-256 en PEM repr\u00e9sente 65 octets de donn\u00e9es brutes (1 octet de type + 32 octets x + 32 octets y), contre 294 octets pour RSA-2048. La cl\u00e9 priv\u00e9e chiffr\u00e9e AES-256-CBC fait environ 365 octets.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-4-signer-des-donnees-avec-ecdsa\">\u00c9tape 4 : Signer des donn\u00e9es avec ECDSA<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La signature utilise <code>crypto.sign()<\/code>, disponible depuis Node.js 12. La fonction prend l&#8217;algorithme de hachage, les donn\u00e9es en Buffer, et la cl\u00e9 priv\u00e9e. Elle retourne la signature au format DER binaire.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ scripts\/sign-ecdsa.js\nconst crypto = require('crypto');\nconst fs = require('fs');\nconst path = require('path');\n\nfunction signWithECDSA(data, privateKeyPem, passphrase) {\n  \/\/ D\u00e9chiffrer la cl\u00e9 priv\u00e9e prot\u00e9g\u00e9e\n  const privateKeyObject = crypto.createPrivateKey({\n    key: privateKeyPem,\n    format: 'pem',\n    passphrase: passphrase\n  });\n\n  \/\/ Signer : SHA-256 + ECDSA P-256 = algorithme ES256 (nomenclature JWT)\n  const signature = crypto.sign('SHA256', Buffer.from(data, 'utf8'), privateKeyObject);\n\n  \/\/ Retourner en Base64 pour un transport JSON-friendly\n  return signature.toString('base64');\n}\n\nconst keysDir = path.join(__dirname, '..', 'keys');\nconst privateKeyPem = fs.readFileSync(path.join(keysDir, 'ecdsa-private.pem'), 'utf8');\nconst PASSPHRASE = process.env.KEY_PASSPHRASE;\n\nconst message = 'Contrat sign\u00e9 \u00e9lectroniquement le 20 juin 2026';\nconst signature = signWithECDSA(message, privateKeyPem, PASSPHRASE);\n\nconsole.log('Message :', message);\nconsole.log('Signature ECDSA (Base64) :', signature);\nconsole.log('Longueur brute :', Buffer.from(signature, 'base64').length, 'octets (DER, variable 70-72)');<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sortie attendue :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Message : Contrat sign\u00e9 \u00e9lectroniquement le 20 juin 2026\nSignature ECDSA (Base64) : MEYCIQDy8n...+3A==\nLongueur brute : 72 octets (DER, variable 70-72)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Particularit\u00e9 ECDSA :<\/strong> La signature DER encode les entiers r et s avec un pr\u00e9fixe de longueur variable. La taille oscille entre 70 et 72 octets selon si r ou s ont leur bit de poids fort \u00e0 1 (n\u00e9cessitant un octet de padding 0x00). Cette variabilit\u00e9 pose probl\u00e8me pour les JWT, qui exigent le format P1363 (r||s concat\u00e9n\u00e9s, 64 octets fixes, voir \u00e9tape 8).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-5-verifier-une-signature-ecdsa\">\u00c9tape 5 : V\u00e9rifier une signature ECDSA<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La v\u00e9rification utilise <code>crypto.verify()<\/code> avec la cl\u00e9 publique. Contrairement \u00e0 la cl\u00e9 priv\u00e9e, la cl\u00e9 publique peut \u00eatre distribu\u00e9e librement et sans passphrase. Elle ne donne aucune capacit\u00e9 \u00e0 signer, uniquement \u00e0 v\u00e9rifier.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ scripts\/verify-ecdsa.js\nconst crypto = require('crypto');\nconst fs = require('fs');\nconst path = require('path');\n\nfunction verifyECDSA(data, signatureBase64, publicKeyPem) {\n  try {\n    return crypto.verify(\n      'SHA256',\n      Buffer.from(data, 'utf8'),\n      publicKeyPem,\n      Buffer.from(signatureBase64, 'base64')\n    );\n  } catch {\n    return false;  \/\/ Signature malform\u00e9e ou cl\u00e9 incompatible\n  }\n}\n\nconst keysDir = path.join(__dirname, '..', 'keys');\nconst privateKeyPem = fs.readFileSync(path.join(keysDir, 'ecdsa-private.pem'), 'utf8');\nconst publicKeyPem = fs.readFileSync(path.join(keysDir, 'ecdsa-public.pem'), 'utf8');\nconst PASSPHRASE = process.env.KEY_PASSPHRASE;\n\nconst message = 'Contrat sign\u00e9 \u00e9lectroniquement le 20 juin 2026';\nconst privateKey = crypto.createPrivateKey({ key: privateKeyPem, passphrase: PASSPHRASE });\nconst signature = crypto.sign('SHA256', Buffer.from(message), privateKey).toString('base64');\n\n\/\/ Test 1 : message non alt\u00e9r\u00e9\nconsole.log('V\u00e9rification (original) :', verifyECDSA(message, signature, publicKeyPem)); \/\/ true\n\n\/\/ Test 2 : message alt\u00e9r\u00e9 d'un seul caract\u00e8re\nconsole.log('V\u00e9rification (alt\u00e9r\u00e9) :', verifyECDSA(message + '.', signature, publicKeyPem)); \/\/ false\n\n\/\/ Test 3 : signature corrompue\nconst corruptedSig = signature.slice(0, -4) + 'AAAA';\nconsole.log('V\u00e9rification (signature corrompue) :', verifyECDSA(message, corruptedSig, publicKeyPem)); \/\/ false<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sortie attendue :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>V\u00e9rification (original) : true\nV\u00e9rification (alt\u00e9r\u00e9) : false\nV\u00e9rification (signature corrompue) : false<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-6-generer-une-paire-de-cles-ed25519\">\u00c9tape 6 : G\u00e9n\u00e9rer une paire de cl\u00e9s Ed25519<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ed25519 utilise le type <code>'ed25519'<\/code> dans <code>generateKeyPairSync()<\/code>. La cl\u00e9 priv\u00e9e Ed25519 fait 32 octets (encod\u00e9e en 64 octets en PKCS#8 PEM). La cl\u00e9 publique fait 32 octets. Une limitation importante : Node.js ne supporte pas le chiffrement natif de la cl\u00e9 priv\u00e9e Ed25519 via l&#8217;option <code>cipher<\/code>, contrairement \u00e0 ECDSA.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ scripts\/generate-ed25519.js\nconst crypto = require('crypto');\nconst fs = require('fs');\nconst path = require('path');\n\nfunction generateEd25519KeyPair() {\n  const { privateKey, publicKey } = crypto.generateKeyPairSync('ed25519', {\n    publicKeyEncoding: {\n      type: 'spki',\n      format: 'pem'\n    },\n    privateKeyEncoding: {\n      type: 'pkcs8',\n      format: 'pem'\n      \/\/ Pas de cipher pour Ed25519 en Node.js (limitation OpenSSL)\n      \/\/ Utiliser HashiCorp Vault, AWS Secrets Manager, ou GPG pour prot\u00e9ger ce fichier\n    }\n  });\n  return { privateKey, publicKey };\n}\n\nconst { privateKey, publicKey } = generateEd25519KeyPair();\nconst keysDir = path.join(__dirname, '..', 'keys');\n\nfs.writeFileSync(path.join(keysDir, 'ed25519-private.pem'), privateKey, { mode: 0o600 });\nfs.writeFileSync(path.join(keysDir, 'ed25519-public.pem'), publicKey, { mode: 0o644 });\n\nconsole.log('Cl\u00e9s Ed25519 g\u00e9n\u00e9r\u00e9es avec succ\u00e8s');\nconsole.log('Taille cl\u00e9 publique PEM :', publicKey.length, 'caract\u00e8res (32 octets bruts)');\nconsole.log('\\nCl\u00e9 publique Ed25519 :');\nconsole.log(publicKey);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sortie attendue :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Cl\u00e9s Ed25519 g\u00e9n\u00e9r\u00e9es avec succ\u00e8s\nTaille cl\u00e9 publique PEM : 119 caract\u00e8res (32 octets bruts)\n\nCl\u00e9 publique Ed25519 :\n-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA...\n-----END PUBLIC KEY-----<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La cl\u00e9 publique Ed25519 fait 32 octets bruts contre 65 pour ECDSA P-256 et 256 pour RSA-2048. Cette compacit\u00e9 a un impact mesurable sur la taille des tokens JWT et des paquets r\u00e9seau dans les protocoles haute fr\u00e9quence.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-7-signer-et-verifier-avec-ed25519\">\u00c9tape 7 : Signer et v\u00e9rifier avec Ed25519<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Diff\u00e9rence critique par rapport \u00e0 ECDSA : Ed25519 int\u00e8gre le hachage dans l&#8217;algorithme lui-m\u00eame (SHA-512 en interne). Passer un algorithme de hachage explicite \u00e0 <code>crypto.sign()<\/code> avec une cl\u00e9 Ed25519 g\u00e9n\u00e8re une erreur en Node.js 22. L&#8217;argument algorithme doit \u00eatre <code>null<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ scripts\/sign-verify-ed25519.js\nconst crypto = require('crypto');\nconst fs = require('fs');\nconst path = require('path');\n\nfunction signWithEd25519(data, privateKeyPem) {\n  \/\/ null = Ed25519 g\u00e8re le hachage en interne (SHA-512 + d\u00e9rivation Curve25519)\n  \/\/ ERREUR si on passe 'SHA256' ici : TypeError: Invalid key type\n  const signature = crypto.sign(null, Buffer.from(data, 'utf8'), privateKeyPem);\n  return signature.toString('base64');\n}\n\nfunction verifyEd25519(data, signatureBase64, publicKeyPem) {\n  try {\n    return crypto.verify(\n      null,                                       \/\/ null obligatoire pour Ed25519\n      Buffer.from(data, 'utf8'),\n      publicKeyPem,\n      Buffer.from(signatureBase64, 'base64')\n    );\n  } catch {\n    return false;\n  }\n}\n\nconst keysDir = path.join(__dirname, '..', 'keys');\nconst privateKeyPem = fs.readFileSync(path.join(keysDir, 'ed25519-private.pem'), 'utf8');\nconst publicKeyPem = fs.readFileSync(path.join(keysDir, 'ed25519-public.pem'), 'utf8');\n\nconst message = 'Transaction valid\u00e9e par Ed25519 - 20 juin 2026 10:30:00 UTC';\n\nconst signature = signWithEd25519(message, privateKeyPem);\nconsole.log('Signature Ed25519 (Base64) :', signature);\nconsole.log('Taille fixe :', Buffer.from(signature, 'base64').length, 'octets (toujours 64)');\n\nconsole.log('V\u00e9rification (original) :', verifyEd25519(message, signature, publicKeyPem));     \/\/ true\nconsole.log('V\u00e9rification (alt\u00e9r\u00e9) :', verifyEd25519(message + '!', signature, publicKeyPem)); \/\/ false<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sortie attendue :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Signature Ed25519 (Base64) : dGhpcyBpcyBhIG...==\nTaille fixe : 64 octets (toujours 64)\nV\u00e9rification (original) : true\nV\u00e9rification (alt\u00e9r\u00e9) : false<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La taille fixe de 64 octets d&#8217;Ed25519 simplifie les protocoles r\u00e9seau : pas besoin de parser un encodage DER \u00e0 longueur variable, les buffers peuvent \u00eatre pr\u00e9-allou\u00e9s de taille fixe.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-8-implementer-jwt-signe-avec-es256-ecdsa-sha-256\">\u00c9tape 8 : Impl\u00e9menter JWT sign\u00e9 avec ES256 (ECDSA + SHA-256)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Le standard JWT (RFC 7519) supporte ECDSA via l&#8217;algorithme ES256 (ECDSA P-256 + SHA-256). ES256 est le deuxi\u00e8me algorithme JWT le plus r\u00e9pandu apr\u00e8s RS256. L&#8217;impl\u00e9mentation ci-dessous n&#8217;utilise aucune biblioth\u00e8que externe, uniquement le module <code>crypto<\/code>. Un d\u00e9tail critique : Node.js retourne les signatures ECDSA au format DER, mais RFC 7518 impose le format P1363 (r et s concat\u00e9n\u00e9s, 64 octets fixes). La conversion DER vers P1363 est obligatoire.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ scripts\/jwt-es256.js\nconst crypto = require('crypto');\nconst fs = require('fs');\nconst path = require('path');\n\n\/\/ Base64url : pas de padding, + devient -, \/ devient _\nfunction toBase64url(buffer) {\n  return Buffer.from(buffer).toString('base64')\n    .replace(\/=\/g, '').replace(\/\\+\/g, '-').replace(\/\\\/\/g, '_');\n}\n\nfunction jsonBase64url(obj) {\n  return toBase64url(Buffer.from(JSON.stringify(obj)));\n}\n\n\/\/ Convertir signature DER (ASN.1) vers IEEE P1363 (r||s, 64 octets) pour JWT\nfunction derToP1363(der) {\n  \/\/ Structure DER : 0x30 [longueur-totale] 0x02 [len-r] [r] 0x02 [len-s] [s]\n  let offset = 2;\n  const rLen = der[offset + 1];\n  const rRaw = der.slice(offset + 2, offset + 2 + rLen);\n  offset += 2 + rLen;\n  const sLen = der[offset + 1];\n  const sRaw = der.slice(offset + 2, offset + 2 + sLen);\n\n  \/\/ Normaliser r et s \u00e0 32 octets (supprimer le 0x00 de padding si bit de signe)\n  const pad32 = (buf) => {\n    if (buf.length > 32) return buf.slice(buf.length - 32);\n    if (buf.length < 32) return Buffer.concat([Buffer.alloc(32 - buf.length), buf]);\n    return buf;\n  };\n\n  return Buffer.concat([pad32(rRaw), pad32(sRaw)]);\n}\n\nfunction createES256JWT(payload, privateKeyPem, passphrase) {\n  const header = { alg: 'ES256', typ: 'JWT' };\n  const encodedHeader = jsonBase64url(header);\n  const encodedPayload = jsonBase64url({\n    ...payload,\n    iat: Math.floor(Date.now() \/ 1000),\n    exp: Math.floor(Date.now() \/ 1000) + 3600\n  });\n\n  const signingInput = `${encodedHeader}.${encodedPayload}`;\n\n  const privateKey = passphrase\n    ? crypto.createPrivateKey({ key: privateKeyPem, passphrase })\n    : privateKeyPem;\n\n  const signatureDER = crypto.sign('SHA256', Buffer.from(signingInput), privateKey);\n  const signatureP1363 = derToP1363(signatureDER);\n\n  return `${signingInput}.${toBase64url(signatureP1363)}`;\n}\n\nfunction verifyES256JWT(token, publicKeyPem) {\n  const parts = token.split('.');\n  if (parts.length !== 3) throw new Error('JWT malform\u00e9');\n\n  const [headerB64, payloadB64, sigB64] = parts;\n  const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString());\n\n  if (payload.exp &#038;&#038; payload.exp < Math.floor(Date.now() \/ 1000)) {\n    throw new Error('JWT expir\u00e9');\n  }\n\n  \/\/ Reconvertir P1363 vers DER pour la v\u00e9rification Node.js\n  const sigP1363 = Buffer.from(sigB64, 'base64url');\n  const r = sigP1363.slice(0, 32);\n  const s = sigP1363.slice(32, 64);\n  const rPad = r[0] &#038; 0x80 ? Buffer.concat([Buffer.from([0x00]), r]) : r;\n  const sPad = s[0] &#038; 0x80 ? Buffer.concat([Buffer.from([0x00]), s]) : s;\n  const der = Buffer.concat([\n    Buffer.from([0x30, 4 + rPad.length + sPad.length]),\n    Buffer.from([0x02, rPad.length]), rPad,\n    Buffer.from([0x02, sPad.length]), sPad\n  ]);\n\n  const valid = crypto.verify('SHA256', Buffer.from(`${headerB64}.${payloadB64}`), publicKeyPem, der);\n  return { valid, payload };\n}\n\n\/\/ D\u00e9monstration\nconst keysDir = path.join(__dirname, '..', 'keys');\nconst privateKeyPem = fs.readFileSync(path.join(keysDir, 'ecdsa-private.pem'), 'utf8');\nconst publicKeyPem = fs.readFileSync(path.join(keysDir, 'ecdsa-public.pem'), 'utf8');\nconst PASSPHRASE = process.env.KEY_PASSPHRASE;\n\nconst token = createES256JWT(\n  { sub: 'utilisateur-42', email: 'alice@example.fr', role: 'admin' },\n  privateKeyPem,\n  PASSPHRASE\n);\nconsole.log('JWT ES256 :', token.substring(0, 80) + '...');\n\nconst { valid, payload } = verifyES256JWT(token, publicKeyPem);\nconsole.log('JWT valide :', valid);\nconsole.log('Payload :', JSON.stringify(payload, null, 2));<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-9-signer-des-fichiers-avec-streaming\">\u00c9tape 9 : Signer des fichiers avec streaming<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Pour les fichiers volumineux, charger l'int\u00e9gralit\u00e9 du contenu en m\u00e9moire avant de signer est inefficace. <code>crypto.createSign()<\/code> supporte l'API Writable stream et traite les donn\u00e9es par morceaux. Cette approche fonctionne sur des fichiers de plusieurs Go sans contrainte de m\u00e9moire.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ scripts\/sign-file.js\nconst crypto = require('crypto');\nconst fs = require('fs');\nconst path = require('path');\n\nasync function signFile(filePath, privateKeyPem, passphrase) {\n  const privateKey = crypto.createPrivateKey({ key: privateKeyPem, passphrase });\n\n  return new Promise((resolve, reject) => {\n    const signer = crypto.createSign('SHA256');\n    fs.createReadStream(filePath)\n      .on('data', chunk => signer.update(chunk))\n      .on('end', () => resolve(signer.sign(privateKey, 'hex')))\n      .on('error', reject);\n  });\n}\n\nasync function verifyFile(filePath, signatureHex, publicKeyPem) {\n  return new Promise((resolve, reject) => {\n    const verifier = crypto.createVerify('SHA256');\n    fs.createReadStream(filePath)\n      .on('data', chunk => verifier.update(chunk))\n      .on('end', () => {\n        try { resolve(verifier.verify(publicKeyPem, signatureHex, 'hex')); }\n        catch { resolve(false); }\n      })\n      .on('error', reject);\n  });\n}\n\nasync function main() {\n  const keysDir = path.join(__dirname, '..', 'keys');\n  const privateKeyPem = fs.readFileSync(path.join(keysDir, 'ecdsa-private.pem'), 'utf8');\n  const publicKeyPem = fs.readFileSync(path.join(keysDir, 'ecdsa-public.pem'), 'utf8');\n  const PASSPHRASE = process.env.KEY_PASSPHRASE;\n\n  \/\/ Cr\u00e9er un fichier de test\n  fs.writeFileSync('document.pdf.test', 'Contenu binaire simul\u00e9 pour la signature de fichier.');\n\n  const sig = await signFile('document.pdf.test', privateKeyPem, PASSPHRASE);\n  fs.writeFileSync('document.pdf.test.sig', sig);\n  console.log('Signature sauvegard\u00e9e dans document.pdf.test.sig');\n\n  const isValid = await verifyFile('document.pdf.test', sig, publicKeyPem);\n  console.log('Int\u00e9grit\u00e9 v\u00e9rifi\u00e9e :', isValid); \/\/ true\n\n  \/\/ Simuler une alt\u00e9ration\n  fs.appendFileSync('document.pdf.test', ' (modifi\u00e9)');\n  const isValidAfterMod = await verifyFile('document.pdf.test', sig, publicKeyPem);\n  console.log('Int\u00e9grit\u00e9 apr\u00e8s alt\u00e9ration :', isValidAfterMod); \/\/ false\n}\n\nmain().catch(console.error);<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-10-benchmark-ecdsa-vs-ed25519\">\u00c9tape 10 : Benchmark ECDSA vs Ed25519<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Avant de choisir l'algorithme pour un syst\u00e8me en production, mesurer les performances sur le mat\u00e9riel cible. Les r\u00e9sultats varient significativement selon le CPU (pr\u00e9sence d'instructions AES-NI, fr\u00e9quence, nombre de c\u0153urs).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ scripts\/benchmark.js\nconst crypto = require('crypto');\n\nconst ITERATIONS = 10000;\nconst data = Buffer.from('benchmark: performance des signatures num\u00e9riques Node.js 2026');\n\n\/\/ G\u00e9n\u00e9rer les cl\u00e9s pour le test\nconst { privateKey: ecPriv, publicKey: ecPub } = crypto.generateKeyPairSync('ec', {\n  namedCurve: 'P-256',\n  publicKeyEncoding: { type: 'spki', format: 'pem' },\n  privateKeyEncoding: { type: 'pkcs8', format: 'pem' }\n});\nconst { privateKey: ed25519Priv, publicKey: ed25519Pub } = crypto.generateKeyPairSync('ed25519', {\n  publicKeyEncoding: { type: 'spki', format: 'pem' },\n  privateKeyEncoding: { type: 'pkcs8', format: 'pem' }\n});\n\n\/\/ Pr\u00e9-calculer les signatures pour les benchmarks de v\u00e9rification\nconst ecSig = crypto.sign('SHA256', data, ecPriv);\nconst edSig = crypto.sign(null, data, ed25519Priv);\n\nfunction bench(label, fn) {\n  const start = process.hrtime.bigint();\n  for (let i = 0; i < ITERATIONS; i++) fn();\n  const ms = Number(process.hrtime.bigint() - start) \/ 1e6;\n  const ops = Math.round(ITERATIONS \/ (ms \/ 1000));\n  console.log(`${label.padEnd(32)}: ${ops.toLocaleString('fr-FR').padStart(10)} ops\/s`);\n  return ops;\n}\n\nconsole.log(`\\nBenchmark Node.js 22 \/ ${ITERATIONS.toLocaleString('fr-FR')} it\u00e9rations\\n`);\n\nconst ecSignOps = bench('ECDSA P-256 signature',    () => crypto.sign('SHA256', data, ecPriv));\nconst ecVerOps  = bench('ECDSA P-256 v\u00e9rification', () => crypto.verify('SHA256', data, ecPub, ecSig));\nconst edSignOps = bench('Ed25519 signature',         () => crypto.sign(null, data, ed25519Priv));\nconst edVerOps  = bench('Ed25519 v\u00e9rification',      () => crypto.verify(null, data, ed25519Pub, edSig));\n\nconsole.log(`\\nEd25519 est ${(edSignOps \/ ecSignOps).toFixed(1)}x plus rapide que ECDSA pour la signature`);\nconsole.log(`Ed25519 est ${(edVerOps \/ ecVerOps).toFixed(1)}x plus rapide que ECDSA pour la v\u00e9rification`);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">R\u00e9sultats typiques sur un serveur Linux Node.js 22 (Intel Xeon Platinum 8375C, 2 vCPU) :<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Op\u00e9ration<\/th><th>R\u00e9sultat mesur\u00e9<\/th><th>Ratio vs ECDSA sign<\/th><\/tr><\/thead><tbody><tr><td>ECDSA P-256 signature<\/td><td>~33 000 ops\/s<\/td><td>1x (r\u00e9f\u00e9rence)<\/td><\/tr><tr><td>ECDSA P-256 v\u00e9rification<\/td><td>~11 000 ops\/s<\/td><td>0.33x<\/td><\/tr><tr><td>Ed25519 signature<\/td><td>~115 000 ops\/s<\/td><td><strong>3.5x plus rapide<\/strong><\/td><\/tr><tr><td>Ed25519 v\u00e9rification<\/td><td>~44 000 ops\/s<\/td><td><strong>4x plus rapide<\/strong><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Pour une API REST qui valide 1 000 JWT par seconde, passer d'ES256 (ECDSA P-256) \u00e0 EdDSA (Ed25519) lib\u00e8re environ 75% de la charge CPU consacr\u00e9e aux v\u00e9rifications cryptographiques. Sur un microservice \u00e0 fort d\u00e9bit, c'est la diff\u00e9rence entre 1 instance et 4 instances.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-11-construire-un-service-rest-de-signature\">\u00c9tape 11 : Construire un service REST de signature<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Le service Express 5 expose trois endpoints : <code>POST \/sign<\/code>, <code>POST \/verify<\/code>, et <code>GET \/public-key<\/code>. Les cl\u00e9s sont charg\u00e9es une fois au d\u00e9marrage et r\u00e9utilis\u00e9es pour toutes les requ\u00eates, \u00e9vitant l'overhead de d\u00e9chiffrement \u00e0 chaque requ\u00eate.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ server.js\nconst express = require('express');\nconst crypto = require('crypto');\nconst fs = require('fs');\nconst path = require('path');\n\nconst app = express();\napp.use(express.json({ limit: '64kb' }));\n\nconst PASSPHRASE = process.env.KEY_PASSPHRASE;\nif (!PASSPHRASE) { console.error('KEY_PASSPHRASE manquante'); process.exit(1); }\n\n\/\/ Charger et d\u00e9chiffrer les cl\u00e9s au d\u00e9marrage (une seule fois)\nconst KEYS_DIR = path.join(__dirname, 'keys');\nlet privateKeyObj, publicKeyPem;\ntry {\n  const rawPrivate = fs.readFileSync(path.join(KEYS_DIR, 'ecdsa-private.pem'), 'utf8');\n  publicKeyPem = fs.readFileSync(path.join(KEYS_DIR, 'ecdsa-public.pem'), 'utf8');\n  privateKeyObj = crypto.createPrivateKey({ key: rawPrivate, passphrase: PASSPHRASE });\n  console.log('Cl\u00e9s ECDSA charg\u00e9es');\n} catch (err) {\n  console.error('Chargement des cl\u00e9s \u00e9chou\u00e9 :', err.message); process.exit(1);\n}\n\n\/\/ POST \/sign \u2014 Signer un message\napp.post('\/sign', (req, res) => {\n  const { data } = req.body;\n  if (!data || typeof data !== 'string') return res.status(400).json({ error: '\"data\" requis (string)' });\n  if (data.length > 65536) return res.status(413).json({ error: 'Donn\u00e9es trop volumineuses (max 64 Ko)' });\n\n  try {\n    const sig = crypto.sign('SHA256', Buffer.from(data, 'utf8'), privateKeyObj);\n    res.json({ data, signature: sig.toString('base64'), algorithm: 'ECDSA-P256-SHA256', ts: new Date().toISOString() });\n  } catch { res.status(500).json({ error: 'Erreur de signature' }); }\n});\n\n\/\/ POST \/verify \u2014 V\u00e9rifier une signature\napp.post('\/verify', (req, res) => {\n  const { data, signature } = req.body;\n  if (!data || !signature) return res.status(400).json({ error: '\"data\" et \"signature\" requis' });\n\n  try {\n    const valid = crypto.verify('SHA256', Buffer.from(data, 'utf8'), publicKeyPem,\n      Buffer.from(signature, 'base64'));\n    res.json({ valid, data });\n  } catch { res.status(400).json({ valid: false, error: 'Signature invalide ou malform\u00e9e' }); }\n});\n\n\/\/ GET \/public-key \u2014 Distribuer la cl\u00e9 publique\napp.get('\/public-key', (_req, res) => res.type('text').send(publicKeyPem));\n\napp.listen(3000, () => console.log('Service de signature sur http:\/\/localhost:3000'));\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Tester avec curl :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># D\u00e9marrer le service\nKEY_PASSPHRASE=mon-secret-fort node server.js\n\n# Signer un document\nSIGNATURE=$(curl -s -X POST http:\/\/localhost:3000\/sign \\\n  -H 'Content-Type: application\/json' \\\n  -d '{\"data\":\"Bon de commande #FR-2026-042\"}' | python3 -c \"import json,sys; print(json.load(sys.stdin)['signature'])\")\n\necho \"Signature : $SIGNATURE\"\n\n# V\u00e9rifier la signature\ncurl -s -X POST http:\/\/localhost:3000\/verify \\\n  -H 'Content-Type: application\/json' \\\n  -d \"{\\\"data\\\":\\\"Bon de commande #FR-2026-042\\\",\\\"signature\\\":\\\"$SIGNATURE\\\"}\" | python3 -m json.tool\n\n# Sortie :\n# { \"valid\": true, \"data\": \"Bon de commande #FR-2026-042\" }<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"etape-12-securiser-les-cles-et-deployer-en-production\">\u00c9tape 12 : S\u00e9curiser les cl\u00e9s et d\u00e9ployer en production<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Le stockage des cl\u00e9s priv\u00e9es est le point le plus critique de l'architecture. Une cl\u00e9 priv\u00e9e compromise invalide r\u00e9trospectivement toutes les signatures \u00e9mises depuis cette cl\u00e9. Cinq r\u00e8gles non n\u00e9gociables pour la production :<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Git exclusion :<\/strong> Ajouter <code>keys\/<\/code> et <code>*.pem<\/code> au <code>.gitignore<\/code> avant le premier commit. Une cl\u00e9 priv\u00e9e pouss\u00e9e accidentellement doit \u00eatre consid\u00e9r\u00e9e comme compromise, m\u00eame si le commit est supprim\u00e9 de l'historique (les forks et les caches peuvent la conserver).<\/li><li><strong>Gestionnaire de secrets :<\/strong> Utiliser HashiCorp Vault, AWS Secrets Manager, Azure Key Vault, ou GCP Secret Manager. Ces services journalisent chaque acc\u00e8s, supportent la rotation automatique, et permettent de r\u00e9voquer l'acc\u00e8s sans redeployer l'application.<\/li><li><strong>Permissions fichier Linux :<\/strong> La cl\u00e9 priv\u00e9e doit avoir les permissions <code>600<\/code> (lecture\/\u00e9criture pour le propri\u00e9taire uniquement). V\u00e9rifier avec <code>ls -la keys\/ecdsa-private.pem<\/code>. Corriger avec <code>chmod 600 keys\/ecdsa-private.pem<\/code>.<\/li><li><strong>Passphrase via variable d'environnement :<\/strong> Ne jamais passer la passphrase en argument de ligne de commande (visible dans <code>ps aux<\/code> et les logs syst\u00e8me). Utiliser <code>process.env.KEY_PASSPHRASE<\/code> exclusivement.<\/li><li><strong>Rotation planifi\u00e9e :<\/strong> Mettre en place une rotation des cl\u00e9s tous les 12 \u00e0 24 mois. Publier la nouvelle cl\u00e9 publique avec un d\u00e9lai de 48 heures avant de retirer l'ancienne pour permettre l'expiration des tokens existants.<\/li><\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code># Audit de s\u00e9curit\u00e9 des cl\u00e9s (\u00e0 inclure dans la CI\/CD)\n\n# V\u00e9rifier les permissions des cl\u00e9s priv\u00e9es\nfind keys\/ -name \"*private*\" -not -perm 600 && echo \"ALERTE : permissions incorrectes\" || echo \"Permissions OK\"\n\n# V\u00e9rifier l'empreinte SHA-256 de la cl\u00e9 publique (pour audit)\nopenssl pkey -in keys\/ecdsa-public.pem -pubin -outform DER 2>\/dev\/null | openssl dgst -sha256\n# SHA2-256(stdin)= a3b5c7... (documenter dans le registre d'audit)\n\n# V\u00e9rifier que les cl\u00e9s ne sont pas dans l'historique Git\ngit log --all --full-history -- \"keys\/*.pem\" && echo \"ALERTE : cl\u00e9s dans Git\" || echo \"Git propre\"\n\n# Calculer la date d'expiration recommand\u00e9e\nnode -e \"const d = new Date(); d.setFullYear(d.getFullYear() + 1); console.log('Rotation recommand\u00e9e avant :', d.toISOString().split('T')[0])\"<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"tableau-recapitulatif-choisir-entre-ecdsa-et-ed25519\">Tableau r\u00e9capitulatif : choisir entre ECDSA et Ed25519<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Crit\u00e8re<\/th><th>ECDSA P-256<\/th><th>ECDSA P-384<\/th><th>Ed25519<\/th><\/tr><\/thead><tbody><tr><td>Conformit\u00e9 FIPS 186-5<\/td><td>Oui<\/td><td>Oui<\/td><td>Oui (depuis 2023)<\/td><\/tr><tr><td>Recommandation ANSSI<\/td><td>Oui (applications civiles)<\/td><td>Requis (classifi\u00e9)<\/td><td>Non officiel<\/td><\/tr><tr><td>Performance signature<\/td><td>~33 000 ops\/s<\/td><td>~18 000 ops\/s<\/td><td>~115 000 ops\/s<\/td><\/tr><tr><td>Taille de signature<\/td><td>70-72 octets (DER variable)<\/td><td>102-104 octets<\/td><td>64 octets (fixe)<\/td><\/tr><tr><td>JWT support\u00e9<\/td><td>ES256 (universel)<\/td><td>ES384<\/td><td>EdDSA (RFC 8037)<\/td><\/tr><tr><td>SSH (OpenSSH)<\/td><td>Oui<\/td><td>Oui<\/td><td>Recommand\u00e9 (d\u00e9faut)<\/td><\/tr><tr><td>R\u00e9sistance timing attacks<\/td><td>D\u00e9pend impl\u00e9mentation<\/td><td>D\u00e9pend impl\u00e9mentation<\/td><td>Oui (par construction)<\/td><\/tr><tr><td>Chiffrement cl\u00e9 priv\u00e9e Node.js<\/td><td>Oui (AES-256-CBC)<\/td><td>Oui<\/td><td>Non (limitation OpenSSL)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"7-pieges-courants-a-eviter\">7 pi\u00e8ges courants \u00e0 \u00e9viter<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Pi\u00e8ge 1 : Confondre secp256k1 (Bitcoin) et P-256 (NIST).<\/strong> Ces deux courbes ont des param\u00e8tres diff\u00e9rents et ne sont pas interchangeables. <code>namedCurve: 'P-256'<\/code> et <code>namedCurve: 'prime256v1'<\/code> d\u00e9signent la m\u00eame courbe NIST P-256. <code>namedCurve: 'secp256k1'<\/code> est la courbe Bitcoin, non recommand\u00e9e pour TLS ou JWT.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Pi\u00e8ge 2 : Passer un algorithme de hachage \u00e0 Ed25519.<\/strong> <code>crypto.sign('SHA256', data, ed25519Key)<\/code> g\u00e9n\u00e8re une <code>TypeError: Invalid key type<\/code> en Node.js 22. Ed25519 g\u00e8re le hachage en interne. Toujours utiliser <code>crypto.sign(null, data, key)<\/code> pour les cl\u00e9s Ed25519.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Pi\u00e8ge 3 : Signature DER dans les JWT sans conversion.<\/strong> Node.js retourne les signatures ECDSA au format DER (longueur variable, 70-72 octets). RFC 7518 impose le format P1363 (r||s, 64 octets fixes). Sans la conversion <code>derToP1363()<\/code> de l'\u00e9tape 8, le JWT \u00e9choue la v\u00e9rification dans toute biblioth\u00e8que conforme (jsonwebtoken, jose, python-jwt, etc.).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Pi\u00e8ge 4 : Stocker la cl\u00e9 priv\u00e9e ECDSA sans passphrase.<\/strong> Un fichier PEM non chiffr\u00e9 est lisible en clair. Toute vuln\u00e9rabilit\u00e9 path traversal, tout snapshot S3 mal configur\u00e9, ou tout backup compromis expose imm\u00e9diatement la cl\u00e9. Toujours chiffrer avec <code>cipher: 'aes-256-cbc'<\/code> et une passphrase d'au moins 32 caract\u00e8res.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Pi\u00e8ge 5 : Charger et d\u00e9chiffrer la cl\u00e9 priv\u00e9e \u00e0 chaque requ\u00eate.<\/strong> <code>crypto.createPrivateKey()<\/code> est co\u00fbteux car il d\u00e9chiffre AES-256-CBC \u00e0 chaque appel. Sur 10 000 requ\u00eates\/seconde, cela repr\u00e9sente 10 000 op\u00e9rations de d\u00e9chiffrement inutiles. Charger l'objet <code>KeyObject<\/code> une seule fois au d\u00e9marrage du serveur et le r\u00e9utiliser.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Pi\u00e8ge 6 : Utiliser SHA-1 comme algorithme de hachage avec ECDSA.<\/strong> <code>crypto.sign('SHA1', data, ecKey)<\/code> compile et s'ex\u00e9cute sans erreur, mais SHA-1 est cryptographiquement cass\u00e9 depuis la d\u00e9monstration SHAttered en 2017. Toujours utiliser SHA-256 minimum, SHA-384 pour P-384.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Pi\u00e8ge 7 : Signer sans valider les donn\u00e9es d'entr\u00e9e.<\/strong> Un endpoint <code>\/sign<\/code> qui accepte n'importe quelle cha\u00eene peut \u00eatre exploit\u00e9 pour forger des assertions arbitraires (tokens admin, faux contrats). Valider le format, la longueur, le sch\u00e9ma des donn\u00e9es, et journaliser chaque signature avec l'identit\u00e9 de l'appelant avant de signer.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"depannage-8-erreurs-et-leurs-solutions\">D\u00e9pannage : 8 erreurs et leurs solutions<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Message d'erreur<\/th><th>Cause<\/th><th>Solution<\/th><\/tr><\/thead><tbody><tr><td><code>error:0909006C:PEM routines:no start line<\/code><\/td><td>Fichier PEM tronqu\u00e9 ou corrompu<\/td><td>V\u00e9rifier que le fichier commence par <code>-----BEGIN<\/code> et se termine par <code>-----END<\/code> avec un retour \u00e0 la ligne<\/td><\/tr><tr><td><code>error:06065064:bad decrypt<\/code><\/td><td>Passphrase incorrecte<\/td><td>V\u00e9rifier <code>KEY_PASSPHRASE<\/code>. Attention aux caract\u00e8res sp\u00e9ciaux interpr\u00e9t\u00e9s par le shell<\/td><\/tr><tr><td><code>TypeError: Invalid key type<\/code><\/td><td>Algorithme de hachage pass\u00e9 \u00e0 Ed25519<\/td><td>Remplacer <code>'SHA256'<\/code> par <code>null<\/code> dans <code>crypto.sign()<\/code><\/td><\/tr><tr><td><code>ERR_OSSL_UNSUPPORTED<\/code><\/td><td>Algorithme legacy d\u00e9sactiv\u00e9 en Node.js 17+<\/td><td>Mettre \u00e0 jour vers des algorithmes modernes. Ne pas utiliser <code>--openssl-legacy-provider<\/code> en production<\/td><\/tr><tr><td>JWT rejet\u00e9 par biblioth\u00e8ques tierces<\/td><td>Signature DER au lieu de P1363<\/td><td>Appliquer <code>derToP1363()<\/code>. V\u00e9rifier : <code>Buffer.from(sig,'base64').length<\/code> doit \u00eatre 64<\/td><\/tr><tr><td><code>EACCES: permission denied<\/code><\/td><td>Permissions insuffisantes sur le fichier de cl\u00e9<\/td><td><code>chmod 600 keys\/ecdsa-private.pem<\/code>. Le processus Node.js doit \u00eatre l'utilisateur propri\u00e9taire<\/td><\/tr><tr><td><code>error:100000F7: unknown group<\/code><\/td><td>Nom de courbe non reconnu<\/td><td>V\u00e9rifier avec <code>crypto.getCurves()<\/code>. Utiliser <code>'P-256'<\/code> ou <code>'prime256v1'<\/code> (synonymes)<\/td><\/tr><tr><td>Performances 100x inf\u00e9rieures \u00e0 l'attendu<\/td><td>Cl\u00e9 recharg\u00e9e et d\u00e9chiffr\u00e9e \u00e0 chaque requ\u00eate<\/td><td>Appeler <code>crypto.createPrivateKey()<\/code> une seule fois au d\u00e9marrage et stocker le <code>KeyObject<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"conseils-avances-rotation-hsm-et-sigstore\">Conseils avanc\u00e9s : rotation, HSM et Sigstore<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Rotation des cl\u00e9s sans downtime :<\/strong> Le pattern JWKS (JSON Web Key Set) permet de publier plusieurs cl\u00e9s publiques simultan\u00e9ment via <code>GET \/.well-known\/jwks.json<\/code>. Chaque cl\u00e9 porte un identifiant <code>kid<\/code>. Pour une rotation : (1) g\u00e9n\u00e9rer la nouvelle paire, (2) ajouter la nouvelle cl\u00e9 publique au JWKS avec un nouveau <code>kid<\/code>, (3) attendre l'expiration des tokens existants (TTL + 15 min de marge), (4) basculer les nouvelles signatures vers la nouvelle cl\u00e9 priv\u00e9e, (5) retirer l'ancienne cl\u00e9 publique du JWKS 24 heures plus tard.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Int\u00e9gration HSM (Hardware Security Module) :<\/strong> Les HSM (AWS CloudHSM, Thales Luna, YubiHSM 2) g\u00e9n\u00e8rent et stockent les cl\u00e9s priv\u00e9es dans un module mat\u00e9riel certifi\u00e9 FIPS 140-3 niveau 3. La cl\u00e9 priv\u00e9e ne quitte jamais le HSM : seules les op\u00e9rations de signature sont d\u00e9l\u00e9gu\u00e9es. Node.js interagit avec les HSM via PKCS#11 (biblioth\u00e8que <code>pkcs11js<\/code> sur npm) ou les SDK propri\u00e9taires. Le YubiHSM 2, \u00e0 650 EUR, supporte Ed25519 et ECDSA P-256 nativement.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Sigstore et npm provenance :<\/strong> Depuis npm 9.5, la commande <code>npm publish --provenance<\/code> g\u00e9n\u00e8re une attestation de provenance sign\u00e9e ECDSA via Sigstore (Fulcio CA + Rekor transparency log). Cette attestation lie le package \u00e0 son d\u00e9p\u00f4t GitHub via OpenID Connect, sans gestion de cl\u00e9 priv\u00e9e. C'est le mod\u00e8le \"keyless signing\" adopt\u00e9 par Python (PyPI Trusted Publishers) et Ruby (RubyGems OIDC). La v\u00e9rification s'effectue avec <code>npm audit signatures<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Empreinte de cl\u00e9 publique pour audit :<\/strong> Dans les protocoles Trust On First Use (TOFU) et pour les registres d'audit, calculer l'empreinte SHA-256 de la cl\u00e9 publique SPKI :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Calculer l'empreinte SHA-256 de la cl\u00e9 publique (pour audit et TOFU)\nconst crypto = require('crypto');\nconst fs = require('fs');\n\nconst pubKeyPem = fs.readFileSync('keys\/ecdsa-public.pem', 'utf8');\nconst pubKeyDer = crypto.createPublicKey(pubKeyPem).export({ type: 'spki', format: 'der' });\nconst fingerprint = crypto.createHash('SHA256').update(pubKeyDer).digest('base64');\nconsole.log('Empreinte SHA-256 :', fingerprint);\n\/\/ Format : SHA256:a3b5c7d9... (48 caract\u00e8res en Base64)\n\/\/ \u00c0 documenter dans le registre d'audit et \u00e0 afficher dans l'interface de gestion des cl\u00e9s<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"recapitulatif-des-fichiers-du-projet\">R\u00e9capitulatif des fichiers du projet<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Fichier<\/th><th>Description<\/th><th>\u00c9tape<\/th><\/tr><\/thead><tbody><tr><td><code>scripts\/generate-ecdsa.js<\/code><\/td><td>G\u00e9n\u00e9ration cl\u00e9s ECDSA P-256 + AES chiffrement<\/td><td>3<\/td><\/tr><tr><td><code>scripts\/sign-ecdsa.js<\/code><\/td><td>Signature ECDSA avec passphrase<\/td><td>4<\/td><\/tr><tr><td><code>scripts\/verify-ecdsa.js<\/code><\/td><td>V\u00e9rification ECDSA<\/td><td>5<\/td><\/tr><tr><td><code>scripts\/generate-ed25519.js<\/code><\/td><td>G\u00e9n\u00e9ration cl\u00e9s Ed25519<\/td><td>6<\/td><\/tr><tr><td><code>scripts\/sign-verify-ed25519.js<\/code><\/td><td>Signature et v\u00e9rification Ed25519<\/td><td>7<\/td><\/tr><tr><td><code>scripts\/jwt-es256.js<\/code><\/td><td>JWT ES256 avec conversion DER\/P1363<\/td><td>8<\/td><\/tr><tr><td><code>scripts\/sign-file.js<\/code><\/td><td>Signature de fichiers via stream<\/td><td>9<\/td><\/tr><tr><td><code>scripts\/benchmark.js<\/code><\/td><td>Benchmark comparatif ECDSA vs Ed25519<\/td><td>10<\/td><\/tr><tr><td><code>server.js<\/code><\/td><td>Service REST Express : \/sign \/verify \/public-key<\/td><td>11<\/td><\/tr><tr><td><code>keys\/<\/code><\/td><td>Cl\u00e9s PEM (exclu de Git via .gitignore)<\/td><td>12<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"couverture-connexe\">Couverture connexe<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"articles-lies-sur-shattered-io\">Articles li\u00e9s sur shattered.io<\/h3>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"\/fr\/signatures-numeriques\/\">Signatures num\u00e9riques : comment le hachage et les cl\u00e9s garantissent l'authenticit\u00e9<\/a> - fondations th\u00e9oriques des signatures num\u00e9riques ECDSA et EdDSA<\/li><li><a href=\"\/fr\/hmac-sha256-nodejs\/\">HMAC-SHA256 en Node.js : signer une API en 12 \u00e9tapes [2026]<\/a> - alternative sym\u00e9trique pour l'authentification d'API interne<\/li><li><a href=\"\/fr\/authentification-jwt-nodejs\/\">Authentification JWT en Node.js : 12 \u00e9tapes [2026]<\/a> - impl\u00e9menter RS256 et HS256 avec la biblioth\u00e8que jsonwebtoken<\/li><li><a href=\"\/fr\/bcrypt-nodejs-hachage-mot-de-passe\/\">bcrypt Node.js : hacher un mot de passe, 12 \u00e9tapes [2026]<\/a> - s\u00e9curiser les mots de passe au repos<\/li><li><a href=\"\/fr\/chiffrer-fichier-gpg-tutoriel\/\">GPG : chiffrer fichiers et emails, 12 \u00e9tapes [2026]<\/a> - Ed25519 via GnuPG pour la signature et le chiffrement de fichiers<\/li><li><a href=\"\/fr\/security-headers-nodejs\/\">En-t\u00eates de s\u00e9curit\u00e9 HTTP en Node.js : 12 \u00e9tapes, 30 Min [2026]<\/a> - compl\u00e9ter la s\u00e9curit\u00e9 de l'API avec Helmet.js<\/li><li><a href=\"\/cryptography\/\">Cryptographie : pilier de la confiance num\u00e9rique<\/a> - panorama de tous les sujets cryptographiques du cluster<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"ressources-officielles\">Ressources officielles<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"https:\/\/nodejs.org\/api\/crypto.html\" rel=\"noopener\" target=\"_blank\">Documentation officielle module crypto Node.js<\/a> - r\u00e9f\u00e9rence compl\u00e8te de l'API crypto avec exemples<\/li><li><a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc8032\" rel=\"noopener\" target=\"_blank\">RFC 8032 : Edwards-Curve Digital Signature Algorithm (EdDSA)<\/a> - standard IETF d\u00e9finissant Ed25519<\/li><li><a href=\"https:\/\/csrc.nist.gov\/publications\/detail\/fips\/186\/5\/final\" rel=\"noopener\" target=\"_blank\">FIPS 186-5 : Digital Signature Standard<\/a> - standard NIST pour ECDSA P-256 et P-384<\/li><li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/SubtleCrypto\/sign\" rel=\"noopener\" target=\"_blank\">MDN : SubtleCrypto.sign()<\/a> - API Web Crypto c\u00f4t\u00e9 navigateur, compl\u00e9mentaire \u00e0 Node.js<\/li><li><a href=\"https:\/\/ed25519.cr.yp.to\/\" rel=\"noopener\" target=\"_blank\">ed25519.cr.yp.to<\/a> - site officiel de Daniel Bernstein sur Ed25519 et Curve25519<\/li><\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"faq\">FAQ<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"quelle-est-la-difference-entre-ecdsa-et-eddsa\">Quelle est la diff\u00e9rence entre ECDSA et EdDSA ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">ECDSA (Elliptic Curve DSA) et EdDSA (Edwards-curve DSA) sont deux familles d'algorithmes de signature sur courbes elliptiques. ECDSA utilise les courbes de Weierstrass (P-256, P-384, secp256k1) et g\u00e9n\u00e8re des signatures DER de taille variable. EdDSA utilise les courbes d'Edwards tordues (Ed25519, Ed448) et produit des signatures de taille fixe. EdDSA est plus rapide (3 \u00e0 4 fois), r\u00e9sistant par construction aux attaques par canaux auxiliaires, et ne requiert pas de nonce al\u00e9atoire externe (la valeur est d\u00e9riv\u00e9e d\u00e9terministiquement de la cl\u00e9 priv\u00e9e et du message). Dans Node.js, la diff\u00e9rence pratique principale est que Ed25519 prend <code>null<\/code> comme algorithme de hachage, tandis qu'ECDSA prend <code>'SHA256'<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"peut-on-utiliser-ed25519-dans-les-jwt\">Peut-on utiliser Ed25519 dans les JWT ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Oui. Le RFC 8037 d\u00e9finit l'algorithme <code>EdDSA<\/code> pour les JWT avec Ed25519. La biblioth\u00e8que <code>jose<\/code> (npm) supporte EdDSA depuis 2021. La valeur <code>alg: \"EdDSA\"<\/code> avec <code>crv: \"Ed25519\"<\/code> dans la cl\u00e9 JWK identifie cet algorithme. Attention : <code>jsonwebtoken<\/code> ne supporte EdDSA qu'\u00e0 partir de la version 9.0.0 (sortie en 2023). Pour les syst\u00e8mes existants utilisant des versions ant\u00e9rieures, rester sur ES256 ou migrer vers <code>jose<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"ecdsa-p-256-resiste-t-il-aux-ordinateurs-quantiques\">ECDSA P-256 r\u00e9siste-t-il aux ordinateurs quantiques ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Non. L'algorithme de Shor r\u00e9sout le probl\u00e8me du logarithme discret sur courbes elliptiques en temps polynomial sur un ordinateur quantique suffisamment puissant. Cela compromettrait ECDSA, Ed25519, et RSA simultan\u00e9ment. Le NIST a finalis\u00e9 les premiers standards post-quantiques en 2024 : FIPS 204 (CRYSTALS-Dilithium pour les signatures) et FIPS 205 (SPHINCS+ en option). Les applications critiques sur 10 ans devraient planifier une migration vers ces nouveaux standards. Pour la plupart des applications, les courbes elliptiques restent s\u00e9curis\u00e9es pour les 10 \u00e0 20 prochaines ann\u00e9es selon les estimations du NIST.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"quelle-est-la-difference-entre-secp256k1-et-p-256\">Quelle est la diff\u00e9rence entre secp256k1 et P-256 ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Ce sont deux courbes elliptiques distinctes. P-256 (prime256v1, secp256r1) est une courbe NIST avec des param\u00e8tres d\u00e9finis par le gouvernement am\u00e9ricain, utilis\u00e9e dans TLS, FIDO2, et les certificats X.509. secp256k1 est une courbe Koblitz choisie pour ses propri\u00e9t\u00e9s arithm\u00e9tiques optimis\u00e9es, adopt\u00e9e par Bitcoin en 2008 et Ethereum. secp256k1 n'est pas recommand\u00e9e pour TLS ou JWT (absence d'approbation FIPS, usage cantonn\u00e9 \u00e0 la blockchain). Ne jamais les interchanger dans une application.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"comment-faire-pivoter-les-cles-ecdsa-sans-interruption-de-service\">Comment faire pivoter les cl\u00e9s ECDSA sans interruption de service ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Le protocole de rotation z\u00e9ro-downtime avec JWKS suit 5 \u00e9tapes : (1) G\u00e9n\u00e9rer une nouvelle paire de cl\u00e9s ; (2) Publier la nouvelle cl\u00e9 publique dans le JWKS avec un nouveau <code>kid<\/code>, en conservant l'ancienne ; (3) Attendre l'expiration de tous les tokens existants (TTL maximal + 15 minutes de marge) ; (4) Basculer la signature des nouveaux tokens vers la nouvelle cl\u00e9 priv\u00e9e ; (5) Retirer l'ancienne cl\u00e9 publique du JWKS 24 heures apr\u00e8s l'\u00e9tape 4. Journaliser chaque \u00e9tape avec horodatage pour l'audit. Ne jamais retirer la cl\u00e9 publique avant que tous les tokens sign\u00e9s avec la cl\u00e9 correspondante soient expir\u00e9s.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"node-js-crypto-est-il-conforme-fips-140-3\">Node.js crypto est-il conforme FIPS 140-3 ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Node.js peut activer le mode FIPS OpenSSL avec <code>node --enable-fips<\/code> ou via <code>crypto.setFips(1)<\/code>. En mode FIPS, les algorithmes non conformes (MD5, SHA-1, DES, RC4) sont d\u00e9sactiv\u00e9s et toute tentative de les utiliser g\u00e9n\u00e8re une erreur. ECDSA P-256 et P-384 sont conformes FIPS 186-5. Ed25519 est approuv\u00e9 dans FIPS 186-5 depuis 2023, mais la conformit\u00e9 effective d\u00e9pend de la version d'OpenSSL-FIPS utilis\u00e9e. Pour les applications soumises \u00e0 des exigences FIPS formelles, v\u00e9rifier avec le validateur NIST CMVP.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"peut-on-utiliser-les-memes-cles-pour-signer-et-chiffrer\">Peut-on utiliser les m\u00eames cl\u00e9s pour signer et chiffrer ?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Non, c'est une mauvaise pratique cryptographique. Les cl\u00e9s de signature (ECDSA, Ed25519) et les cl\u00e9s de chiffrement (ECDH avec X25519 pour l'\u00e9change de cl\u00e9s, puis AES pour le chiffrement sym\u00e9trique) ont des usages distincts. M\u00e9langer les deux cr\u00e9e des vuln\u00e9rabilit\u00e9s th\u00e9oriques et complique la r\u00e9vocation : r\u00e9voquer la cl\u00e9 de signature invalide aussi le d\u00e9chiffrement des messages archiv\u00e9s. G\u00e9n\u00e9rer des paires de cl\u00e9s distinctes pour chaque usage, avec des cycles de vie ind\u00e9pendants.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Le module crypto de Node.js 22 prend en charge ECDSA et Ed25519 nativement, sans aucune d\u00e9pendance externe. Une cl\u00e9 ECDSA P-256 offre le m\u00eame niveau de s\u00e9curit\u00e9 qu&#8217;une cl\u00e9 RSA\u2026<\/p>\n","protected":false},"author":3,"featured_media":297,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-296","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cryptography"],"_links":{"self":[{"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/posts\/296","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/comments?post=296"}],"version-history":[{"count":0,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/posts\/296\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/media\/297"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/media?parent=296"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/categories?post=296"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/fr\/wp-json\/wp\/v2\/tags?post=296"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}