{"id":59,"date":"2026-06-11T16:45:09","date_gmt":"2026-06-11T16:45:09","guid":{"rendered":"https:\/\/shattered.io\/it\/2026\/06\/11\/crittografia-end-to-end-nodejs\/"},"modified":"2026-06-11T16:45:09","modified_gmt":"2026-06-11T16:45:09","slug":"crittografia-end-to-end-nodejs","status":"publish","type":"post","link":"https:\/\/shattered.io\/it\/2026\/06\/11\/crittografia-end-to-end-nodejs\/","title":{"rendered":"Crittografia End-to-End in Node.js: 12 Step [2026]"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">La <strong>crittografia end-to-end<\/strong> non \u00e8 pi\u00f9 un argomento riservato ai team di Signal o WhatsApp. Nel 2026 qualsiasi sviluppatore che gestisce messaggi privati, documenti sanitari, dati finanziari o backup degli utenti deve sapere come implementarla. Questo tutorial ti guida passo dopo passo nella costruzione di un sistema di <strong>crittografia end-to-end<\/strong> funzionante in Node.js, usando <code>libsodium<\/code>, lo scambio di chiavi X25519 e la cifratura autenticata XChaCha20-Poly1305. Al termine avrai un progetto completo, testabile e pronto a essere adattato in produzione, con una sezione finale dedicata alla migrazione post-quantum.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Aggiornato all&#8217;11 giugno 2026. Tempo di completamento stimato: 60-90 minuti. Livello: intermedio (serve confidenza con JavaScript asincrono e con la riga di comando).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"cose-la-crittografia-end-to-end-e-perche-conta-nel-2026\">Cos&#8217;\u00e8 la crittografia end-to-end e perch\u00e9 conta nel 2026<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La <strong>crittografia end-to-end<\/strong> (in inglese E2EE, end-to-end encryption) garantisce che solo il mittente e il destinatario possano leggere il contenuto di un messaggio. Le chiavi di decifratura non lasciano mai i dispositivi degli utenti. Nemmeno il server che trasporta i dati, l&#8217;amministratore di sistema o un attaccante che compromette l&#8217;infrastruttura pu\u00f2 accedere al testo in chiaro. Questo \u00e8 il punto che distingue la <strong>crittografia end-to-end<\/strong> dalla classica cifratura in transito (TLS) o a riposo (cifratura del disco): in quei due casi il server vede comunque i dati in chiaro a un certo punto.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La differenza \u00e8 sostanziale per la conformit\u00e0 normativa. Il GDPR (Regolamento UE 2016\/679) considera la cifratura una misura tecnica adeguata ai sensi dell&#8217;articolo 32, e nel 2026 le autorit\u00e0 di controllo europee la citano regolarmente nelle istruttorie sui data breach. Quando i dati esfiltrati sono cifrati end-to-end e le chiavi restano sui dispositivi, l&#8217;impatto di una violazione si riduce drasticamente, perch\u00e9 l&#8217;attaccante ottiene solo ciphertext inutilizzabile.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I tre obiettivi che costruiremo passo dopo passo sono questi:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Riservatezza<\/strong>: nessuno tranne il destinatario legge il messaggio.<\/li>\n<li><strong>Autenticit\u00e0 e integrit\u00e0<\/strong>: il destinatario verifica che il messaggio arrivi davvero dal mittente e che non sia stato modificato di un solo bit.<\/li>\n<li><strong>Forward secrecy<\/strong>: la compromissione di una chiave oggi non espone i messaggi scambiati ieri.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Per ottenere questi tre obiettivi non scriveremo primitive crittografiche a mano. La regola d&#8217;oro della crittografia applicata resta valida nel 2026: non implementare algoritmi da zero. Useremo <code>libsodium<\/code>, la libreria moderna derivata da NaCl che espone API ad alto livello difficili da usare in modo insicuro. La documentazione ufficiale di libsodium descrive la versione <code>1.0.22-stable<\/code> come libreria per cifratura, decifratura, firme e hashing delle password.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"prerequisiti-versioni-e-strumenti-necessari\">Prerequisiti: versioni e strumenti necessari<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prima di scrivere codice, verifica di avere l&#8217;ambiente corretto. Tutte le versioni qui sotto sono quelle consigliate a giugno 2026.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Componente<\/th><th>Versione minima<\/th><th>Versione consigliata<\/th><th>Note<\/th><\/tr><\/thead><tbody>\n<tr><td>Node.js<\/td><td>20 LTS<\/td><td>22 LTS<\/td><td>Necessario per ES modules e API crypto native moderne<\/td><\/tr>\n<tr><td>npm<\/td><td>10.x<\/td><td>10.x o superiore<\/td><td>Incluso con Node.js<\/td><\/tr>\n<tr><td>libsodium-wrappers<\/td><td>0.7.x<\/td><td>0.8.4<\/td><td>Wrapper JavaScript\/WebAssembly di libsodium<\/td><\/tr>\n<tr><td>libsodium (nativa)<\/td><td>1.0.20<\/td><td>1.0.22-stable<\/td><td>Usata internamente dal wrapper<\/td><\/tr>\n<tr><td>Editor<\/td><td>qualsiasi<\/td><td>VS Code<\/td><td>Con supporto ESLint consigliato<\/td><\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Controlla la versione di Node.js installata con un singolo comando. Se ottieni una versione inferiore alla 20, aggiorna prima di proseguire, perch\u00e9 le API <code>crypto.webcrypto<\/code> e il supporto stabile agli ES modules sono indispensabili per il progetto.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node --version\nv22.11.0\n\n$ npm --version\n10.9.0<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Una nota importante sulla scelta della libreria. Node.js ha un modulo <code>crypto<\/code> nativo capace di AES-256-GCM e di ECDH, e per molti casi d&#8217;uso basta. In questo tutorial usiamo <code>libsodium-wrappers<\/code> perch\u00e9 espone XChaCha20-Poly1305 (nonce a 192 bit, molto pi\u00f9 tollerante alla generazione casuale) e le API <code>crypto_kx<\/code> e <code>crypto_box<\/code>, pensate esattamente per lo scenario end-to-end. Mostreremo comunque l&#8217;equivalente con il modulo nativo dove utile.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"architettura-del-progetto-di-crittografia-end-to-end\">Architettura del progetto di crittografia end-to-end<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Prima del codice, fissiamo il modello mentale. Immagina due utenti, Alice e Bob, che vogliono scambiarsi messaggi tramite un server che non deve mai vedere il testo in chiaro. Il flusso della <strong>crittografia end-to-end<\/strong> si articola in quattro fasi.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Generazione delle chiavi<\/strong>: ogni utente genera localmente una coppia di chiavi X25519 (pubblica e privata). La chiave privata non lascia mai il dispositivo.<\/li>\n<li><strong>Scambio delle chiavi pubbliche<\/strong>: gli utenti si scambiano le sole chiavi pubbliche tramite il server. Da queste derivano un segreto condiviso identico su entrambi i lati, senza mai trasmetterlo.<\/li>\n<li><strong>Cifratura<\/strong>: il mittente cifra il messaggio con una chiave di sessione derivata dal segreto condiviso, usando XChaCha20-Poly1305.<\/li>\n<li><strong>Decifratura e verifica<\/strong>: il destinatario decifra e verifica contestualmente l&#8217;autenticit\u00e0 grazie al tag Poly1305 incorporato.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Il segreto condiviso si ottiene con la matematica delle curve ellittiche (Diffie-Hellman su Curve25519, da cui il nome X25519). La propriet\u00e0 chiave \u00e8 questa: Alice combina la propria chiave privata con la pubblica di Bob, Bob combina la propria privata con la pubblica di Alice, e i due ottengono lo stesso valore senza che quel valore transiti mai sulla rete. Le chiavi pubbliche X25519 sono lunghe 32 byte, compatte e facili da archiviare.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-1-inizializzare-il-progetto-e-installare-libsodium\">Step 1: Inizializzare il progetto e installare libsodium<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Crea una nuova cartella, inizializza il progetto come modulo ES e installa l&#8217;unica dipendenza necessaria. Useremo <code>\"type\": \"module\"<\/code> per scrivere <code>import<\/code> moderni invece di <code>require<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ mkdir e2ee-node &amp;&amp; cd e2ee-node\n$ npm init -y\n$ npm pkg set type=\"module\"\n$ npm install libsodium-wrappers@0.8.4\n\nadded 1 package in 1s<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Un dettaglio cruciale di <code>libsodium-wrappers<\/code>: la libreria viene caricata in modo asincrono (\u00e8 compilata in WebAssembly) e devi attendere la promise <code>sodium.ready<\/code> prima di chiamare qualsiasi funzione. Dimenticare questo passaggio \u00e8 il primo errore in assoluto di chi inizia. Creiamo un piccolo modulo di bootstrap che esporta l&#8217;istanza gi\u00e0 pronta.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ sodium.js\nimport _sodium from 'libsodium-wrappers';\n\nlet ready = false;\n\nexport async function getSodium() {\n  if (!ready) {\n    await _sodium.ready;\n    ready = true;\n  }\n  return _sodium;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Da qui in avanti ogni modulo che ha bisogno di crittografia chiamer\u00e0 <code>await getSodium()<\/code>. In questo modo la libreria \u00e8 inizializzata una sola volta per processo, anche se la importi in pi\u00f9 file.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-2-generare-le-coppie-di-chiavi-x25519\">Step 2: Generare le coppie di chiavi X25519<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Ogni utente ha bisogno di una coppia di chiavi per lo scambio. La funzione <code>crypto_kx_keypair<\/code> genera una chiave pubblica e una privata da 32 byte ciascuna, pensate proprio per il key exchange. Creiamo il modulo <code>keys.js<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ keys.js\nimport { getSodium } from '.\/sodium.js';\n\nexport async function generateKeyPair() {\n  const sodium = await getSodium();\n  const { publicKey, privateKey } = sodium.crypto_kx_keypair();\n  return { publicKey, privateKey };\n}\n\n\/\/ Utility: byte -&gt; base64 per il trasporto\/archiviazione\nexport async function toBase64(bytes) {\n  const sodium = await getSodium();\n  return sodium.to_base64(bytes, sodium.base64_variants.ORIGINAL);\n}\n\nexport async function fromBase64(str) {\n  const sodium = await getSodium();\n  return sodium.from_base64(str, sodium.base64_variants.ORIGINAL);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La chiave pubblica pu\u00f2 essere inviata al server e condivisa con gli altri utenti senza rischi. La chiave privata, invece, deve restare sul dispositivo: salvala in un keystore sicuro (Keychain su macOS, DPAPI su Windows, libsecret su Linux) oppure cifrala con una password derivata via Argon2 prima di scriverla su disco. Non salvarla mai in chiaro in un database lato server, perch\u00e9 questo annullerebbe l&#8217;intera propriet\u00e0 end-to-end.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-3-lo-scambio-di-chiavi-key-exchange-con-crypto_kx\">Step 3: Lo scambio di chiavi (key exchange) con crypto_kx<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Qui avviene la magia del Diffie-Hellman. Le funzioni <code>crypto_kx_client_session_keys<\/code> e <code>crypto_kx_server_session_keys<\/code> prendono la coppia di chiavi locale e la chiave pubblica della controparte, e restituiscono due chiavi di sessione da 32 byte: una per ricevere (<code>rx<\/code>) e una per trasmettere (<code>tx<\/code>). Il design garantisce che la chiave <code>tx<\/code> del client corrisponda alla chiave <code>rx<\/code> del server e viceversa.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ session.js\nimport { getSodium } from '.\/sodium.js';\n\n\/\/ Lato che inizia la conversazione (client)\nexport async function clientSession(myKeys, theirPublicKey) {\n  const sodium = await getSodium();\n  const { sharedRx, sharedTx } = sodium.crypto_kx_client_session_keys(\n    myKeys.publicKey,\n    myKeys.privateKey,\n    theirPublicKey\n  );\n  return { rx: sharedRx, tx: sharedTx };\n}\n\n\/\/ Lato che riceve la conversazione (server\/peer)\nexport async function serverSession(myKeys, theirPublicKey) {\n  const sodium = await getSodium();\n  const { sharedRx, sharedTx } = sodium.crypto_kx_server_session_keys(\n    myKeys.publicKey,\n    myKeys.privateKey,\n    theirPublicKey\n  );\n  return { rx: sharedRx, tx: sharedTx };\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Attenzione alla simmetria dei ruoli: se Alice usa <code>clientSession<\/code>, Bob deve usare <code>serverSession<\/code>, altrimenti le chiavi <code>rx<\/code> e <code>tx<\/code> non combaceranno e la decifratura fallir\u00e0. \u00c8 una convenzione, non una gerarchia di sicurezza: entrambi i lati restano paritari. Nella pratica chi avvia la conversazione assume il ruolo di client.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il segreto condiviso non viene mai inviato sulla rete. Sul filo viaggiano solo le due chiavi pubbliche da 32 byte. Un attaccante che intercetta entrambe le chiavi pubbliche non riesce comunque a ricavare le chiavi di sessione, perch\u00e9 il problema del logaritmo discreto su Curve25519 \u00e8 computazionalmente intrattabile per i computer classici.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-4-cifrare-i-messaggi-con-xchacha20-poly1305\">Step 4: Cifrare i messaggi con XChaCha20-Poly1305<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Con la chiave di sessione possiamo cifrare. Usiamo <code>crypto_aead_xchacha20poly1305_ietf_encrypt<\/code>, una funzione di cifratura autenticata (AEAD) che produce contemporaneamente ciphertext e tag di autenticazione. XChaCha20-Poly1305 combina lo stream cipher ChaCha20 (specificato in RFC 8439) con il MAC Poly1305, e adotta un nonce esteso a 192 bit (24 byte).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ cipher.js\nimport { getSodium } from '.\/sodium.js';\n\nexport async function encrypt(plaintext, key, associatedData = null) {\n  const sodium = await getSodium();\n  \/\/ Nonce a 192 bit: generato in modo casuale a ogni messaggio\n  const nonce = sodium.randombytes_buf(\n    sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES\n  );\n  const message = typeof plaintext === 'string'\n    ? sodium.from_string(plaintext)\n    : plaintext;\n\n  const ciphertext = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(\n    message,\n    associatedData,   \/\/ dati autenticati ma non cifrati (es. ID mittente)\n    null,             \/\/ nsec, sempre null in questa variante\n    nonce,\n    key\n  );\n\n  return { ciphertext, nonce };\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il parametro <code>associatedData<\/code> (AAD, associated data) merita attenzione. Non viene cifrato, ma viene autenticato: se qualcuno lo modifica, la decifratura fallisce. \u00c8 perfetto per legare il ciphertext a metadati che devono restare in chiaro ma immutabili, come l&#8217;ID del mittente, un timestamp o il numero di sequenza del messaggio. Usarlo previene gli attacchi di sostituzione del contesto, in cui un attaccante riusa un ciphertext valido in una conversazione diversa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il nonce a 192 bit di XChaCha20 \u00e8 il motivo principale per cui lo preferiamo ad AES-256-GCM in questo scenario. Con AES-GCM il nonce \u00e8 di soli 96 bit e generarlo casualmente porta a una probabilit\u00e0 di collisione non trascurabile dopo qualche miliardo di messaggi con la stessa chiave. Con 192 bit la generazione puramente casuale \u00e8 sicura per qualsiasi volume realistico, eliminando la necessit\u00e0 di un contatore stateful.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-5-decifrare-e-verificare-lautenticita\">Step 5: Decifrare e verificare l&#8217;autenticit\u00e0<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La decifratura \u00e8 speculare. La funzione <code>crypto_aead_xchacha20poly1305_ietf_decrypt<\/code> verifica il tag Poly1305 prima di restituire il testo in chiaro: se il ciphertext, il nonce o l&#8217;AAD sono stati alterati, lancia un&#8217;eccezione e non restituisce nulla. Questa \u00e8 la garanzia di integrit\u00e0: non esiste modo di ottenere testo in chiaro da un messaggio manomesso.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ cipher.js (continua)\nexport async function decrypt(ciphertext, nonce, key, associatedData = null) {\n  const sodium = await getSodium();\n  try {\n    const decrypted = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(\n      null,             \/\/ nsec, sempre null\n      ciphertext,\n      associatedData,\n      nonce,\n      key\n    );\n    return sodium.to_string(decrypted);\n  } catch (err) {\n    \/\/ Tag non valido: messaggio manomesso o chiave errata\n    throw new Error('Decifratura fallita: messaggio non autentico');\n  }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Non ignorare mai l&#8217;eccezione e non restituire un valore parziale. Un fallimento di decifratura significa una di queste cose: chiave sbagliata, nonce sbagliato, AAD diverso, oppure un tentativo di manomissione. In tutti i casi il comportamento corretto \u00e8 rifiutare il messaggio. Mostrare messaggi di errore diversi per &#8220;chiave errata&#8221; e &#8220;tag non valido&#8221; pu\u00f2 aprire la porta a un oracle attack: meglio un errore generico.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-6-gestione-corretta-dei-nonce\">Step 6: Gestione corretta dei nonce<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Il nonce (number used once) \u00e8 il dato pi\u00f9 sottovalutato e pi\u00f9 pericoloso di tutto il sistema. La regola assoluta degli schemi AEAD \u00e8: mai riusare la stessa coppia (chiave, nonce) per due messaggi diversi. Il riuso del nonce con la stessa chiave rompe sia la riservatezza sia, in molti casi, l&#8217;integrit\u00e0, perch\u00e9 permette di recuperare il keystream e di forgiare tag validi.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Esistono due strategie corrette. La prima, che abbiamo gi\u00e0 adottato, \u00e8 generare un nonce casuale a 192 bit per ogni messaggio: con XChaCha20 lo spazio \u00e8 cos\u00ec grande che le collisioni sono trascurabili. La seconda \u00e8 usare un contatore monotono crescente, utile quando vuoi anche rilevare e rifiutare i replay. Ecco un esempio del secondo approccio, che incorpora il numero di sequenza nel nonce.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ counter-nonce.js\nimport { getSodium } from '.\/sodium.js';\n\nexport function makeCounterNonce(sodium, counter) {\n  const nonce = new Uint8Array(\n    sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES\n  );\n  \/\/ Scrive il contatore (big-endian) negli ultimi 8 byte del nonce\n  const view = new DataView(nonce.buffer);\n  view.setBigUint64(\n    nonce.length - 8,\n    BigInt(counter),\n    false \/\/ big-endian\n  );\n  return nonce;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Se scegli il contatore, devi persistere il valore in modo durevole: un riavvio che azzera il contatore reintroduce il riuso del nonce. Per questo, per la maggior parte delle applicazioni, il nonce casuale a 192 bit \u00e8 la scelta pi\u00f9 semplice e robusta. La tabella riassume il confronto tra le due strategie e i due cifrari principali.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Caratteristica<\/th><th>XChaCha20-Poly1305<\/th><th>ChaCha20-Poly1305 (IETF)<\/th><th>AES-256-GCM<\/th><\/tr><\/thead><tbody>\n<tr><td>Dimensione nonce<\/td><td>192 bit (24 byte)<\/td><td>96 bit (12 byte)<\/td><td>96 bit (12 byte)<\/td><\/tr>\n<tr><td>Nonce casuale sicuro<\/td><td>S\u00ec, sempre<\/td><td>Solo fino a circa 2^32 msg<\/td><td>Solo fino a circa 2^32 msg<\/td><\/tr>\n<tr><td>Accelerazione hardware<\/td><td>No (CPU generica)<\/td><td>No<\/td><td>S\u00ec (AES-NI)<\/td><\/tr>\n<tr><td>Robustezza nonce casuale<\/td><td>Molto alta<\/td><td>Media<\/td><td>Media<\/td><\/tr>\n<tr><td>Tag di autenticazione<\/td><td>128 bit<\/td><td>128 bit<\/td><td>128 bit<\/td><\/tr>\n<tr><td>Disponibile in libsodium<\/td><td>S\u00ec (alto livello)<\/td><td>S\u00ec<\/td><td>Non come primitiva<\/td><\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-7-forward-secrecy-con-la-rotazione-delle-chiavi-ratchet\">Step 7: Forward secrecy con la rotazione delle chiavi (ratchet)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Finora abbiamo una sessione cifrata, ma se un attaccante registra tutto il traffico e in futuro ruba la chiave privata di lungo termine, pu\u00f2 decifrare l&#8217;intero storico. La <strong>forward secrecy<\/strong> elimina questo rischio: ogni messaggio (o gruppo di messaggi) usa una chiave effimera che viene distrutta subito dopo l&#8217;uso. La compromissione della chiave di oggi non rivela i messaggi di ieri.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il riferimento di settore \u00e8 il Double Ratchet del Signal Protocol, che combina uno scambio Diffie-Hellman effimero a ogni passo con un ratchet simmetrico via funzione di derivazione delle chiavi. Implementare un Double Ratchet completo va oltre questo tutorial, ma possiamo costruire una versione semplificata di ratchet simmetrico che fa avanzare la chiave a ogni messaggio usando una KDF (key derivation function). Usiamo <code>crypto_kdf_derive_from_key<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ ratchet.js\nimport { getSodium } from '.\/sodium.js';\n\nconst CONTEXT = 'e2ee-ctx'; \/\/ 8 byte di contesto\n\n\/\/ Deriva la chiave del messaggio N e la prossima chiave di catena\nexport async function ratchetStep(chainKey, counter) {\n  const sodium = await getSodium();\n  const messageKey = sodium.crypto_kdf_derive_from_key(\n    32, counter, CONTEXT, chainKey\n  );\n  const nextChainKey = sodium.crypto_kdf_derive_from_key(\n    32, counter + 1, CONTEXT, chainKey\n  );\n  \/\/ La vecchia chainKey va azzerata dal chiamante dopo questo step\n  return { messageKey, nextChainKey };\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il principio \u00e8 semplice: ogni messaggio ottiene una <code>messageKey<\/code> univoca derivata dalla catena, e la catena avanza in modo irreversibile. Dato che la KDF \u00e8 a senso unico, conoscere una chiave futura non permette di tornare indietro alle chiavi passate. Ricorda di sovrascrivere in memoria le chiavi usate con <code>sodium.memzero<\/code> non appena hai finito, per ridurre la finestra di esposizione in caso di dump della RAM.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-8-firmare-lidentita-con-ed25519\">Step 8: Firmare l&#8217;identit\u00e0 con Ed25519<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Lo scambio X25519 protegge dalla lettura, ma da solo non protegge dall&#8217;attacco man-in-the-middle: se un attaccante sostituisce le chiavi pubbliche durante lo scambio, pu\u00f2 inserirsi al centro della conversazione. La difesa \u00e8 autenticare le chiavi pubbliche con una firma digitale Ed25519 legata a un&#8217;identit\u00e0 verificabile. Ed25519 \u00e8 veloce, produce firme da 64 byte e chiavi pubbliche da 32 byte.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ identity.js\nimport { getSodium } from '.\/sodium.js';\n\nexport async function createIdentity() {\n  const sodium = await getSodium();\n  const { publicKey, privateKey } = sodium.crypto_sign_keypair();\n  return { signPub: publicKey, signPriv: privateKey };\n}\n\n\/\/ Firma la chiave pubblica X25519 con la chiave di identit\u00e0 Ed25519\nexport async function signKey(x25519PublicKey, signPriv) {\n  const sodium = await getSodium();\n  return sodium.crypto_sign_detached(x25519PublicKey, signPriv);\n}\n\nexport async function verifyKey(signature, x25519PublicKey, signPub) {\n  const sodium = await getSodium();\n  return sodium.crypto_sign_verify_detached(\n    signature, x25519PublicKey, signPub\n  );\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nel mondo reale la chiave di identit\u00e0 Ed25519 viene verificata fuori banda: tramite un QR code scansionato di persona (il &#8220;safety number&#8221; di Signal), un certificato emesso da una PKI aziendale, o la trust on first use con notifica all&#8217;utente in caso di cambio chiave. Senza questo anello, la <strong>crittografia end-to-end<\/strong> protegge dal server curioso ma non da un man-in-the-middle attivo capace di sostituire le chiavi.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-9-serializzare-e-trasportare-il-messaggio-cifrato\">Step 9: Serializzare e trasportare il messaggio cifrato<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Il ciphertext e il nonce sono array di byte (<code>Uint8Array<\/code>). Per inviarli su una API JSON o salvarli in un database, vanno serializzati. Lo standard pratico \u00e8 codificarli in base64 e impacchettarli in un oggetto con un campo di versione, utile per future migrazioni di algoritmo.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ envelope.js\nimport { toBase64, fromBase64 } from '.\/keys.js';\n\nexport async function pack(ciphertext, nonce, senderId) {\n  return JSON.stringify({\n    v: 1,\n    alg: 'xchacha20poly1305',\n    sender: senderId,\n    nonce: await toBase64(nonce),\n    ct: await toBase64(ciphertext)\n  });\n}\n\nexport async function unpack(json) {\n  const obj = JSON.parse(json);\n  if (obj.v !== 1) throw new Error('Versione envelope non supportata');\n  return {\n    senderId: obj.sender,\n    nonce: await fromBase64(obj.nonce),\n    ciphertext: await fromBase64(obj.ct)\n  };\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Il campo <code>v<\/code> di versione non \u00e8 un dettaglio cosmetico: quando migrerai a una primitiva post-quantum (Step 12) potrai distinguere i vecchi messaggi dai nuovi e decifrare entrambi durante il periodo di transizione. Lega il campo <code>sender<\/code> all&#8217;AAD durante la cifratura, cos\u00ec l&#8217;identit\u00e0 del mittente resta autenticata e non sostituibile.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-10-il-progetto-completo-che-mette-tutto-insieme\">Step 10: Il progetto completo che mette tutto insieme<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u00c8 il momento di assemblare i moduli in un flusso end-to-end completo, da Alice a Bob, con scambio di chiavi, cifratura e decifratura. Crea <code>demo.js<\/code>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ demo.js\nimport { generateKeyPair } from '.\/keys.js';\nimport { clientSession, serverSession } from '.\/session.js';\nimport { encrypt, decrypt } from '.\/cipher.js';\nimport { pack, unpack } from '.\/envelope.js';\n\nasync function main() {\n  \/\/ 1. Alice e Bob generano le proprie coppie di chiavi\n  const alice = await generateKeyPair();\n  const bob = await generateKeyPair();\n\n  \/\/ 2. Scambio: ognuno conosce la chiave pubblica dell'altro\n  const aliceSession = await clientSession(alice, bob.publicKey);\n  const bobSession = await serverSession(bob, alice.publicKey);\n\n  \/\/ 3. Alice cifra un messaggio per Bob (usa la sua chiave tx)\n  const aad = new TextEncoder().encode('alice@example.com');\n  const { ciphertext, nonce } = await encrypt(\n    'Ciao Bob, questo messaggio \u00e8 end-to-end.',\n    aliceSession.tx,\n    aad\n  );\n\n  \/\/ 4. Serializzazione e \"invio\" tramite il server\n  const envelope = await pack(ciphertext, nonce, 'alice@example.com');\n  console.log('Sul filo viaggia solo:', envelope.slice(0, 80), '...');\n\n  \/\/ 5. Bob riceve, deserializza e decifra (usa la sua chiave rx)\n  const received = await unpack(envelope);\n  const aadBob = new TextEncoder().encode(received.senderId);\n  const plaintext = await decrypt(\n    received.ciphertext,\n    received.nonce,\n    bobSession.rx,\n    aadBob\n  );\n\n  console.log('Bob legge:', plaintext);\n}\n\nmain().catch(console.error);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nota la simmetria delle chiavi di sessione: Alice cifra con <code>aliceSession.tx<\/code> e Bob decifra con <code>bobSession.rx<\/code>. Grazie al design di <code>crypto_kx<\/code> queste due chiavi sono identiche. Per il traffico nella direzione opposta (Bob verso Alice) useresti <code>bobSession.tx<\/code> e <code>aliceSession.rx<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-11-eseguire-i-test-e-leggere-loutput-di-esempio\">Step 11: Eseguire i test e leggere l&#8217;output di esempio<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Avvia la demo e osserva il risultato. Il server vede solo ciphertext base64 illeggibile, mentre Bob ricostruisce il testo originale.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node demo.js\nSul filo viaggia solo: {\"v\":1,\"alg\":\"xchacha20poly1305\",\"sender\":\"alice@example.com\",\"nonce\":\"k9Xq2...\"} ...\nBob legge: Ciao Bob, questo messaggio \u00e8 end-to-end.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Aggiungiamo un test che dimostra la propriet\u00e0 di integrit\u00e0: se modifichiamo un solo byte del ciphertext, la decifratura deve fallire. Questo \u00e8 il cuore della verifica di autenticit\u00e0.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ tamper-test.js\nimport { generateKeyPair } from '.\/keys.js';\nimport { clientSession, serverSession } from '.\/session.js';\nimport { encrypt, decrypt } from '.\/cipher.js';\n\nconst alice = await generateKeyPair();\nconst bob = await generateKeyPair();\nconst aSess = await clientSession(alice, bob.publicKey);\nconst bSess = await serverSession(bob, alice.publicKey);\n\nconst { ciphertext, nonce } = await encrypt('dati riservati', aSess.tx);\n\n\/\/ Manomissione: capovolgiamo un bit\nciphertext[0] ^= 0x01;\n\ntry {\n  await decrypt(ciphertext, nonce, bSess.rx);\n  console.log('ERRORE: la manomissione non \u00e8 stata rilevata!');\n} catch (e) {\n  console.log('OK: manomissione rilevata -&gt;', e.message);\n}<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>$ node tamper-test.js\nOK: manomissione rilevata -&gt; Decifratura fallita: messaggio non autentico<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Un benchmark rapido aiuta a capire le prestazioni. Su hardware desktop moderno senza accelerazione AES-NI, XChaCha20-Poly1305 cifra centinaia di megabyte al secondo, pi\u00f9 che sufficiente per messaggistica e file di dimensione media. Su CPU con AES-NI, AES-256-GCM pu\u00f2 risultare pi\u00f9 veloce su file molto grandi, ma per messaggi corti la differenza \u00e8 irrilevante e la robustezza del nonce a 192 bit pesa di pi\u00f9 nella scelta.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-12-migrazione-post-quantum-con-kem-ibrido-ml-kem\">Step 12: Migrazione post-quantum con KEM ibrido ML-KEM<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Nel 2026 la minaccia quantistica non \u00e8 pi\u00f9 teorica nella pianificazione di sicurezza. Il rischio concreto \u00e8 il &#8220;harvest now, decrypt later&#8221;: un attaccante registra oggi il traffico cifrato con X25519 e lo decifra in futuro, quando un computer quantistico abbastanza grande romper\u00e0 la crittografia a curve ellittiche. Per i dati che devono restare riservati per un decennio, questo rischio \u00e8 reale adesso.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La risposta \u00e8 il KEM (key encapsulation mechanism) ibrido. ML-KEM, lo standard NIST pubblicato come FIPS 203 e basato sull&#8217;algoritmo Kyber, \u00e8 il KEM post-quantum di riferimento. La strategia consigliata per la transizione non \u00e8 sostituire X25519, ma affiancarlo: il segreto condiviso finale deriva dalla combinazione del segreto X25519 e del segreto ML-KEM. Cos\u00ec la sessione resta sicura finch\u00e9 almeno uno dei due rimane intatto.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ hybrid-kdf.js (concetto)\n\/\/ ss_classico  = X25519(privA, pubB)          \/\/ 32 byte\n\/\/ ss_pq        = ML-KEM-768 encapsulate(...)   \/\/ segreto post-quantum\n\/\/ chiave finale = BLAKE2b( ss_classico || ss_pq )\nimport { getSodium } from '.\/sodium.js';\n\nexport async function combineSecrets(ssClassic, ssPq) {\n  const sodium = await getSodium();\n  const concat = new Uint8Array(ssClassic.length + ssPq.length);\n  concat.set(ssClassic, 0);\n  concat.set(ssPq, ssClassic.length);\n  \/\/ BLAKE2b come KDF per derivare 32 byte di chiave di sessione\n  return sodium.crypto_generichash(32, concat);\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Al momento <code>libsodium-wrappers<\/code> non espone ML-KEM come primitiva di prima classe, quindi nel 2026 servono librerie dedicate (ad esempio i binding di liboqs o le API sperimentali del modulo crypto in alcune build di Node.js) per la parte post-quantum, mentre libsodium continua a fornire il ramo classico X25519 e la KDF di combinazione. Il campo <code>v<\/code> di versione che abbiamo previsto nell&#8217;envelope serve proprio a gestire la coesistenza tra messaggi classici e ibridi durante la migrazione. Per approfondire lo stato dell&#8217;arte, consulta la nostra guida dedicata alla crittografia post-quantum.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"5-errori-comuni-da-evitare-nella-crittografia-end-to-end\">5 errori comuni da evitare nella crittografia end-to-end<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La maggior parte delle vulnerabilit\u00e0 nei sistemi E2EE non nasce da algoritmi deboli, ma da errori di implementazione. Ecco i cinque pi\u00f9 frequenti, con la relativa correzione.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Riuso del nonce<\/strong>. Usare due volte la stessa coppia (chiave, nonce) \u00e8 l&#8217;errore pi\u00f9 grave: espone il keystream e permette di forgiare messaggi. Correzione: nonce casuale a 192 bit con XChaCha20, oppure contatore monotono persistito su disco.<\/li>\n<li><strong>Cifratura senza autenticazione<\/strong>. Usare uno stream cipher puro senza MAC lascia il ciphertext manipolabile. Correzione: usa sempre un AEAD come XChaCha20-Poly1305, che integra l&#8217;autenticazione.<\/li>\n<li><strong>Chiave statica senza ratchet<\/strong>. Cifrare anni di messaggi con la stessa chiave annulla la forward secrecy. Correzione: ruota le chiavi con un ratchet (Step 7) e azzera quelle vecchie.<\/li>\n<li><strong>Chiave privata sul server<\/strong>. Salvare la chiave privata lato server distrugge l&#8217;intera propriet\u00e0 end-to-end. Correzione: la chiave privata resta sul dispositivo, cifrata a riposo.<\/li>\n<li><strong>Nessuna autenticazione delle chiavi pubbliche<\/strong>. Senza firma Ed25519 e verifica fuori banda, un man-in-the-middle attivo sostituisce le chiavi. Correzione: firma le chiavi (Step 8) e verifica i safety number.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Un sesto errore, pi\u00f9 sottile, \u00e8 dimenticare di chiamare <code>await sodium.ready<\/code>: il programma fallisce con metodi <code>undefined<\/code> in modo confuso. Il modulo di bootstrap dello Step 1 lo previene centralizzando l&#8217;inizializzazione.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"risoluzione-dei-problemi-8-errori-frequenti-e-soluzioni\">Risoluzione dei problemi: 8 errori frequenti e soluzioni<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Quando qualcosa non funziona, questa tabella di troubleshooting copre i casi pi\u00f9 comuni che incontrerai mentre sviluppi un sistema di <strong>crittografia end-to-end<\/strong> in Node.js.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Sintomo \/ Errore<\/th><th>Causa probabile<\/th><th>Soluzione<\/th><\/tr><\/thead><tbody>\n<tr><td><code>TypeError: ...is not a function<\/code><\/td><td>Chiamata prima di <code>sodium.ready<\/code><\/td><td>Attendi <code>await getSodium()<\/code> prima di ogni operazione<\/td><\/tr>\n<tr><td>Decifratura sempre fallita tra due peer<\/td><td>Ruoli client\/server invertiti<\/td><td>Un lato usa <code>clientSession<\/code>, l&#8217;altro <code>serverSession<\/code><\/td><\/tr>\n<tr><td>Decifratura con chiave errata<\/td><td>Chiavi <code>rx<\/code>\/<code>tx<\/code> scambiate<\/td><td>Cifra con <code>tx<\/code>, decifra con la <code>rx<\/code> corrispondente<\/td><\/tr>\n<tr><td>Decifratura fallita dopo modifica AAD<\/td><td>AAD diverso tra cifratura e decifratura<\/td><td>Passa lo stesso identico AAD in entrambe le funzioni<\/td><\/tr>\n<tr><td><code>incorrect base64<\/code> in fase di unpack<\/td><td>Variante base64 diversa<\/td><td>Usa la stessa variante (ORIGINAL) per encode e decode<\/td><\/tr>\n<tr><td>Caratteri accentati corrotti<\/td><td>Conversione stringa\/byte errata<\/td><td>Usa <code>from_string<\/code>\/<code>to_string<\/code> di libsodium (UTF-8)<\/td><\/tr>\n<tr><td><code>ERR_REQUIRE_ESM<\/code><\/td><td>Mix di require e import<\/td><td>Imposta <code>\"type\": \"module\"<\/code> e usa solo <code>import<\/code><\/td><\/tr>\n<tr><td>Collisione nonce su volumi enormi<\/td><td>Contatore non persistito dopo riavvio<\/td><td>Persisti il contatore o passa al nonce casuale XChaCha20<\/td><\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Se la decifratura fallisce in modo intermittente solo in produzione e non in locale, controlla la serializzazione: middleware che alterano l&#8217;encoding (ad esempio body-parser che reinterpreta i byte come UTF-8) sono una causa frequente di corruzione del ciphertext. Trasporta sempre i byte in base64 o base64url, mai come stringa grezza.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"consigli-avanzati-per-la-produzione\">Consigli avanzati per la produzione<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Una volta che il flusso base funziona, queste pratiche elevano il progetto a livello produttivo.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"azzeramento-sicuro-della-memoria\">Azzeramento sicuro della memoria<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">JavaScript non garantisce quando il garbage collector liberer\u00e0 un buffer contenente chiavi. Usa <code>sodium.memzero(buffer)<\/code> per sovrascrivere esplicitamente le chiavi di sessione e i testi in chiaro non appena hai finito. Riduce la finestra in cui un dump della memoria espone segreti. Non \u00e8 una garanzia assoluta in un runtime gestito, ma \u00e8 una difesa in profondit\u00e0 che ha senso adottare.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"limiti-di-dimensione-e-streaming\">Limiti di dimensione e streaming<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Le API <code>crypto_aead_..._encrypt<\/code> caricano l&#8217;intero messaggio in memoria. Per file di grandi dimensioni usa l&#8217;API di streaming <code>crypto_secretstream_xchacha20poly1305<\/code>, che cifra a chunk e gestisce automaticamente i nonce e un tag finale che impedisce il troncamento del flusso. \u00c8 la scelta giusta per allegati, backup e trasferimenti di file end-to-end.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"audit-e-dipendenze\">Audit e dipendenze<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Esegui <code>npm audit<\/code> a ogni build e blocca la versione esatta di <code>libsodium-wrappers<\/code> nel file di lock. La supply chain di npm resta un vettore d&#8217;attacco primario nel 2026: una dipendenza crittografica compromessa annulla qualsiasi garanzia. Verifica gli hash dei pacchetti e considera l&#8217;uso di un registry privato con mirroring per gli ambienti sensibili.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"confronto-libsodium-contro-il-modulo-crypto-nativo-di-node-js\">Confronto: libsodium contro il modulo crypto nativo di Node.js<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Molti chiedono se serva davvero una dipendenza esterna. La risposta dipende dal caso d&#8217;uso. Il modulo <code>crypto<\/code> nativo di Node.js supporta AES-256-GCM e ECDH e non richiede pacchetti aggiuntivi, ma le sue API a basso livello lasciano pi\u00f9 spazio all&#8217;errore. La tabella riassume i criteri di scelta.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Criterio<\/th><th>libsodium-wrappers<\/th><th>crypto nativo Node.js<\/th><\/tr><\/thead><tbody>\n<tr><td>Dipendenze esterne<\/td><td>Una (libsodium-wrappers)<\/td><td>Nessuna<\/td><\/tr>\n<tr><td>API ad alto livello sicure<\/td><td>S\u00ec (crypto_box, crypto_kx)<\/td><td>No, a basso livello<\/td><\/tr>\n<tr><td>XChaCha20-Poly1305<\/td><td>S\u00ec, nonce 192 bit<\/td><td>No (solo ChaCha20 IETF 96 bit)<\/td><\/tr>\n<tr><td>Scambio chiavi pronto<\/td><td>S\u00ec (crypto_kx)<\/td><td>Manuale via ECDH + KDF<\/td><\/tr>\n<tr><td>Streaming AEAD<\/td><td>S\u00ec (secretstream)<\/td><td>Parziale, manuale<\/td><\/tr>\n<tr><td>Funziona anche nel browser<\/td><td>S\u00ec (WebAssembly)<\/td><td>No (solo Node)<\/td><\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">In sintesi: per un&#8217;applicazione end-to-end completa, soprattutto se il codice gira anche su client browser, <code>libsodium-wrappers<\/code> riduce drasticamente la superficie d&#8217;errore. Per una singola operazione di cifratura a riposo lato server, il modulo nativo \u00e8 pi\u00f9 che adeguato. La nostra guida ad AES-256 in Node.js copre quest&#8217;ultimo scenario in dettaglio.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"domande-frequenti-sulla-crittografia-end-to-end\">Domande frequenti sulla crittografia end-to-end<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"qual-e-la-differenza-tra-crittografia-end-to-end-e-https\">Qual \u00e8 la differenza tra crittografia end-to-end e HTTPS?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">HTTPS (basato su TLS) cifra i dati in transito tra client e server, ma il server vede comunque il testo in chiaro. La <strong>crittografia end-to-end<\/strong> cifra i dati in modo che solo i due endpoint (mittente e destinatario) possano leggerli, escludendo anche il server intermedio. Le due tecnologie sono complementari: si usa TLS per il trasporto e l&#8217;E2EE per il contenuto.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"libsodium-e-sicuro-per-la-produzione-nel-2026\">libsodium \u00e8 sicuro per la produzione nel 2026?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">S\u00ec. libsodium \u00e8 una delle librerie crittografiche pi\u00f9 verificate e usate, derivata da NaCl di Daniel J. Bernstein. La versione documentata \u00e8 1.0.22-stable e il wrapper JavaScript <code>libsodium-wrappers<\/code> 0.8.4 \u00e8 mantenuto attivamente. Le sue API ad alto livello sono progettate per essere difficili da usare in modo insicuro.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"devo-usare-xchacha20-poly1305-o-aes-256-gcm\">Devo usare XChaCha20-Poly1305 o AES-256-GCM?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Entrambi sono sicuri. XChaCha20-Poly1305 ha un nonce a 192 bit che rende la generazione casuale sicura per qualsiasi volume, mentre AES-256-GCM ha un nonce a 96 bit pi\u00f9 soggetto a collisioni e beneficia dell&#8217;accelerazione hardware AES-NI. Per la messaggistica end-to-end, XChaCha20-Poly1305 \u00e8 la scelta pi\u00f9 robusta per gestione dei nonce.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"la-crittografia-end-to-end-protegge-dai-computer-quantistici\">La crittografia end-to-end protegge dai computer quantistici?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Lo scambio X25519 da solo non \u00e8 resistente ai computer quantistici. Per proteggere i dati a lungo termine dal rischio &#8220;harvest now, decrypt later&#8221;, affianca a X25519 un KEM post-quantum come ML-KEM (FIPS 203), combinando i due segreti in un approccio ibrido, come descritto nello Step 12.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"cosa-succede-se-perdo-la-chiave-privata\">Cosa succede se perdo la chiave privata?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Se la chiave privata viene persa, i messaggi cifrati per quella chiave diventano irrecuperabili: \u00e8 il prezzo della vera <strong>crittografia end-to-end<\/strong>, perch\u00e9 nessun server custodisce una copia. Le soluzioni pratiche includono il backup cifrato della chiave (protetto da una passphrase robusta derivata con Argon2) o un sistema di recupero con chiavi multiple.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"questo-codice-e-pronto-per-la-produzione-cosi-come\">Questo codice \u00e8 pronto per la produzione cos\u00ec com&#8217;\u00e8?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Il tutorial fornisce le fondamenta corrette, ma un sistema di produzione richiede in pi\u00f9: gestione completa del ratchet (Double Ratchet), autenticazione robusta delle identit\u00e0, gestione del recupero chiavi, audit di sicurezza indipendente e protezione dell&#8217;archiviazione delle chiavi private. Usa questo progetto come base solida da estendere, non come prodotto finito.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"posso-usare-lo-stesso-codice-nel-browser\">Posso usare lo stesso codice nel browser?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">S\u00ec. Uno dei vantaggi di <code>libsodium-wrappers<\/code> \u00e8 che, essendo compilato in WebAssembly, funziona in modo identico in Node.js e nel browser. Questo permette di condividere la logica crittografica tra client e server, riducendo le incongruenze. Ricorda comunque che nel browser la protezione della chiave privata \u00e8 pi\u00f9 delicata.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"related-coverage\">Related Coverage<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"\/aes-256-encryption-nodejs\/\">AES-256 Encryption in Node.js: la guida passo passo alla cifratura simmetrica<\/a><\/li>\n<li><a href=\"\/argon2-password-hashing-nodejs\/\">Argon2 Password Hashing in Node.js: proteggere le password e derivare chiavi<\/a><\/li>\n<li><a href=\"\/post-quantum-cryptography-2026\/\">Crittografia post-quantum nel 2026: ML-KEM, Dilithium e la migrazione<\/a><\/li>\n<li><a href=\"\/signal-vs-whatsapp-vs-telegram\/\">Signal vs WhatsApp vs Telegram: come si confrontano per la crittografia end-to-end<\/a><\/li>\n<li><a href=\"\/https-tls-explained\/\">HTTPS e TLS spiegati: cosa protegge davvero il lucchetto<\/a><\/li>\n<li><a href=\"\/cryptography\/\">Crittografia e hashing: la guida pilastro di shattered.io<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"conclusione-una-base-solida-per-la-crittografia-end-to-end\">Conclusione: una base solida per la crittografia end-to-end<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Hai costruito un sistema di <strong>crittografia end-to-end<\/strong> funzionante in Node.js: generazione di chiavi X25519, scambio sicuro con <code>crypto_kx<\/code>, cifratura autenticata con XChaCha20-Poly1305, gestione corretta dei nonce, forward secrecy con un ratchet simmetrico, firma delle identit\u00e0 con Ed25519 e una strategia di migrazione post-quantum. I tre obiettivi iniziali (riservatezza, autenticit\u00e0, forward secrecy) sono coperti dal codice che hai scritto.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Il passo successivo \u00e8 estendere il ratchet a un Double Ratchet completo e integrare la verifica delle identit\u00e0 nella tua applicazione reale. Per i riferimenti normativi e tecnici, consulta la documentazione ufficiale di libsodium, la specifica del Double Ratchet di Signal, lo standard FIPS 203 del NIST per ML-KEM e la RFC 8439 per ChaCha20-Poly1305. La crittografia applicata premia chi resta aggiornato: rivedi le tue scelte ogni anno, perch\u00e9 lo scenario delle minacce, soprattutto quella quantistica, evolve in fretta.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Fonti e approfondimenti: <a href=\"https:\/\/doc.libsodium.org\/\" target=\"_blank\" rel=\"noopener\">documentazione ufficiale libsodium<\/a>, <a href=\"https:\/\/nodejs.org\/api\/crypto.html\" target=\"_blank\" rel=\"noopener\">modulo crypto di Node.js<\/a>, <a href=\"https:\/\/signal.org\/docs\/specifications\/doubleratchet\/\" target=\"_blank\" rel=\"noopener\">specifica Double Ratchet di Signal<\/a>, <a href=\"https:\/\/csrc.nist.gov\/pubs\/fips\/203\/final\" target=\"_blank\" rel=\"noopener\">NIST FIPS 203 (ML-KEM)<\/a>, <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc8439\" target=\"_blank\" rel=\"noopener\">RFC 8439 (ChaCha20-Poly1305)<\/a>.<\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>La crittografia end-to-end non \u00e8 pi\u00f9 un argomento riservato ai team di Signal o WhatsApp. Nel 2026 qualsiasi sviluppatore che gestisce messaggi privati, documenti sanitari, dati finanziari o backup degli\u2026<\/p>\n","protected":false},"author":5,"featured_media":60,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2],"tags":[],"class_list":["post-59","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\/59","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\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/comments?post=59"}],"version-history":[{"count":0,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/posts\/59\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/media\/60"}],"wp:attachment":[{"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/media?parent=59"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/categories?post=59"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/shattered.io\/it\/wp-json\/wp\/v2\/tags?post=59"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}