{"id":193,"date":"2026-06-17T20:33:16","date_gmt":"2026-06-17T20:33:16","guid":{"rendered":"https:\/\/shattered.io\/it\/2026\/06\/17\/ecc-ecdh-ecdsa-nodejs\/"},"modified":"2026-06-17T20:35:00","modified_gmt":"2026-06-17T20:35:00","slug":"ecc-ecdh-ecdsa-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/it\/ecc-ecdh-ecdsa-nodejs\/","title":{"rendered":"ECC in Node.js: ECDH ed ECDSA in 12 Step [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">La crittografia a curva ellittica (ECC) permette di raggiungere 128 bit di sicurezza con una chiave da 256 bit, mentre RSA richiede 3.072 bit per lo stesso livello di protezione. Meno dati, meno CPU, stessa solidit\u00e0 crittografica: questo spiega perch\u00e9 ECC \u00e8 lo standard scelto da TLS 1.3, SSH, Signal e Bitcoin. In questa guida percorri 12 step pratici in Node.js: dalla generazione di chiavi P-256, allo scambio ECDH, alla derivazione con HKDF, fino alla firma ECDSA e a un progetto completo di messaggistica cifrata.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"prerequisiti\">Prerequisiti<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Node.js v20.0.0 o superiore<\/strong> (consigliato: v22 LTS o v26): tutte le API usate in questa guida sono native nel modulo <code>node:crypto<\/code>, senza dipendenze esterne<\/li>\n<li><strong>npm v9+<\/strong> per inizializzare il progetto<\/li>\n<li>Conoscenza base di JavaScript asincrono e dei tipi <code>Buffer<\/code><\/li>\n<li>Terminale con accesso a <code>openssl<\/code> (preinstallato su macOS e Linux; su Windows usa WSL o Git Bash)<\/li>\n<li>Facoltativo: <code>Node.js v26.3.0<\/code> per accedere alle API pi\u00f9 recenti come il formato <code>raw-public<\/code> per le chiavi EC<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Verifica la versione con <code>node -v<\/code>. Se usi Node.js 18, le funzioni <code>hkdfSync<\/code> e <code>generateKeyPairSync<\/code> con <code>ec<\/code> sono gi\u00e0 disponibili.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"cose-la-crittografia-a-curva-ellittica\">Cos&#8217;\u00e8 la crittografia a curva ellittica<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Una curva ellittica \u00e8 l&#8217;insieme dei punti che soddisfano l&#8217;equazione <code>y\u00b2 = x\u00b3 + ax + b<\/code> su un campo finito. Il segreto crittografico sta nel problema del logaritmo discreto su curve ellittiche (ECDLP): dato un punto base <code>G<\/code> e un punto pubblico <code>Q = kG<\/code>, risalire allo scalare intero <code>k<\/code> (la chiave privata) \u00e8 computazionalmente infeasibile per curve ben scelte.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nella pratica questo significa due operazioni fondamentali:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>ECDH (Elliptic Curve Diffie-Hellman)<\/strong>: Alice e Bob si scambiano le rispettive chiavi pubbliche. Ognuno moltiplica la propria chiave privata per la chiave pubblica dell&#8217;altro e ottiene lo stesso punto condiviso, che diventa il segreto comune.<\/li>\n<li><strong>ECDSA (Elliptic Curve Digital Signature Algorithm)<\/strong>: la chiave privata genera una firma che chiunque pu\u00f2 verificare usando la chiave pubblica, senza conoscere la chiave privata.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Secondo il documento <a href=\"https:\/\/nvlpubs.nist.gov\/nistpubs\/SpecialPublications\/NIST.SP.800-186.pdf\" rel=\"noopener noreferrer\" target=\"_blank\">NIST SP 800-186<\/a>, la sicurezza di una curva a <code>k<\/code> bit \u00e8 circa <code>k\/2<\/code> bit: una curva P-256 offre 128 bit di sicurezza. Lo stesso livello richiede una chiave RSA da 3.072 bit. In termini di velocit\u00e0, le operazioni ECC su P-256 risultano significativamente pi\u00f9 rapide delle operazioni RSA equivalenti, con un consumo di memoria nettamente inferiore: un elemento determinante per dispositivi IoT, app mobili e server ad alto traffico.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Le applicazioni reali che usano ECC includono: TLS 1.3 (scambio di chiavi ECDHE con P-256 o X25519), SSH con chiavi Ed25519, JWT firmati ES256, certificati HTTPS ECDSA, e Bitcoin (secp256k1 per le firme delle transazioni).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"confronto-tra-le-principali-curve-ellittiche\">Confronto tra le principali curve ellittiche<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Non tutte le curve ellittiche sono equivalenti per sicurezza o interoperabilit\u00e0. La tabella seguente riassume le curve pi\u00f9 rilevanti per Node.js, con i rispettivi livelli di sicurezza e usi tipici.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Curva<\/th>\n<th>Nome OpenSSL<\/th>\n<th>Sicurezza (bit)<\/th>\n<th>Uso principale<\/th>\n<th>Standard<\/th>\n<th>Supporto Node.js<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>P-256<\/td>\n<td>prime256v1<\/td>\n<td>128<\/td>\n<td>ECDH + ECDSA, TLS, JWT ES256<\/td>\n<td>NIST FIPS 186-5<\/td>\n<td>Nativo<\/td>\n<\/tr>\n<tr>\n<td>P-384<\/td>\n<td>secp384r1<\/td>\n<td>192<\/td>\n<td>ECDH + ECDSA, alta sicurezza<\/td>\n<td>NIST FIPS 186-5<\/td>\n<td>Nativo<\/td>\n<\/tr>\n<tr>\n<td>P-521<\/td>\n<td>secp521r1<\/td>\n<td>256<\/td>\n<td>ECDH + ECDSA, sicurezza massima<\/td>\n<td>NIST FIPS 186-5<\/td>\n<td>Nativo<\/td>\n<\/tr>\n<tr>\n<td>secp256k1<\/td>\n<td>secp256k1<\/td>\n<td>128<\/td>\n<td>Bitcoin, Ethereum<\/td>\n<td>SEC 2<\/td>\n<td>Nativo<\/td>\n<\/tr>\n<tr>\n<td>Ed25519<\/td>\n<td>ed25519<\/td>\n<td>128<\/td>\n<td>Firma EdDSA deterministica, SSH<\/td>\n<td>RFC 8032<\/td>\n<td>Nativo (v15+)<\/td>\n<\/tr>\n<tr>\n<td>X25519<\/td>\n<td>x25519<\/td>\n<td>128<\/td>\n<td>Solo ECDH, TLS 1.3<\/td>\n<td>RFC 7748<\/td>\n<td>Nativo (v17+)<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Per la maggior parte delle applicazioni Node.js <strong>P-256<\/strong> \u00e8 la scelta di default: \u00e8 supportata da tutti i browser, dai provider TLS e dagli standard JWT. Se hai requisiti di sicurezza elevata (conformit\u00e0 FIPS a 192+ bit), usa P-384. Per le firme SSH prefer Ed25519: la generazione del nonce \u00e8 deterministica e non soffre dei problemi di casualit\u00e0 che affliggono ECDSA.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-1-generare-una-coppia-di-chiavi-ec-p-256\">Step 1: Generare una coppia di chiavi EC P-256<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Il modulo <code>node:crypto<\/code> espone <code>generateKeyPairSync<\/code> e il suo equivalente asincrono <code>generateKeyPair<\/code>. Il parametro <code>namedCurve<\/code> accetta il nome OpenSSL della curva. Per P-256 il nome \u00e8 <code>'prime256v1'<\/code> (alias: <code>'secp256r1'<\/code>).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">\/\/ step1-keygen.js\nconst { generateKeyPairSync, createHash } = require('node:crypto');\n\nconst { publicKey, privateKey } = generateKeyPairSync('ec', {\n  namedCurve: 'prime256v1',         \/\/ P-256, 128-bit di sicurezza\n  publicKeyEncoding:  { type: 'spki',  format: 'pem' },\n  privateKeyEncoding: { type: 'pkcs8', format: 'pem' },\n});\n\nconsole.log('=== CHIAVE PUBBLICA (SPKI PEM) ===');\nconsole.log(publicKey);\n\nconsole.log('=== CHIAVE PRIVATA (PKCS8 PEM) ===');\nconsole.log(privateKey);\n\n\/\/ Fingerprint SHA-256 della chiave pubblica\nconst fp = createHash('sha256').update(publicKey).digest('hex');\nconsole.log('Fingerprint:', fp.match(\/.{2}\/g).join(':'));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Output atteso:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>=== CHIAVE PUBBLICA (SPKI PEM) ===\n-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEk3...\n-----END PUBLIC KEY-----\n\n=== CHIAVE PRIVATA (PKCS8 PEM) ===\n-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0w...\n-----END PRIVATE KEY-----\n\nFingerprint: 3a:9f:c1:04:77:b2:...\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Perch\u00e9 SPKI e PKCS8?<\/strong> Il formato <code>spki<\/code> (SubjectPublicKeyInfo) \u00e8 lo standard X.509 per le chiavi pubbliche, compatibile con OpenSSL, Java e i browser. Il formato <code>pkcs8<\/code> \u00e8 la rappresentazione standard per le chiavi private EC e RSA. Se hai bisogno del formato raw per protocolli personalizzati, usa <code>format: 'jwk'<\/code> per ottenere un JSON Web Key direttamente.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-2-salvare-e-ricaricare-le-chiavi-da-disco\">Step 2: Salvare e ricaricare le chiavi da disco<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In produzione le chiavi devono essere persistenti. Questo step mostra come scrivere le chiavi PEM su file, proteggere la chiave privata con una passphrase AES-256-CBC, e ricaricarle al successivo avvio del server.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">\/\/ step2-keys-io.js\nconst { generateKeyPairSync, createPrivateKey, createPublicKey } = require('node:crypto');\nconst { writeFileSync, readFileSync, mkdirSync } = require('node:fs');\n\nmkdirSync('.\/keys', { recursive: true });\n\n\/\/ Genera con passphrase: la chiave privata \u00e8 cifrata a riposo\nconst { publicKey, privateKey } = generateKeyPairSync('ec', {\n  namedCurve: 'prime256v1',\n  publicKeyEncoding:  { type: 'spki',  format: 'pem' },\n  privateKeyEncoding: {\n    type: 'pkcs8',\n    format: 'pem',\n    cipher: 'aes-256-cbc',          \/\/ cifra la chiave privata su disco\n    passphrase: process.env.KEY_PASS || 'cambiamiInProduzione!',\n  },\n});\n\nwriteFileSync('.\/keys\/ec-public.pem',  publicKey,  { mode: 0o644 });\nwriteFileSync('.\/keys\/ec-private.pem', privateKey, { mode: 0o600 });\nconsole.log('Chiavi salvate in .\/keys\/');\n\n\/\/ Ricarica le chiavi\nconst rawPriv = readFileSync('.\/keys\/ec-private.pem', 'utf8');\nconst rawPub  = readFileSync('.\/keys\/ec-public.pem',  'utf8');\n\nconst loadedPriv = createPrivateKey({\n  key: rawPriv,\n  passphrase: process.env.KEY_PASS || 'cambiamiInProduzione!',\n});\nconst loadedPub = createPublicKey(rawPub);\n\nconsole.log('Tipo chiave privata:', loadedPriv.asymmetricKeyType); \/\/ ec\nconsole.log('Tipo chiave pubblica:', loadedPub.asymmetricKeyType); \/\/ ec<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Le autorizzazioni <code>0o600<\/code> impediscono la lettura della chiave privata da altri utenti Unix. In un container Docker, monta la chiave come secret (ad esempio con Docker Swarm Secrets o Kubernetes Secrets) anzich\u00e9 integrarla nell&#8217;immagine.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-3-scambio-di-chiavi-ecdh\">Step 3: Scambio di chiavi ECDH<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">ECDH (Elliptic Curve Diffie-Hellman) permette a due parti di concordare un segreto condiviso su un canale non cifrato, senza mai trasmettere la chiave privata. Ogni parte genera un key pair effimero, scambia la chiave pubblica, e calcola lo stesso segreto moltiplicando la propria chiave privata per la chiave pubblica dell&#8217;altro.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">\/\/ step3-ecdh.js\nconst { createECDH } = require('node:crypto');\n\n\/\/ Simulazione: Alice e Bob su macchine diverse\nconst alice = createECDH('prime256v1');\nconst bob   = createECDH('prime256v1');\n\n\/\/ Ogni parte genera il proprio key pair effimero\nalice.generateKeys();\nbob.generateKeys();\n\n\/\/ Scambio pubblico dei punti (non segreto, pu\u00f2 avvenire in chiaro)\nconst alicePubKey = alice.getPublicKey();   \/\/ Buffer (65 byte, non compresso)\nconst bobPubKey   = bob.getPublicKey();\n\n\/\/ Calcolo segreto condiviso (lato Alice)\nconst aliceSecret = alice.computeSecret(bobPubKey);\n\n\/\/ Calcolo segreto condiviso (lato Bob)\nconst bobSecret = bob.computeSecret(alicePubKey);\n\n\/\/ I segreti DEVONO essere identici\nconsole.log('Segreti identici:', aliceSecret.equals(bobSecret)); \/\/ true\nconsole.log('Lunghezza segreto:', aliceSecret.length, 'byte');   \/\/ 32 byte per P-256\nconsole.log('Segreto (hex):', aliceSecret.toString('hex'));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il segreto ECDH \u00e8 il punto X della moltiplicazione scalare. Per P-256 \u00e8 sempre esattamente 32 byte. <strong>Non usare questo segreto direttamente come chiave AES<\/strong>: il segreto ECDH ha una distribuzione non uniforme e potrebbe avere bias crittografici. Devi sempre passarlo attraverso una funzione di derivazione chiavi (KDF).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-4-derivare-una-chiave-aes-con-hkdf\">Step 4: Derivare una chiave AES con HKDF<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">HKDF (HMAC-based Key Derivation Function, <a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc5869\" rel=\"noopener noreferrer\" target=\"_blank\">RFC 5869<\/a>) trasforma il segreto ECDH in una o pi\u00f9 chiavi crittograficamente uniformi. Node.js v15+ include <code>hkdfSync<\/code> e la variante asincrona <code>hkdf<\/code> nel modulo <code>node:crypto<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">\/\/ step4-hkdf.js\nconst { createECDH, hkdfSync } = require('node:crypto');\n\nconst alice = createECDH('prime256v1');\nconst bob   = createECDH('prime256v1');\nalice.generateKeys();\nbob.generateKeys();\nconst sharedSecret = alice.computeSecret(bob.getPublicKey());\n\n\/\/ HKDF: hash, ikm (input key material), salt, info, lunghezza output\nconst aesKeyBuffer = hkdfSync(\n  'sha256',            \/\/ algoritmo hash\n  sharedSecret,        \/\/ IKM: segreto ECDH grezzo\n  '',                  \/\/ salt: stringa vuota (o un nonce pubblico concordato)\n  'aes-key-v1',        \/\/ info: stringa di contesto per separare usi diversi\n  32                   \/\/ lunghezza output: 32 byte = 256 bit per AES-256\n);\n\nconst aesKey = Buffer.from(aesKeyBuffer);\nconsole.log('Chiave AES-256 derivata:', aesKey.toString('hex')); \/\/ 64 char hex\n\n\/\/ Per derivare anche una chiave MAC separata:\nconst macKeyBuffer = hkdfSync('sha256', sharedSecret, '', 'mac-key-v1', 32);\nconst macKey = Buffer.from(macKeyBuffer);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il parametro <code>info<\/code> lega la chiave derivata al contesto specifico, impedendo il riutilizzo della stessa chiave in contesti diversi (ad esempio, separare la chiave di cifratura da quella MAC). Cambia il valore di <code>info<\/code> per ogni scopo: <code>'encrypt-v1'<\/code>, <code>'mac-v1'<\/code>, <code>'auth-v1'<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-5-6-cifrare-e-decifrare-con-aes-256-gcm\">Step 5-6: Cifrare e decifrare con AES-256-GCM<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">AES-256-GCM \u00e8 una modalit\u00e0 di cifratura autenticata (AEAD): garantisce sia la riservatezza (nessuno legge il messaggio) sia l&#8217;integrit\u00e0 (nessuno modifica il messaggio senza che te ne accorga). \u00c8 la combinazione consigliata con ECDH per implementare la crittografia end-to-end.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">\/\/ step5-6-aes-gcm.js\nconst { createCipheriv, createDecipheriv, randomBytes, hkdfSync, createECDH } = require('node:crypto');\n\n\/\/ Setup: chiave AES derivata da ECDH (vedi Step 3-4)\nconst alice = createECDH('prime256v1');\nconst bob   = createECDH('prime256v1');\nalice.generateKeys();\nbob.generateKeys();\nconst sharedSecret = alice.computeSecret(bob.getPublicKey());\nconst aesKey = Buffer.from(hkdfSync('sha256', sharedSecret, '', 'encrypt-v1', 32));\n\n\/\/ --- CIFRATURA ---\nfunction cifra(chiave, testo) {\n  const iv = randomBytes(12);  \/\/ 96 bit: la dimensione corretta per AES-GCM\n  const cipher = createCipheriv('aes-256-gcm', chiave, iv);\n\n  const datiCifrati = Buffer.concat([\n    cipher.update(testo, 'utf8'),\n    cipher.final(),\n  ]);\n  const authTag = cipher.getAuthTag(); \/\/ 16 byte: il tag di autenticazione\n\n  \/\/ Combina iv + authTag + testo cifrato per la trasmissione\n  return Buffer.concat([iv, authTag, datiCifrati]).toString('base64');\n}\n\n\/\/ --- DECIFRATURA ---\nfunction decifra(chiave, payloadBase64) {\n  const payload = Buffer.from(payloadBase64, 'base64');\n\n  const iv         = payload.subarray(0, 12);\n  const authTag    = payload.subarray(12, 28);\n  const cifrato    = payload.subarray(28);\n\n  const decipher = createDecipheriv('aes-256-gcm', chiave, iv);\n  decipher.setAuthTag(authTag);\n\n  return Buffer.concat([\n    decipher.update(cifrato),\n    decipher.final(),     \/\/ lancia un errore se il tag non corrisponde\n  ]).toString('utf8');\n}\n\nconst messaggio   = 'Messaggio segreto: deploy alle 03:00';\nconst cifrato     = cifra(aesKey, messaggio);\nconst decifrato   = decifra(aesKey, cifrato);\n\nconsole.log('Cifrato (base64):', cifrato);\nconsole.log('Decifrato:', decifrato);\nconsole.log('Uguale:', messaggio === decifrato); \/\/ true<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il campo <code>authTag<\/code> (16 byte) \u00e8 la firma crittografica del messaggio cifrato. Se un attaccante modifica anche un solo bit del testo cifrato, <code>decipher.final()<\/code> lancia un&#8217;eccezione <code>ERR_CRYPTO_GCM_AUTH_FAILED<\/code>, che devi gestire lato server per respingere il messaggio corrotto.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-7-8-firma-e-verifica-ecdsa\">Step 7-8: Firma e verifica ECDSA<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">ECDSA permette di provare la provenienza di un dato: solo chi possiede la chiave privata pu\u00f2 generare una firma valida, ma chiunque pu\u00f2 verificarla con la chiave pubblica. Il modulo <code>node:crypto<\/code> espone <code>sign<\/code> e <code>verify<\/code> che accettano direttamente oggetti chiave PEM o KeyObject.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">\/\/ step7-8-ecdsa.js\nconst { generateKeyPairSync, sign, verify } = require('node:crypto');\n\n\/\/ Genera chiavi per l'identit\u00e0 (non effimere: queste restano a lungo termine)\nconst { publicKey, privateKey } = generateKeyPairSync('ec', {\n  namedCurve: 'prime256v1',\n  publicKeyEncoding:  { type: 'spki',  format: 'pem' },\n  privateKeyEncoding: { type: 'pkcs8', format: 'pem' },\n});\n\nconst documento = Buffer.from(JSON.stringify({\n  messaggio: 'Trasferisci 1000 EUR a Mario Rossi',\n  timestamp: Date.now(),\n}));\n\n\/\/ Step 7 - FIRMA\n\/\/ sign(hash, data, privateKey): restituisce un Buffer DER con la firma\nconst firma = sign('sha256', documento, privateKey);\nconsole.log('Firma (hex):', firma.toString('hex'));\nconsole.log('Lunghezza firma:', firma.length, 'byte'); \/\/ DER: variabile, ~70-72 byte per P-256\n\n\/\/ Step 8 - VERIFICA\nconst valida = verify('sha256', documento, publicKey, firma);\nconsole.log('Firma valida:', valida); \/\/ true\n\n\/\/ Tentativo di verifica con dato alterato\nconst documentoAlterato = Buffer.from('dato diverso');\nconst firmaInvalida = verify('sha256', documentoAlterato, publicKey, firma);\nconsole.log('Firma su dato alterato:', firmaInvalida); \/\/ false<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La firma ECDSA \u00e8 codificata in formato DER (Distinguished Encoding Rules) e ha una lunghezza variabile: per P-256 \u00e8 in genere tra 70 e 72 byte. Se hai bisogno del formato IEEE P1363 (firma a lunghezza fissa, usata nei JWT ES256), usa la libreria <code>node-jose<\/code> o converti manualmente i componenti <code>r<\/code> e <code>s<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"confronto-ecdsa-vs-eddsa\">Confronto ECDSA vs EdDSA<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Caratteristica<\/th>\n<th>ECDSA (P-256)<\/th>\n<th>EdDSA (Ed25519)<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Sicurezza (bit)<\/td>\n<td>128<\/td>\n<td>128<\/td>\n<\/tr>\n<tr>\n<td>Nonce<\/td>\n<td>Casuale (critico)<\/td>\n<td>Deterministico (RFC 8032)<\/td>\n<\/tr>\n<tr>\n<td>Rischio nonce reuse<\/td>\n<td>Critico (espone la privkey)<\/td>\n<td>Nessuno (nonce calcolato)<\/td>\n<\/tr>\n<tr>\n<td>Lunghezza firma<\/td>\n<td>70-72 byte DER<\/td>\n<td>64 byte fissi<\/td>\n<\/tr>\n<tr>\n<td>Standard<\/td>\n<td>FIPS 186-5<\/td>\n<td>RFC 8032<\/td>\n<\/tr>\n<tr>\n<td>Supporto JWT<\/td>\n<td>ES256, ES384, ES512<\/td>\n<td>EdDSA<\/td>\n<\/tr>\n<tr>\n<td>Supporto SSH<\/td>\n<td>ecdsa-sha2-nistp256<\/td>\n<td>ssh-ed25519 (preferito)<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Per nuovi sistemi dove non esistono vincoli di interoperabilit\u00e0 con ECDSA, preferisci Ed25519: il nonce deterministico elimina il rischio di compromissione accidentale della chiave privata dovuta a un generatore di numeri casuali difettoso.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-9-calcolare-il-fingerprint-di-una-chiave-pubblica\">Step 9: Calcolare il fingerprint di una chiave pubblica<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Il fingerprint \u00e8 un hash breve della chiave pubblica che ne permette l&#8217;identificazione e la verifica out-of-band (ad esempio per confronto telefonico o via QR code). \u00c8 la tecnica usata da SSH, Signal e GPG per il modello Trust On First Use (TOFU).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">\/\/ step9-fingerprint.js\nconst { generateKeyPairSync, createHash } = require('node:crypto');\n\nconst { publicKey } = generateKeyPairSync('ec', {\n  namedCurve: 'prime256v1',\n  publicKeyEncoding: { type: 'spki', format: 'pem' },\n  privateKeyEncoding: { type: 'pkcs8', format: 'pem' },\n});\n\n\/\/ Fingerprint SHA-256 della chiave pubblica DER\nfunction fingerprint(pubKeyPem) {\n  const keyObj = require('node:crypto').createPublicKey(pubKeyPem);\n  const der = keyObj.export({ type: 'spki', format: 'der' });\n  const hash = createHash('sha256').update(der).digest('hex');\n  return hash.match(\/.{2}\/g).join(':');\n}\n\nconst fp = fingerprint(publicKey);\nconsole.log('Fingerprint SHA-256:');\nconsole.log(fp);\n\/\/ Esempio: 3a:9f:c1:04:77:b2:e9:15:d4:fa:88:23:ab:cd:ef:01:...\n\n\/\/ Verifica manuale: confronta questo valore con quello del peer\n\/\/ tramite un canale sicuro (telefono, QR code, Signal)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Usare il DER (formato binario) anzich\u00e9 il PEM (stringa base64 con intestazioni) garantisce che il fingerprint non cambi se il PEM viene riformattato con interruzioni di riga diverse. Il valore esadecimale con separatori <code>:<\/code> \u00e8 la rappresentazione standard adottata da OpenSSH.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-10-11-gestione-sicura-delle-chiavi-in-produzione\">Step 10-11: Gestione sicura delle chiavi in produzione<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"dove-non-conservare-le-chiavi-private\">Dove non conservare le chiavi private<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Mai in variabili d&#8217;ambiente non cifrate<\/strong>: i file <code>.env<\/code> in chiaro finiscono spesso nei repository Git.<\/li>\n<li><strong>Mai nel codice sorgente<\/strong>: ogni commit storico espone la chiave.<\/li>\n<li><strong>Mai nei log di sistema<\/strong>: aggiungi sempre filtri per evitare che le chiavi finiscano in stdout.<\/li>\n<li><strong>Mai in database senza cifratura a riposo<\/strong>: usa la cifratura con passphrase AES-256-CBC come mostrato nello Step 2.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"strategie-consigliate-per-node-js-in-produzione\">Strategie consigliate per Node.js in produzione<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Variabili d&#8217;ambiente da vault<\/strong>: usa HashiCorp Vault, AWS Secrets Manager, o Azure Key Vault per iniettare la passphrase al boot del container.<\/li>\n<li><strong>File system cifrati<\/strong>: monta le chiavi su volumi LUKS (vedi la guida <a href=\"\/it\/luks-cifrare-disco-ubuntu\/\">LUKS su Ubuntu<\/a>) o con tmpfs in-memory.<\/li>\n<li><strong>Rotazione periodica<\/strong>: ruota le chiavi ECDH ogni sessione (chiavi effimere) e le chiavi ECDSA di identit\u00e0 ogni 12-24 mesi.<\/li>\n<li><strong>Hardware Security Module (HSM)<\/strong>: per applicazioni ad alta criticit\u00e0, l&#8217;operazione di firma avviene dentro l&#8217;HSM e la chiave privata non lascia mai il dispositivo.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-12-progetto-completo-sistema-di-messaggistica-sicura\">Step 12: Progetto completo &#8211; sistema di messaggistica sicura<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Questo step mette insieme tutti i componenti precedenti in un modulo autonomo <code>SecureMessenger<\/code>: ogni istanza ha una coppia di chiavi di identit\u00e0 ECDSA (persistente) e scambia messaggi usando ECDH + HKDF + AES-256-GCM con un partner.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">\/\/ secure-messenger.js\nconst {\n  generateKeyPairSync, createECDH, hkdfSync,\n  createCipheriv, createDecipheriv,\n  sign, verify, createHash, randomBytes,\n} = require('node:crypto');\n\nclass SecureMessenger {\n  constructor(nome) {\n    this.nome = nome;\n\n    \/\/ Chiave di identit\u00e0 ECDSA (firma, lunga durata)\n    const { publicKey, privateKey } = generateKeyPairSync('ec', {\n      namedCurve: 'prime256v1',\n      publicKeyEncoding:  { type: 'spki',  format: 'pem' },\n      privateKeyEncoding: { type: 'pkcs8', format: 'pem' },\n    });\n    this.identPub  = publicKey;\n    this.identPriv = privateKey;\n\n    \/\/ Coppia ECDH effimera per questa sessione\n    this.ecdh = createECDH('prime256v1');\n    this.ecdh.generateKeys();\n    this.sessionKey = null;\n  }\n\n  \/\/ Restituisce la chiave ECDH pubblica da inviare al partner\n  getSessionPublicKey() {\n    return this.ecdh.getPublicKey('base64');\n  }\n\n  \/\/ Stabilisce la chiave di sessione dal punto ECDH del partner\n  stabilisciSessione(peerEcdhPubBase64) {\n    const peerPub    = Buffer.from(peerEcdhPubBase64, 'base64');\n    const sharedSec  = this.ecdh.computeSecret(peerPub);\n    this.sessionKey  = Buffer.from(\n      hkdfSync('sha256', sharedSec, '', `${this.nome}-session-v1`, 32)\n    );\n    console.log(`[${this.nome}] Sessione stabilita. Chiave: ${this.sessionKey.toString('hex').slice(0, 16)}...`);\n  }\n\n  \/\/ Cifra e firma un messaggio\n  invia(testo) {\n    if (!this.sessionKey) throw new Error('Sessione non stabilita');\n\n    const iv      = randomBytes(12);\n    const cipher  = createCipheriv('aes-256-gcm', this.sessionKey, iv);\n    const cifrato = Buffer.concat([cipher.update(testo, 'utf8'), cipher.final()]);\n    const authTag = cipher.getAuthTag();\n\n    \/\/ Firma il contenuto cifrato con la chiave di identit\u00e0 ECDSA\n    const daFirmare = Buffer.concat([iv, authTag, cifrato]);\n    const firma = sign('sha256', daFirmare, this.identPriv);\n\n    return {\n      mittente: this.nome,\n      pubIdent: this.identPub,\n      iv:       iv.toString('base64'),\n      authTag:  authTag.toString('base64'),\n      cifrato:  cifrato.toString('base64'),\n      firma:    firma.toString('base64'),\n    };\n  }\n\n  \/\/ Verifica la firma e decifra il messaggio\n  ricevi(pacchetto) {\n    const iv      = Buffer.from(pacchetto.iv,      'base64');\n    const authTag = Buffer.from(pacchetto.authTag, 'base64');\n    const cifrato = Buffer.from(pacchetto.cifrato, 'base64');\n    const firma   = Buffer.from(pacchetto.firma,   'base64');\n\n    \/\/ 1. Verifica firma ECDSA\n    const daVerificare = Buffer.concat([iv, authTag, cifrato]);\n    const firmaOk = verify('sha256', daVerificare, pacchetto.pubIdent, firma);\n    if (!firmaOk) throw new Error('FIRMA NON VALIDA: messaggio rifiutato');\n\n    \/\/ 2. Decifra con AES-256-GCM\n    const decipher = createDecipheriv('aes-256-gcm', this.sessionKey, iv);\n    decipher.setAuthTag(authTag);\n    const testo = Buffer.concat([decipher.update(cifrato), decipher.final()]).toString('utf8');\n\n    return { mittente: pacchetto.mittente, testo };\n  }\n}\n\n\/\/ --- Demo ---\nconst alice = new SecureMessenger('Alice');\nconst bob   = new SecureMessenger('Bob');\n\n\/\/ Scambio chiavi ECDH (in un'app reale: via WebSocket o API)\nalice.stabilisciSessione(bob.getSessionPublicKey());\nbob.stabilisciSessione(alice.getSessionPublicKey());\n\n\/\/ Alice invia un messaggio cifrato e firmato a Bob\nconst pacchetto = alice.invia('Ciao Bob! Questo messaggio \u00e8 E2E cifrato e firmato.');\nconsole.log('\\nPacchetto cifrato:', JSON.stringify({\n  ...pacchetto,\n  cifrato: pacchetto.cifrato.slice(0, 20) + '...',\n}, null, 2));\n\n\/\/ Bob riceve, verifica la firma e decifra\nconst ricevuto = bob.ricevi(pacchetto);\nconsole.log('\\nRicevuto da:', ricevuto.mittente);\nconsole.log('Testo:', ricevuto.testo);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Output atteso:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>[Alice] Sessione stabilita. Chiave: 3f8a2c1b9e4d7f05...\n[Bob]   Sessione stabilita. Chiave: 3f8a2c1b9e4d7f05...\n\nRicevuto da: Alice\nTesto: Ciao Bob! Questo messaggio \u00e8 E2E cifrato e firmato.<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"5-errori-comuni-nellimplementazione-ecc\">5 errori comuni nell&#8217;implementazione ECC<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"errore-1-riutilizzo-del-nonce-ecdsa\">Errore 1: Riutilizzo del nonce ECDSA<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">ECDSA richiede un nonce <code>k<\/code> univoco per ogni firma. Se lo stesso <code>k<\/code> viene usato due volte con la stessa chiave privata, la chiave privata \u00e8 matematicamente recuperabile da chiunque osservi le due firme. PlayStation 3 (2010) e alcune implementazioni Bitcoin antiche sono cadute in questa trappola. In Node.js il nonce \u00e8 generato internamente dall&#8217;OpenSSL sottostante usando <code>RAND_bytes<\/code>, ma se usi librerie JavaScript pure verificate che usino RFC 6979 (nonce deterministico).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"errore-2-usare-il-segreto-ecdh-grezzo-come-chiave-aes\">Errore 2: Usare il segreto ECDH grezzo come chiave AES<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Il segreto ECDH \u00e8 la coordinata X del punto risultante. Ha una distribuzione non uniforme e potrebbe avere bit fissi. Passarlo direttamente come chiave AES compromette la sicurezza. <strong>Sempre<\/strong> usa HKDF (o almeno SHA-256 del segreto) per derivare la chiave simmetrica.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"errore-3-riutilizzo-delliv-in-aes-gcm\">Errore 3: Riutilizzo dell&#8217;IV in AES-GCM<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">AES-GCM \u00e8 catastroficamente rotto se lo stesso IV viene usato due volte con la stessa chiave. Un attaccante pu\u00f2 recuperare la chiave di autenticazione, falsificare messaggi e, in alcuni scenari, decifrare i dati. Genera sempre l&#8217;IV con <code>randomBytes(12)<\/code> per ogni messaggio. Con 12 byte casuali, la probabilit\u00e0 di collisione \u00e8 trascurabile fino a circa 2^32 messaggi.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"errore-4-non-validare-la-chiave-pubblica-del-peer\">Errore 4: Non validare la chiave pubblica del peer<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Un attaccante pu\u00f2 inviare una chiave pubblica non valida (punto non sulla curva) per forzare un segreto ECDH prevedibile. Node.js valida automaticamente il punto durante <code>computeSecret<\/code>, ma se importi chiavi da formati personalizzati verifica sempre che il punto sia sulla curva. Usa <code>createPublicKey<\/code> con gestione delle eccezioni:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">try {\n  const peerKey = createPublicKey({ key: datiPeer, format: 'der', type: 'spki' });\n  \/\/ Chiave valida, procedi\n} catch (err) {\n  throw new Error('Chiave pubblica non valida: attacco possibile');\n}<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"errore-5-mischiare-curve-diverse-nello-stesso-protocollo\">Errore 5: Mischiare curve diverse nello stesso protocollo<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Usare P-256 per l&#8217;identit\u00e0 e secp256k1 per ECDH, o firmare con P-384 e verificare con P-256, causa errori silenziosi o vulnerabilit\u00e0 di downgrade. Stabilisci una politica di curva per l&#8217;intero sistema e documentala: usa solo P-256 o solo X25519 per ECDH, solo Ed25519 o solo ECDSA P-256 per le firme.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"8-problemi-comuni-e-soluzioni\">8 problemi comuni e soluzioni<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Problema<\/th>\n<th>Messaggio di errore<\/th>\n<th>Causa<\/th>\n<th>Soluzione<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>ECDH produce segreti diversi<\/td>\n<td><code>segreti non identici<\/code><\/td>\n<td>Versioni diverse di Node.js usano formati diversi per i punti pubblici<\/td>\n<td>Specifica esplicitamente <code>getPublicKey('uncompressed')<\/code> su entrambe le parti<\/td>\n<\/tr>\n<tr>\n<td>hkdfSync non disponibile<\/td>\n<td><code>TypeError: hkdfSync is not a function<\/code><\/td>\n<td>Node.js &lt; v15.0.0<\/td>\n<td>Aggiorna a Node.js v20+ o usa la libreria <code>hkdf<\/code> da npm<\/td>\n<\/tr>\n<tr>\n<td>Firma non valida dopo serializzazione<\/td>\n<td><code>false da verify()<\/code><\/td>\n<td>Il dato firmato \u00e8 stato convertito in stringa UTF-8 perdendo byte<\/td>\n<td>Firma sempre su <code>Buffer<\/code>, non su stringhe: <code>Buffer.from(dato)<\/code><\/td>\n<\/tr>\n<tr>\n<td>ERR_CRYPTO_GCM_AUTH_FAILED<\/td>\n<td><code>Error: Unsupported state or unable to authenticate data<\/code><\/td>\n<td>IV o authTag errati, o dato cifrato alterato<\/td>\n<td>Verifica l&#8217;ordine di concatenazione IV+authTag+cifrato e che il tag abbia esattamente 16 byte<\/td>\n<\/tr>\n<tr>\n<td>Chiave PEM rifiutata<\/td>\n<td><code>Error: error:09091064:PEM routines<\/code><\/td>\n<td>Intestazioni PEM mancanti o file corrotto<\/td>\n<td>Assicurati che il PEM inizi con <code>-----BEGIN<\/code> senza spazi iniziali<\/td>\n<\/tr>\n<tr>\n<td>Curva non supportata<\/td>\n<td><code>Error: curve not supported<\/code><\/td>\n<td>OpenSSL sulla versione di Node non include la curva richiesta<\/td>\n<td>Verifica con <code>crypto.getCurves()<\/code> le curve disponibili nella tua build<\/td>\n<\/tr>\n<tr>\n<td>Chiave privata con passphrase sbagliata<\/td>\n<td><code>Error: EVP_DecryptFinal_ex: bad decrypt<\/code><\/td>\n<td>Passphrase errata o encoding diverso<\/td>\n<td>Passa la passphrase come <code>Buffer<\/code> se contiene caratteri non ASCII<\/td>\n<\/tr>\n<tr>\n<td>Firma ECDSA DER troppo lunga per JWT<\/td>\n<td>JWT non valido lato consumer<\/td>\n<td>JWT ES256 richiede formato P1363 (64 byte fissi), non DER<\/td>\n<td>Converti con <code>derToJose<\/code> dalla libreria <code>ecdsa-sig-formatter<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"tecniche-avanzate-ed25519-e-x25519\">Tecniche avanzate: Ed25519 e X25519<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Le curve Curve25519 e le relative varianti Ed25519 (firma) e X25519 (key agreement) sono progettate con criteri di sicurezza pi\u00f9 rigorosi delle curve NIST, sono resistenti agli attacchi timing side-channel e producono chiavi di dimensioni compatte. Node.js le supporta nativamente dalla v15 (Ed25519) e v17 (X25519).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">\/\/ Ed25519: firma deterministica (nessun rischio nonce reuse)\nconst { generateKeyPairSync, sign, verify } = require('node:crypto');\n\nconst { publicKey, privateKey } = generateKeyPairSync('ed25519', {\n  publicKeyEncoding:  { type: 'spki',  format: 'pem' },\n  privateKeyEncoding: { type: 'pkcs8', format: 'pem' },\n});\n\nconst dato = Buffer.from('dati da firmare con Ed25519');\nconst firma = sign(null, dato, privateKey);        \/\/ null: Ed25519 usa l'hash interno\nconst ok    = verify(null, dato, publicKey, firma);\nconsole.log('Firma Ed25519 valida:', ok);\nconsole.log('Lunghezza firma:', firma.length, 'byte'); \/\/ Sempre 64 byte\n\n\/\/ X25519: key agreement (usato da TLS 1.3 e Signal)\nconst { diffieHellman, generateKeyPairSync: genKP } = require('node:crypto');\n\nconst aliceKP = genKP('x25519', {\n  publicKeyEncoding:  { type: 'spki',  format: 'pem' },\n  privateKeyEncoding: { type: 'pkcs8', format: 'pem' },\n});\nconst bobKP = genKP('x25519', {\n  publicKeyEncoding:  { type: 'spki',  format: 'pem' },\n  privateKeyEncoding: { type: 'pkcs8', format: 'pem' },\n});\n\nconst aliceSegreto = diffieHellman({\n  privateKey: require('node:crypto').createPrivateKey(aliceKP.privateKey),\n  publicKey:  require('node:crypto').createPublicKey(bobKP.publicKey),\n});\nconsole.log('Segreto X25519 (hex):', aliceSegreto.toString('hex').slice(0, 32), '...');<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">TLS 1.3 usa X25519 come curva di default per il key agreement (insieme a X448 per sicurezza elevata). Se stai costruendo un server HTTPS con Node.js, il modulo <code>node:tls<\/code> sceglie automaticamente X25519 o P-256 in base alla negoziazione con il client, vedi la guida <a href=\"\/it\/certbot-lets-encrypt-https\/\">Let&#8217;s Encrypt e Certbot: HTTPS Gratis in 10 Step<\/a> per la configurazione TLS del server.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"confronto-ecc-vs-rsa-in-node-js\">Confronto ECC vs RSA in Node.js<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Metrica<\/th>\n<th>ECC P-256<\/th>\n<th>RSA-2048<\/th>\n<th>RSA-3072<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Sicurezza equivalente (bit)<\/td>\n<td>128<\/td>\n<td>112<\/td>\n<td>128<\/td>\n<\/tr>\n<tr>\n<td>Dimensione chiave privata<\/td>\n<td>32 byte<\/td>\n<td>256 byte<\/td>\n<td>384 byte<\/td>\n<\/tr>\n<tr>\n<td>Dimensione chiave pubblica<\/td>\n<td>65 byte<\/td>\n<td>256 byte<\/td>\n<td>384 byte<\/td>\n<\/tr>\n<tr>\n<td>Dimensione firma<\/td>\n<td>70-72 byte DER<\/td>\n<td>256 byte<\/td>\n<td>384 byte<\/td>\n<\/tr>\n<tr>\n<td>Velocit\u00e0 generazione chiavi<\/td>\n<td>Molto veloce<\/td>\n<td>Lenta<\/td>\n<td>Molto lenta<\/td>\n<\/tr>\n<tr>\n<td>Compatibilit\u00e0<\/td>\n<td>Alta (TLS, JWT, SSH)<\/td>\n<td>Massima (legacy)<\/td>\n<td>Alta<\/td>\n<\/tr>\n<tr>\n<td>Resistenza post-quantistica<\/td>\n<td>No (entrambi vulnerabili)<\/td>\n<td>No<\/td>\n<td>No<\/td>\n<\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">RSA-2048 \u00e8 classificato a 112 bit di sicurezza, inferiore a P-256 (128 bit), secondo NIST SP 800-186. Se devi interoperare con sistemi legacy che richiedono RSA, usa RSA-3072 per raggiungere lo stesso livello P-256. Per sistemi completamente nuovi senza vincoli legacy, ECC P-256 o Ed25519 sono sempre la scelta migliore.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"copertura-correlata\">Copertura correlata<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"articoli-correlati-su-shattered-io\">Articoli correlati su shattered.io<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/it\/crittografia-end-to-end-nodejs\/\">Crittografia End-to-End in Node.js: 12 Step<\/a> &#8211; Implementazione E2E con ECDH e AES-GCM<\/li>\n<li><a href=\"\/it\/autenticazione-jwt-nodejs\/\">Autenticazione JWT in Node.js: 12 Step<\/a> &#8211; JWT con algoritmo ES256 (ECDSA P-256)<\/li>\n<li><a href=\"\/it\/openssl-certificati-chiavi\/\">OpenSSL 3.5 LTS: Chiavi e Certificati in 12 Step<\/a> &#8211; Gestione certificati X.509 con OpenSSL<\/li>\n<li><a href=\"\/it\/gpg-cifrare-firmare-file\/\">GPG: Cifrare e Firmare File in 12 Step<\/a> &#8211; Crittografia asimmetrica con GPG\/PGP<\/li>\n<li><a href=\"\/it\/certbot-lets-encrypt-https\/\">Let&#8217;s Encrypt e Certbot: HTTPS Gratis in 10 Step<\/a> &#8211; Certificati TLS con curve ECDSA<\/li>\n<li><a href=\"\/it\/hashing-password-bcrypt-nodejs\/\">Hashing Password con bcrypt in Node.js: 12 Step<\/a> &#8211; Sicurezza delle credenziali utente<\/li>\n<li><a href=\"\/crittografia\/\">Crittografia: guida alla categoria<\/a> &#8211; Tutti gli articoli sulla crittografia<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"domande-frequenti\">Domande frequenti<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"qual-e-la-differenza-tra-p-256-e-secp256k1\">Qual \u00e8 la differenza tra P-256 e secp256k1?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Entrambe sono curve a 256 bit con 128 bit di sicurezza, ma hanno parametri matematici diversi. P-256 (anche chiamata secp256r1 o prime256v1) \u00e8 la curva NIST standard usata in TLS, HTTPS e JWT. secp256k1 \u00e8 la curva usata da Bitcoin ed Ethereum: \u00e8 pi\u00f9 rapida per alcune operazioni, ma non \u00e8 approvata FIPS e non \u00e8 supportata dai provider TLS nei browser. Per applicazioni web usa P-256; per blockchain usa secp256k1.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"ecc-e-sicuro-contro-i-computer-quantistici\">ECC \u00e8 sicuro contro i computer quantistici?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No. L&#8217;algoritmo di Shor su un computer quantistico sufficientemente potente risolverebbe il problema ECDLP in tempo polinomiale, rendendo obsoleto ECC insieme a RSA. Il NIST ha standardizzato nel 2024 tre algoritmi post-quantistici (ML-KEM, ML-DSA, SLH-DSA) come successori. Per oggi ECC rimane sicuro contro i computer classici; inizia a pianificare la migrazione verso gli algoritmi post-quantistici per i sistemi con dati sensibili a lungo termine. Leggi la guida <a href=\"\/it\/post-quantum-cryptography-2026\/\" rel=\"noopener\">Crittografia Post-Quantistica<\/a> per i dettagli sulla transizione.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"devo-usare-ecdsa-o-eddsa-per-le-firme\">Devo usare ECDSA o EdDSA per le firme?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Per i sistemi nuovi preferisci Ed25519 (EdDSA): il nonce \u00e8 deterministico (RFC 6979 esteso), quindi non \u00e8 possibile compromettere la chiave privata per un difetto del generatore casuale. Le firme hanno lunghezza fissa (64 byte), semplificando il parsing. ECDSA P-256 \u00e8 necessario quando devi compatibilit\u00e0 con: JWT ES256, TLS con certificati ECDSA, o partner che richiedono FIPS 186-5.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"quanto-e-sicuro-aes-256-gcm-in-combinazione-con-ecdh\">Quanto \u00e8 sicuro AES-256-GCM in combinazione con ECDH?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">La combinazione ECDH-P256 + HKDF-SHA256 + AES-256-GCM \u00e8 lo schema raccomandato per la crittografia ibrida asimmetrica\/simmetrica. Offre 128 bit di sicurezza (limitata dalla curva P-256, non dall&#8217;AES che \u00e8 a 256 bit). Questo schema \u00e8 usato da TLS 1.3 (in varianti con X25519) e da Signal (con X3DH + Double Ratchet). La vulnerabilit\u00e0 principale non \u00e8 il cifrario, ma gli errori implementativi: nonce AES-GCM riutilizzato, segreto ECDH usato grezzo, IV prevedibile.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"posso-usare-la-stessa-coppia-di-chiavi-ec-per-ecdh-e-ecdsa\">Posso usare la stessa coppia di chiavi EC per ECDH e ECDSA?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Tecnicamente s\u00ec (con P-256), ma \u00e8 una cattiva pratica per due motivi: separa i rischi (se la chiave ECDH \u00e8 compromessa, le firme storiche rimangono valide) e rispetta il principio di singola responsabilit\u00e0 crittografica. La raccomandazione standard \u00e8 usare chiavi dedicate per ogni scopo: una coppia per ECDSA (identit\u00e0, firma) e coppie effimere distinte per ECDH (sessione, key agreement).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"come-verifico-quali-curve-supporta-la-mia-versione-di-node-js\">Come verifico quali curve supporta la mia versione di Node.js?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Usa <code>crypto.getCurves()<\/code> per ottenere la lista completa delle curve disponibili nell&#8217;OpenSSL collegato alla tua installazione Node.js:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"javascript\" class=\"language-javascript\">const crypto = require('node:crypto');\nconst curve = crypto.getCurves();\nconst principali = ['prime256v1', 'secp384r1', 'secp521r1', 'secp256k1'];\nprincipali.forEach(c => console.log(c, curve.includes(c) ? 'disponibile' : 'NON disponibile'));<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Se una curva non \u00e8 disponibile, aggiorna Node.js all&#8217;ultima versione LTS (v22 o v20) che include OpenSSL 3.x con tutte le curve NIST e Curve25519.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"quanto-e-diversa-lecc-dalla-crittografia-rsa-per-lapi-node-js\">Quanto \u00e8 diversa l&#8217;ECC dalla crittografia RSA per l&#8217;API Node.js?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">L&#8217;API \u00e8 quasi identica: entrambe usano <code>generateKeyPairSync<\/code>, <code>sign<\/code>, <code>verify<\/code>, <code>createCipheriv<\/code>. La differenza principale \u00e8 nel tipo (&#8216;ec&#8217; vs &#8216;rsa&#8217;) e nei parametri: ECC usa <code>namedCurve<\/code>, RSA usa <code>modulusLength<\/code>. Per il key agreement, RSA usa RSA-OAEP (cifratura della chiave simmetrica), ECC usa ECDH (accordo matematico). ECC \u00e8 generalmente preferito per nuove implementazioni per le chiavi pi\u00f9 piccole e le operazioni pi\u00f9 veloci.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>La crittografia a curva ellittica (ECC) permette di raggiungere 128 bit di sicurezza con una chiave da 256 bit, mentre RSA richiede 3.072 bit per lo stesso livello di protezione.\u2026<\/p>\n","protected":false},"author":8,"featured_media":194,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-193","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cryptography"],"_links":{"self":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts\/193","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/users\/8"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/comments?post=193"}],"version-history":[{"count":1,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts\/193\/revisions"}],"predecessor-version":[{"id":195,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts\/193\/revisions\/195"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/media\/194"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/media?parent=193"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/categories?post=193"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/tags?post=193"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}