Web Crypto API: tutorial completo per crittografare file
Padroneggia la Web Crypto API per la crittografia dei file nel browser. AES-GCM, RSA-OAEP e gestione delle chiavi nelle applicazioni JavaScript.
La Web Crypto API permette di cifrare i file direttamente nel browser usando i metodi nativi window.crypto.subtle, senza librerie esterne. Per la cifratura dei file, si deriverà tipicamente una chiave da una password tramite PBKDF2 (210.000 iterazioni, SHA-256), poi si cifrano i byte del file con AES-GCM usando un IV a 96 bit e un auth tag a 128 bit. I flussi di lavoro con chiave pubblica usano RSA-OAEP con chiavi a 4096 bit per il wrapping della chiave simmetrica. Il GDPR e il Garante per la protezione dei dati personali richiedono la pseudonimizzazione o la cifratura dei dati personali dove applicabile: AES-256-GCM lato browser soddisfa il requisito dell'articolo 32 GDPR sulle misure tecniche adeguate, poiché il server non vede mai il plaintext.
Perché SubtleCrypto Batte le Librerie Pure-JS
window.crypto.subtle chiama nel backend crittografico nativo auditato del browser, di solito BoringSSL in Chromium o CommonCrypto su Safari. Rispetto alle opzioni pure-JS come CryptoJS o sjcl, SubtleCrypto gira 30-80x più velocemente per AES-GCM, evita i side-channel di timing negli interpreti JavaScript e spedisce zero byte agli utenti. Il compromesso è un'API basata su Promise che opera solo su oggetti ArrayBuffer e CryptoKey, quindi si passa molto tempo a mescolare tra Uint8Array, Blob e ReadableStream. Per dimensioni di file superiori a 100 MB, quella meccanica conta più della velocità crittografica grezza.
Derivare una Chiave da una Password con PBKDF2
Non usare mai direttamente una password come chiave AES. Importare invece la password come materiale grezzo, poi derivare una chiave a 256 bit:
async function deriveKey(password, salt) {
const enc = new TextEncoder();
const material = await crypto.subtle.importKey(
'raw', enc.encode(password), 'PBKDF2', false, ['deriveKey']
);
return crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 210000, hash: 'SHA-256' },
material,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
La guida OWASP 2026 chiede almeno 600.000 iterazioni con SHA-256, sebbene 210.000 rimanga accettabile per contesti a basso rischio. Generare un sale fresco a 16 byte per file con crypto.getRandomValues e memorizzarlo accanto al ciphertext. Argon2id sarebbe più forte ma non è ancora esposto da SubtleCrypto.
Cifrare un File con AES-GCM
AES-GCM fornisce riservatezza e autenticità in un unico passaggio. La regola critica è non riusare mai una coppia (chiave, IV):
async function encryptFile(file, key) {
const iv = crypto.getRandomValues(new Uint8Array(12));
const plaintext = await file.arrayBuffer();
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv, tagLength: 128 },
key,
plaintext
);
return { iv, ciphertext };
}
Per un file da 2 GB, file.arrayBuffer() allocherà l'intero buffer, che spesso crasha Safari mobile. Dividere il file in chunk da 4 MB, cifrare ciascuno con un IV univoco derivato da un contatore concatenato con un prefisso casuale e anteporre un byte di versione e il sale così il decifratore sa con cosa ha a che fare.
Streaming di File di Grandi Dimensioni tramite TransformStream
Per evitare il sovraccarico di memoria, avvolgere la cifratura in un TransformStream e far passare il file attraverso:
const chunkSize = 4 * 1024 * 1024;
const encryptor = new TransformStream({
async transform(chunk, controller) {
const iv = nextIV(counter++);
const ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, chunk);
controller.enqueue(new Uint8Array([...iv, ...new Uint8Array(ct)]));
}
});
await file.stream()
.pipeThrough(sliceByChunks(chunkSize))
.pipeThrough(encryptor)
.pipeTo(uploadSink);
file.stream() restituisce un ReadableStream<Uint8Array> che legge dal disco pigramente. Lo slicer produce chunk di dimensioni fisse così i tag GCM si allineano in modo prevedibile. La memoria di picco rimane sotto i 20 MB anche per un upload da 10 GB.
Wrapping della Chiave Simmetrica con RSA-OAEP
Quando è necessario condividere un file con un destinatario specifico, generare la loro coppia di chiavi RSA una volta e pubblicare la chiave pubblica:
const keypair = await crypto.subtle.generateKey(
{ name: 'RSA-OAEP', modulusLength: 4096,
publicExponent: new Uint8Array([1,0,1]), hash: 'SHA-256' },
true, ['wrapKey', 'unwrapKey']
);
Generare una chiave AES-GCM per il file, poi avvolgerla:
const wrapped = await crypto.subtle.wrapKey(
'raw', fileKey, keypair.publicKey,
{ name: 'RSA-OAEP' }
);
Le chiavi RSA a 4096 bit forniscono circa 150 bit di sicurezza fino al 2030 secondo NIST SP 800-57. Se serve la forward secrecy o la resistenza post-quantum, abbinare RSA-OAEP con ECDH su P-384 o migrare a ML-KEM (Kyber) una volta che il working group WebCrypto lo implementa.
Memorizzare le Chiavi in Sicurezza in IndexedDB
Gli oggetti CryptoKey sono non-estraibili per impostazione predefinita, il che significa che si possono persistere in IndexedDB senza mai esporre i byte grezzi a JavaScript:
const db = await openDB('keystore', 1);
await db.put('keys', keypair.privateKey, 'user-signing-key');
I browser serializzano la chiave usando l'algoritmo di clone strutturato e mantengono i byte effettivi nel backend crittografico. Uno script compromesso può chiamare encrypt o decrypt con la chiave memorizzata ma non può leggerne il materiale. Questo è un passo di hardening significativo rispetto al stuffare chiavi base64 in localStorage.
Gestire gli Errori che l'API Lancia
SubtleCrypto lancia OperationError per i fallimenti di decifratura autenticata, che di solito significa che il ciphertext è stato manomesso, l'IV è sbagliato o l'utente ha digitato la password sbagliata. Lancia DataError quando il buffer di input ha la lunghezza sbagliata, NotSupportedError quando l'algoritmo non è implementato e InvalidAccessError quando la chiave non è stata importata con i flag di utilizzo corretti. Avvolgere sempre la decifratura in try/catch, mostrare un neutro "il file non può essere decifrato" e evitare di rivelare se il tag o la struttura ha fallito.
HexaTransfer in Pratica
HexaTransfer usa esattamente questa pipeline AES-GCM più PBKDF2 tramite crittografia end-to-end per ogni upload, con chiavi derivate in un Worker e ciphertext in streaming allo storage senza che il server veda mai il plaintext. Un flusso minimale di upload cifrato: generare sale e IV con getRandomValues, derivare una chiave AES-GCM dalla password dell'utente tramite PBKDF2, far passare il file attraverso un TransformStream che cifra ogni chunk da 4 MB, anteporre un piccolo header contenente versione, sale e conteggio dei chunk, e fare POST del risultato al server. In download, invertire il processo chunk per chunk, intercettando OperationError come segnale di password errata o corruzione.
Prova su https://hexatransfer.com — gratuito, senza account, massimo 10 GB.
Invia file di grandi dimensioni in modo sicuro con crittografia end-to-end
Trasferisci file fino a 10 GB gratuitamente con crittografia end-to-end. Nessun account necessario. I tuoi file vengono crittografati nel browser prima del caricamento — nessun altro può leggerli.
Invia un file