Senza una corretta crittografia, i dati sono vulnerabili all'intercettazione, alla modifica e al furto durante la trasmissione. JavaScript, a lungo confinato alle interazioni basate sul browser, offre ora strumenti maturi, pronti alla produzione per l'implementazione di forti canali di crittografia e di comunicazione. Questa guida fornisce una guida completa e pratica dell'utilizzo di JavaScript per la crittografia dei dati e le librerie di sicurezza sicure, coprendo le pratiche di implementazione dei dati.

Comprendere i Fondamenti di Crittografia dei dati

La crittografia è il processo di trasformazione del testo chiaro leggibile in un testo cifrato illeggibile utilizzando un algoritmo e una chiave segreta. Solo le parti autorizzate che possiedono la chiave di decrittografia corretta possono invertire la trasformazione e recuperare i dati originali. La forza di un sistema di crittografia dipende dalle proprietà matematiche dell'algoritmo, dalla lunghezza della chiave e dalla casualità, e dalla sicurezza delle pratiche di gestione chiave.

La cripografia distingue tra due ampie categorie di crittografia:

  • Crezione simmetrica[[] – utilizza una singola chiave condivisa per la crittografia e la decrittografia. È veloce ed efficiente per i dati di massa, ma la chiave deve essere scambiata in modo sicuro tra le parti. Esempi: AES, ChaCha20.
  • Codifica asimmetrica[] – utilizza una coppia di chiavi pubblico-privato. La chiave pubblica crittografa i dati; solo la chiave privata corrispondente può decifrarlo. Questo elimina la necessità di un segreto condiviso ma è computazionalmente più lento. Esempi: RSA, ECC (cripografia a curva ellittica).

Inoltre, hashing[] (ad esempio, SHA‐256) è una funzione a senso unico che produce una digerenza a dimensione fissa dai dati di input. Non è crittografia (non si può invertire), ma è essenziale per i controlli di integrità, l'archiviazione password e le firme digitali. HMAC verifica a autentica] (i dati chiave codifica basata su Hash-

Crittografia nel Browser vs. Node.js

JavaScript funziona in due ambienti principali: il browser e Node.js (server-side). Ogni ambiente fornisce diverse interfacce native:

librerie di terze parti come ]CryptoJS, libsodium.js, e Forge]] possono integrare le API native, ma gli sviluppatori dovrebbero preferire implementazioni native ogni volta possibile per evitare algoritmi scarsamente implementati o deprecati.

Algoritmi di crittografia comune in JavaScript

AES (Standard di crittografia avanzata)

AES è il cifrario simmetrico de facto utilizzato in tutto il mondo, che opera su blocchi a 128 bit e supporta le dimensioni chiave di 128, 192, o 256 bit.

  • AES‐GCM[] (Modalità Gallo/Paese) — fornisce una crittografia autenticata; crittografa e produce un tag di autenticazione che rileva la manomissione.
  • AES‐CBC[] (Cintura blocco cifrario) – richiede un vettore di inizializzazione (IV) e un'imbottitura. Non fornisce l'integrità da solo, quindi dovrebbe essere combinato con HMAC o utilizzato in un protocollo che aggiunge l'autenticazione.

Esempio utilizzando l'API Web Crypto (browser) per crittografare con AES‐GCM:

async function encryptAESGCM(data, key) {
 const iv = crypto.getRandomValues(new Uint8Array(12));
 const encoded = new TextEncoder().encode(data);
 const encrypted = await crypto.subtle.encrypt(
 { name: 'AES-GCM', iv },
 key,
 encoded
 );
 return { iv, ciphertext: new Uint8Array(encrypted) };
}

// Generate a 256‑bit AES key
const key = await crypto.subtle.generateKey(
 { name: 'AES-GCM', length: 256 },
 true,
 ['encrypt', 'decrypt']
);

RSA (Rivest–Shamir–Adleman)

RSA è un algoritmo asimmetrico comunemente usato per lo scambio di chiavi e le firme digitali. In JavaScript, è disponibile tramite il Web Crypto API (browser) e il modulo Node.js . RSA-OAEP (Optimal Asymmetric Encryption Padding) è lo schema di crittografia raccomandato, in quanto include l'imbottitura casuale per proteggere dagli attacchi prescelti.

RSA è molto più lento della crittografia simmetrica ed è limitato nella dimensione dei dati che può crittografare (proporzionale alla dimensione chiave). In pratica, RSA è usato per crittografare una chiave AES (crittografia ibrida), e poi AES crittografa il carico reale.

SHA‐256 / SHA‐3 (Secure Hash Algorithms)

SHA‐256 è l'hash più utilizzato nella moderna sicurezza del web (ad esempio, per i certificati TLS, HMAC e password che si schiantano con il sale). L'API Web Crypto fornisce per SHA‐1, SHA‐256, SHA‐384, e SHA‐512. Nota che SHA‐1 è un attacco di collisione.

Cripografia della curva ellittica (ECC)

ECC offre una sicurezza equivalente a RSA con chiavi molto più corte, rendendolo ideale per applicazioni mobili e ad alte prestazioni. L'API Web Crypto supporta ECDH (Elliptic Curve Diffie‐Hellman) per un accordo chiave e ECDSA (Elliptic Curve Digital Signature Algorithm) per la firma. Il modulo Node.js [[[‐FLT:7] supporta anche queste curve (e.g4).

Implementazione della crittografia in JavaScript: una guida pratica

Qui di seguito sono riportati esempi completi, copia-e-paste sia per il browser che per Node.js. Preferisci sempre il modulo Web Crypto API o Node.js su librerie di terze parti quando possibile, come sono controllati e eseguire operazioni crittografiche nella memoria sicura del sistema.

Browser: Crittografia simmetrica con AES‐GCM (Web Crypto API)

// Encrypt a message with a given password
async function encryptWithPassword(password, plaintext) {
 // Derive a key from the password using PBKDF2
 const encoder = new TextEncoder();
 const salt = crypto.getRandomValues(new Uint8Array(16));
 const keyMaterial = await crypto.subtle.importKey(
 'raw',
 encoder.encode(password),
 'PBKDF2',
 false,
 ['deriveKey']
 );
 const key = await crypto.subtle.deriveKey(
 {
 name: 'PBKDF2',
 salt,
 iterations: 600000, // OWASP recommended min
 hash: 'SHA-256'
 },
 keyMaterial,
 { name: 'AES-GCM', length: 256 },
 false,
 ['encrypt']
 );

 // Encrypt
 const iv = crypto.getRandomValues(new Uint8Array(12));
 const encrypted = await crypto.subtle.encrypt(
 { name: 'AES-GCM', iv },
 key,
 encoder.encode(plaintext)
 );

 // Return salt, iv, ciphertext as base64 for transport
 return {
 salt: btoa(String.fromCharCode(...salt)),
 iv: btoa(String.fromCharCode(...iv)),
 ciphertext: btoa(String.fromCharCode(...new Uint8Array(encrypted)))
 };
}

// Decrypt
async function decryptWithPassword(password, { salt, iv, ciphertext }) {
 const encoder = new TextEncoder();
 const decoder = new TextDecoder();
 const saltBytes = Uint8Array.from(atob(salt), c => c.charCodeAt(0));
 const ivBytes = Uint8Array.from(atob(iv), c => c.charCodeAt(0));
 const data = Uint8Array.from(atob(ciphertext), c => c.charCodeAt(0));

 const keyMaterial = await crypto.subtle.importKey(
 'raw',
 encoder.encode(password),
 'PBKDF2',
 false,
 ['deriveKey']
 );
 const key = await crypto.subtle.deriveKey(
 {
 name: 'PBKDF2',
 salt: saltBytes,
 iterations: 600000,
 hash: 'SHA-256'
 },
 keyMaterial,
 { name: 'AES-GCM', length: 256 },
 false,
 ['decrypt']
 );

 const decrypted = await crypto.subtle.decrypt(
 { name: 'AES-GCM', iv: ivBytes },
 key,
 data
 );
 return decoder.decode(decrypted);
}

Node.js: AES‐256‐GCM con il modulo di crittografia

const crypto = require('crypto');

function encrypt(text, key) {
 const iv = crypto.randomBytes(12);
 const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
 let encrypted = cipher.update(text, 'utf8', 'hex');
 encrypted += cipher.final('hex');
 const authTag = cipher.getAuthTag().toString('hex');
 return { iv: iv.toString('hex'), encrypted, authTag };
}

function decrypt(encryptedData, key) {
 const decipher = crypto.createDecipheriv(
 'aes-256-gcm',
 key,
 Buffer.from(encryptedData.iv, 'hex')
 );
 decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'));
 let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
 decrypted += decipher.final('utf8');
 return decrypted;
}

// Usage: generate a 256‑bit key
const key = crypto.randomBytes(32);
const message = 'This is secret.';
const ciphertext = encrypt(message, key);
console.log(ciphertext);
console.log('Decrypted:', decrypt(ciphertext, key));

Migliori Pratiche di Comunicazione Sicuro

La crittografia da sola non garantisce una comunicazione sicura. Le seguenti pratiche devono essere integrate nell'intero flusso di dati:

1. Utilizzare sempre HTTPS con TLS 1.2 o superiore

HTTPS non è facoltativo. Crittografa l’intera conversazione HTTP, prevenendo attacchi di intercettazione e di dominio. Configura il server da usare TLS 1.2] o TLS 1.3] e disabilitare le suite di cifratura deboli. Servizi come

2. Gestione chiave corretta di implementazione

  • Utilizzare variabili di ambiente o volte sicure (ad esempio, HashiCorp Vault, AWS Secrets Manager) sul server.
  • Ruotare le chiavi regolarmente e immediatamente revocare le chiavi compromesse.
  • Utilizzare le funzioni di derivazione chiave (PBKDF2, scrypt, Argon2) per la crittografia basata su password.
  • Quando si scambiano le chiavi, utilizzare un protocollo di accordo chiave sicuro come ECDH o RSA‐OAEP, e combinare con una fase di conferma chiave.

3. Autenticazione e autorizzazione

La crittografia protegge i dati in transito, ma il sistema deve anche verificare l'identità. Utilizzare l'autenticazione basata su gettoni (JWT, OAuth 2.0) e garantire che i gettoni vengano trasmessi solo su HTTPS. Per le API, richiedono un accesso valido per ogni richiesta e implementano le vite a token corto con gettoni di aggiornamento.

4. Proteggere contro attacchi comuni Web

  • Cross‐Site Scripting (XSS) – Gli attaccanti possono iniettare script che rubano dati o chiavi crittografati.
  • Cross-Site Request Forgery (CSRF)[ – Utilizzare i token anti-CSRF o i cookie SameSite per evitare richieste forgiate.
  • Clickjacking[] – Impostare l'intestazione ] o .
  • In-the-Middle[[] – Enforce HSTS (HTTP Strict Transport Security) e considerare la pinning del certificato per applicazioni di alta sicurezza.

5. Convalida tutti gli ingressi e utilizza Confronti a tempo costante

Quando si confrontano HMAC, tag di autenticazione o password, utilizzare sempre una funzione di confronto costante-tempo per prevenire gli attacchi di temporizzazione. Node.js fornisce ; per il browser, è possibile implementare un confronto costante-tempo o utilizzare una libreria come .

Argomenti avanzati: Firme digitali, Crittografia ibrida e Avviso di attesa

Firme digitali

Le firme digitali forniscono non-repudiazione e dimostrano l'autenticità di un messaggio. Utilizzare ECDSA (con curva P‐256) o RSA‐PSS. Nel browser, il Web Crypto API supporta e operazioni. Esempio snippet (browser):

// Generate signing key pair
const keyPair = await crypto.subtle.generateKey(
 { name: 'ECDSA', namedCurve: 'P-256' },
 true,
 ['sign', 'verify']
);

// Sign a message
const data = new TextEncoder().encode('Important contract');
const signature = await crypto.subtle.sign(
 { name: 'ECDSA', hash: 'SHA-256' },
 keyPair.privateKey,
 data
);

Crittografia ibrida per grandi carichi di pagamento

La crittografia asimmetrica non può gestire in modo efficiente i grandi dati. L'approccio standard è la crittografia ibrida:

  1. Generare una chiave simmetrica casuale (ad esempio AES a 256 bit).
  2. Crittografare la chiave simmetrica con la chiave RSA pubblica del destinatario (RSA‐OAEP).
  3. Crittografare il messaggio reale con AES‐GCM utilizzando la chiave simmetrica.
  4. Trasmettere sia la chiave simmetrica crittografata che il testo cifrato (con IV e tag auth).

Il destinatario decifra la chiave simmetrica con la chiave privata, quindi decifra il messaggio con AES‐GCM.

Certificato di pinza

Per proteggere ulteriormente dalle autorità di certificazione compromessa, è possibile pernottare il certificato di server previsto o il relativo hash chiave pubblica. Questo può essere fatto utilizzando l'intestazione [[ (deprecato ma ancora utilizzato in alcuni contesti) o tramite l'intestazione [] (Certificate Transparency).

Pitfalls comune e come evitare di loro

  • Utilizzando un IV debole o un nonce[] – Il IV deve essere casuale e unico per ogni operazione di crittografia con la stessa chiave.
  • Implementazione della crittografia personalizzata[[] – Non scrivere mai il tuo algoritmo di crittografia.
  • Clicca distorsione in cookie o storage locale[[] – Lo storage del browser è accessibile a JavaScript e quindi agli attacchi XSS. Per la crittografia lato client, considerare l'utilizzo della Web Crypto API per generare chiavi che non sono mai esposte alla memoria JavaScript.
  • Autenticazione di autenticazione[] – Crittografia senza un tag di autenticazione (ad esempio, utilizzando AES‐CBC senza HMAC) consente agli aggressori di modificare il testo cifrato non registrato.
  • Ignorando attacchi side-channel[[] – In ambienti ad alta sicurezza, essere consapevoli degli attacchi di tempismo, cache e analisi di potenza.

Conclusioni

JavaScript è pienamente in grado di implementare una crittografia robusta e di garantire la comunicazione quando utilizzato correttamente. Il modulo Web Crypto API e Node.js fornisce funzioni crittografiche di livello di produzione, di base che seguono gli standard del settore. Combinando la crittografia simmetrica e asimmetrica con le migliori pratiche come HTTPS, la gestione delle chiavi corretta, i confronti di tempo costante e gli aggiornamenti di input, è possibile costruire applicazioni web che proteggono i dati degli utenti