Vai al contenuto
HexaTransfer
Torna al blog
Crittografia e sicurezza

Guida Web Crypto API: crittografia nativa del browser per sviluppatori

Padroneggia la Web Crypto API per applicazioni di trasferimento crittografato. Guida completa a AES-GCM, RSA-OAEP e gestione chiavi.

Aggiornato il

Pubblicato da HexaTransfer, servizio di Gaprod — Groupe Dwenola SAS. Queste guide presentano il nostro servizio e altri prodotti. Informazioni sull’editore

La Web Crypto API (specificata nella raccomandazione W3C Web Cryptography API, esposta tramite window.crypto.subtle) è il modo nativo del browser per eseguire crittografia senza dover caricare una libreria crypto dalla rete. Supporta AES-GCM, AES-CBC, AES-CTR, AES-KW, HMAC, RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5, ECDH, ECDSA, HKDF e PBKDF2 su tutti i browser moderni (Chrome 37+, Firefox 34+, Safari 10.1+, Edge 79+). Per le applicazioni di trasferimento file questo è rilevante perché ogni byte di testo cifrato può essere generato lato client prima del caricamento, con il browser che fornisce un'implementazione auditata e a tempo costante. Questa guida tratta le primitive che contano per il trasferimento file crittografato e le insidie che fanno inciampare ogni prima implementazione.

SubtleCrypto è basato su Promise ed è asincrono

Ogni metodo su crypto.subtle restituisce una Promise. È intenzionale: le operazioni crittografiche possono essere scaricate su hardware o thread in background, quindi forzare l'asincronia impedisce che l'API venga usata in modi che bloccherebbero il thread principale. Forma del codice:

const key = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  true, // extractable
  ["encrypt", "decrypt"]
);

Il secondo argomento (true) contrassegna la chiave come estraibile, il che significa che può essere successivamente esportata tramite crypto.subtle.exportKey(). Per le chiavi a lungo termine, impostalo su false per mantenere i byte grezzi irraggiungibili da JavaScript. Per le chiavi che devi serializzare in un frammento URL (il pattern HexaTransfer), impostalo su true.

Il terzo argomento è un array di utilizzi della chiave. Una chiave generata con ["encrypt"] non può essere usata per decifrare, anche se AES-GCM è simmetrico. Questa separazione impedisce che un flusso di crittografia compromesso venga abusato per decifrare dati storici.

AES-GCM per la crittografia simmetrica dei file

AES-GCM è il motore di lavoro per i contenuti dei file. Fornisce crittografia autenticata con dati associati (AEAD): testo cifrato più tag di autenticazione più dati associati opzionali che vengono autenticati ma non crittografati. Per il trasferimento file, usa una chiave a 256 bit e un nonce a 96 bit per NIST SP 800-38D.

const iv = crypto.getRandomValues(new Uint8Array(12)); // nonce a 96 bit
const ciphertext = await crypto.subtle.encrypt(
  { name: "AES-GCM", iv },
  key,
  plaintext
);

L'output include un tag di autenticazione GCM a 128 bit aggiunto al testo cifrato. La decifratura verifica automaticamente il tag e lancia un errore se non corrisponde. Non riutilizzare mai un nonce con la stessa chiave; la sicurezza di GCM crolla catastroficamente sul riutilizzo del nonce (gli aggressori possono recuperare la chiave di autenticazione). Per il trasferimento file dove ogni file ottiene una nuova chiave, i nonce casuali sono sicuri; per le chiavi a lungo termine, usa un contatore.

PBKDF2 per le chiavi derivate da password

Quando gli utenti digitano una password per proteggere un file, non puoi usare la password direttamente come chiave AES. Eseguila prima attraverso PBKDF2:

const passwordKey = await crypto.subtle.importKey(
  "raw",
  new TextEncoder().encode(password),
  "PBKDF2",
  false,
  ["deriveKey"]
);

const aesKey = await crypto.subtle.deriveKey(
  {
    name: "PBKDF2",
    salt: crypto.getRandomValues(new Uint8Array(16)),
    iterations: 600000,
    hash: "SHA-256",
  },
  passwordKey,
  { name: "AES-GCM", length: 256 },
  false,
  ["encrypt", "decrypt"]
);

Le linee guida OWASP 2023 sull'hashing delle password raccomandano 600.000 iterazioni per PBKDF2-SHA-256. Qualsiasi valore inferiore a 310.000 è al di sotto delle best practice attuali. Il salt deve essere casuale e memorizzato insieme al testo cifrato (non è segreto, deve solo essere unico).

Per il nuovo codice nel 2026, considera Argon2id invece di PBKDF2. Argon2 non è ancora nella Web Crypto API, ma librerie come argon2-browser o @noble/hashes forniscono implementazioni JavaScript/WASM. Argon2id resiste agli attacchi GPU molto meglio di PBKDF2.

RSA-OAEP per il wrapping delle chiavi

Per gli scenari in cui vuoi crittografare la chiave AES di un file con la chiave pubblica di un destinatario, usa RSA-OAEP. Genera le chiavi:

const keyPair = await crypto.subtle.generateKey(
  {
    name: "RSA-OAEP",
    modulusLength: 4096,
    publicExponent: new Uint8Array([1, 0, 1]), // 65537
    hash: "SHA-256",
  },
  true,
  ["encrypt", "decrypt"]
);

Usa modulusLength 4096 per le nuove chiavi; 2048 è accettabile ma inizierà a essere deprecato man mano che le linee temporali quantistiche si consolidano. RSA-OAEP crittografa solo payload piccoli (al massimo modulusLength/8 - 2*hashLength - 2 byte), quindi esegui il wrapping di una chiave AES a 256 bit piuttosto che crittografare direttamente i contenuti del file.

Per le app sensibili alle prestazioni, ECDH con P-256 o P-384 è un'alternativa migliore a RSA. La generazione delle chiavi è un ordine di grandezza più veloce e le dimensioni delle chiavi sono molto più piccole.

Streaming per file di grandi dimensioni

Un file da 2 GB non si adatta comodamente in un ArrayBuffer del browser. Chrome, Firefox e Safari permettono tutti di leggere i file con File.stream() che restituisce un ReadableStream, poi elaborarli a blocchi. La Web Crypto API stessa non ha ancora metodi di encrypt/decrypt in streaming (è una lacuna nella specifica), quindi due alternative:

  1. Suddividi in blocchi (64 KB o 1 MB) e crittografa ciascuno con un nonce univoco. Il destinatario li concatena in ordine. Questo perde il vero AEAD sull'intero file ma funziona per la maggior parte dei casi.
  2. Usa una libreria WASM per la crittografia (libsodium.js, @noble/ciphers con backend WASM) che supporta modalità AEAD in streaming come XChaCha20-Poly1305 o AES-GCM-SIV.

Per i trasferimenti sotto poche centinaia di megabyte, AES-GCM bufferizzato funziona bene ed è molto più semplice. Sopra quella soglia, lo streaming diventa necessario per evitare pressione sulla memoria.

Esportazione, importazione delle chiavi e frammenti URL

Per i flussi stile HexaTransfer dove la chiave viaggia nel frammento URL:

const rawKey = await crypto.subtle.exportKey("raw", aesKey);
const keyBase64 = btoa(String.fromCharCode(...new Uint8Array(rawKey)));
// URL di condivisione come https://example.com/file/abc123#key=keyBase64

I frammenti URL non vengono mai inviati ai server nelle richieste HTTP (il browser li rimuove). Questo mantiene la chiave lato client anche se l'utente condivide un link. Sul lato del destinatario:

const keyBase64 = window.location.hash.slice(5); // rimuovi "#key="
const rawKey = Uint8Array.from(atob(keyBase64), c => c.charCodeAt(0));
const key = await crypto.subtle.importKey(
  "raw", rawKey, "AES-GCM", false, ["decrypt"]
);

Usa la codifica base64url (sostituisci + con -, / con _, rimuovi il padding) per evitare problemi di codifica URL.

Insidie comuni

Usare Math.random() per salt o IV. Math.random() non è crittograficamente sicuro. Usa sempre crypto.getRandomValues().

Riutilizzare gli IV con la stessa chiave. Le proprietà di sicurezza di GCM si rompono completamente sul riutilizzo del nonce. I nonce casuali a 96 bit collidono dopo ~2^48 crittografie con la stessa chiave (birthday bound). Per il trasferimento file dove ogni file ha la sua chiave, è sicuro; per le chiavi a lungo termine, usa un contatore.

Dimenticare HTTPS. crypto.subtle è disponibile solo in contesti sicuri (HTTPS o localhost). Su un'origine non sicura, crypto.subtle è indefinito.

Memorizzare chiavi estraibili in IndexedDB non protetto. Se hai bisogno di persistere le chiavi, esegui il loro wrapping (es. con una chiave derivata da una passphrase) prima di memorizzarle. Non memorizzare mai chiavi AES grezze in localStorage, che è accessibile a qualsiasi script sull'origine.

Fidarsi delle password degli utenti senza PBKDF2. Una password grezza convertita in byte UTF-8 non è una chiave a 256 bit. Deriva sempre.

Non verificare i tag di autenticazione. crypto.subtle.decrypt() lo fa automaticamente per AES-GCM, ma se implementi protocolli personalizzati sopra di esso, non saltare il controllo.

Sfumature di supporto del browser

Tutti i principali browser supportano Web Crypto su HTTPS. Alcune particolarità:

  • Il PBKDF2 di Safari era più lento di Chrome/Firefox per anni; il divario si è chiuso in Safari 15.
  • Firefox applica una validazione degli input più rigida; il codice che funziona in Chrome potrebbe lanciare OperationError in Firefox. Testa in entrambi.
  • Web Crypto nei service worker funziona ma richiede che l'ambito di registrazione sia HTTPS.
  • Node.js fornisce require("crypto").webcrypto con un'API compatibile da Node 15, utile per codice crittografico isomorfo.

Quando usare una libreria invece

HexaTransfer genera con Web Crypto una chiave AES-256-GCM casuale, non derivata dalla password opzionale. Il frammento dopo # viene separato prima della richiesta HTTP, come descrive RFC 3986. La password è un controllo di accesso separato, conservato sul server come hash. AES-GCM include l’autenticazione; il formato non aggiunge un HMAC separato.

Per un flusso di trasferimento crittografato completo, Web Crypto ti porta lì in meno di 100 righe di codice: genera chiave AES, deriva o casuale, crittografa il file, carica il testo cifrato, condividi il link con la chiave nel frammento, il destinatario importa la chiave e decifra. Questo è tutto.

Provalo su hexatransfer.com — gratis, senza registrazione, fino a 50 GB.

Invia file di grandi dimensioni in modo sicuro con crittografia end-to-end

Invia fino a 50 GiB gratis con crittografia end-to-end, senza account né email. Scegli da 1 a 30 giorni; 14 per impostazione predefinita.

Invia un file