Vai al contenuto
HexaTransfer
Torna al blog
Crittografia e sicurezza

Funzioni di derivazione chiave: PBKDF2, Argon2 e scrypt

Confronta le funzioni di derivazione per crittografia basata su password. Punti di forza di PBKDF2, Argon2 e scrypt.

Per le chiavi di cifratura derivate da password nel 2026, Argon2id è la scelta raccomandata (vincitore del PHC, preferito di OWASP, difende attivamente contro attaccanti GPU e ASIC), scrypt è un'ottima alternativa (memory-hard, ampiamente usato nelle criptovalute), e PBKDF2-SHA-256 a 600.000+ iterazioni rimane accettabile per compatibilità ma offre una resistenza GPU minima. Per le app di trasferimento file in cui gli utenti inseriscono una password per proteggere un file condiviso, Argon2id con 3 iterazioni, 64 MiB di memoria e parallelismo 4 è la baseline moderna. PBKDF2 sopravvive perché è integrato nella Web Crypto API e non richiede dipendenze WASM. Ecco come differiscono le tre funzioni e quando ciascuna ha senso.

Tabella di confronto

| Proprietà | PBKDF2 | scrypt | Argon2id | |---|---|---|---| | Anno di introduzione | 2000 (RFC 2898) | 2009 (RFC 7914) | 2015 (vincitore PHC) | | Memory-hard | No | Sì | Sì | | Flessibilità dei parametri | Solo iterazioni | N, r, p | tempo, memoria, parallelismo | | Resistenza GPU | Debole | Moderata | Forte | | Resistenza ASIC | Molto debole | Moderata | Forte | | Nativa nel browser (Web Crypto) | Sì | No | No | | Raccomandazione OWASP 2024 | Fallback accettabile | Accettabile | Preferita | | Costo tipico nel browser (hardware moderno) | 600.000 iter = ~500 ms | N=2^17 = ~800 ms | 3 iter, 64 MiB = ~1 s |

Perché la memory-hardness conta

Il modello di minaccia per la cifratura basata su password è il brute force offline. Un attaccante si impossessa del ciphertext più il salt, esegue un dizionario di password e prova a derivare una chiave che decifra correttamente. La difesa consiste nel rendere ogni tentativo costoso.

PBKDF2 rende ogni tentativo costoso solo in termini di CPU (iterazioni SHA-256). Le GPU moderne eseguono miliardi di operazioni SHA-256 al secondo; una GPU da gaming può testare 10–100 milioni di tentativi PBKDF2-SHA-256-600000 al giorno. Gli attaccanti con ASIC fanno ordini di grandezza di meglio.

Le funzioni memory-hard (scrypt, Argon2) richiedono una quantità fissa di memoria per ogni tentativo. GPU e ASIC hanno larghezza di banda di memoria limitata, quindi il parallelismo per dispositivo è delimitato. Un requisito di 64 MiB di memoria significa che una GPU con 16 GB di VRAM può eseguire al massimo 256 tentativi paralleli, non milioni. Il costo economico del brute force aumenta di 2–3 ordini di grandezza.

PBKDF2: il default legacy

PBKDF2 (RFC 2898) itera una funzione pseudocasuale, tipicamente HMAC-SHA-256 o HMAC-SHA-512, su password e salt. Il conteggio delle iterazioni è l'unico parametro regolabile.

const passwordKey = await crypto.subtle.importKey(
  "raw", new TextEncoder().encode(password),
  "PBKDF2", false, ["deriveKey"]
);
const aesKey = await crypto.subtle.deriveKey(
  {
    name: "PBKDF2",
    salt,  // 16 byte casuali
    iterations: 600000,
    hash: "SHA-256",
  },
  passwordKey,
  { name: "AES-GCM", length: 256 },
  false,
  ["encrypt", "decrypt"]
);

OWASP 2023 raccomanda un minimo di 600.000 iterazioni di PBKDF2-SHA-256. NIST SP 800-132 è più datato e meno specifico. Alcuni default di prodotto (es. il default 100.100 di LastPass del 2018) sono considerati troppo bassi nel 2026.

Vantaggi: integrato in Web Crypto, nessun WASM, validato FIPS, supportato nella ripresa di sessione TLS 1.3, funziona in modo identico su Node e browser.

Limitazioni: nessuna memory-hardness, vulnerabile all'accelerazione GPU e ASIC. Raddoppiare le iterazioni raddoppia il costo dell'attaccante ma anche quello dell'utente legittimo. A un certo punto gli utenti legittimi si rifiutano di aspettare e si deve limitare le iterazioni.

scrypt: il primo deployment memory-hard

scrypt (RFC 7914) è stato inventato da Colin Percival nel 2009 per Tarsnap. Mescola il materiale della password attraverso un grande buffer di memoria, costringendo l'attaccante a mantenere quel buffer durante ogni tentativo.

Tre parametri:

  • N: fattore di costo (tipicamente 2^14 a 2^20). L'uso di memoria è circa 128 × N × r byte.
  • r: dimensione del blocco (tipicamente 8). Influisce sulla memoria e sul conteggio delle iterazioni GHASH.
  • p: parallelizzazione (tipicamente 1). Valori più alti accelerano il calcolo legittimo ma anche quello degli attaccanti; di solito lasciato a 1.

OWASP raccomanda N=2^17, r=8, p=1 come baseline, che consuma ~128 MiB e gira in circa 800 ms su hardware moderno.

scrypt non è nella Web Crypto API. In JavaScript, usa scrypt-js, @noble/hashes o libsodium.js. Litecoin e Dogecoin usano scrypt come proof-of-work, il che ha incentivato lo sviluppo di ASIC specifici per scrypt, erodendo in parte il vantaggio asimmetrico originale.

Argon2id: il default del 2026

Argon2 ha vinto il Password Hashing Competition nel 2015. Tre varianti: Argon2d (più veloce, data-dipendente, vulnerabile ai side-channel), Argon2i (data-indipendente, più lento), Argon2id (ibrido, raccomandato per la maggior parte degli usi). RFC 9106 lo ha standardizzato nel 2021.

Tre parametri:

  • t (tempo): iterazioni attraverso la memoria. Tipicamente 2–3.
  • m (memoria): memoria in KiB. Tipicamente 65536 (64 MiB) o superiore.
  • p (parallelismo): grado di parallelismo. Tipicamente 1–4.

Baseline OWASP 2024: t=2, m=19456 (19 MiB), p=1 per il minimo, e t=3, m=65536 (64 MiB), p=4 per protezione più forte.

import { argon2id } from '@noble/hashes/argon2';
import { utf8ToBytes } from '@noble/hashes/utils';

const derivedKey = argon2id(utf8ToBytes(password), salt, {
  t: 3, m: 65536, p: 4, dkLen: 32
});

Oppure via argon2-browser (WASM):

import argon2 from 'argon2-browser';
const hash = await argon2.hash({
  pass: password, salt,
  type: argon2.ArgonType.Argon2id,
  time: 3, mem: 65536, parallelism: 4, hashLen: 32
});

Argon2id sconfigge gli attaccanti GPU più efficacemente di scrypt perché il suo pattern di accesso alla memoria è meno adatto ai design di memoria bulk. Gli ASIC per Argon2 esistono nella ricerca ma non sono ancora dispiegati economicamente su scala di attacco.

Scegliere i parametri per la tua app

Il metodo di calibrazione: scegli il tempo di attesa massimo che i tuoi utenti tollerano (di solito 500 ms a 2 secondi), misura sul dispositivo più lento target e imposta i parametri per rientrare in quel budget.

Per condivisioni file protette da password in stile HexaTransfer, dove la derivazione avviene una volta al caricamento e una volta al download, 1–2 secondi è accettabile. Parametri:

  • PBKDF2-SHA-256: 600.000–1.200.000 iterazioni
  • scrypt: N=2^17, r=8, p=1
  • Argon2id: t=3, m=65536, p=4

Per i sistemi di login dove l'utente aspetta dopo aver digitato la password, 300–500 ms è il limite UX. I parametri si dimezzano circa. Per scenari batch in cui l'utente non aspetta (es. ri-cifratura in background), puoi spingere fino a 3–5 secondi.

Gestione del salt

Tutte e tre le KDF hanno bisogno di un salt. Regole:

  • Minimo 16 byte casuali
  • Generato via crypto.getRandomValues(), mai Math.random()
  • Unico per ogni password (se Alice e Bob usano la stessa password, i loro salt devono essere diversi così le chiavi derivate differiscono)
  • Non segreto — conservalo insieme al ciphertext

Il pepper (un segreto aggiunto a tutte le derivazioni) viene a volte discusso. Per il trasferimento file in cui il "server" è un semplice blob store, il pepper non aggiunge valore perché non c'è nessun segreto lato server. Per i sistemi basati su account, un pepper lato server conservato separatamente dal database delle password rende i dump del database meno utili agli attaccanti.

Migrazione tra KDF

Se hai un deployment esistente su PBKDF2 e vuoi passare ad Argon2id:

  • Conserva l'identificatore della KDF nei metadati del ciphertext ("kdf": "pbkdf2-sha256-600000" o "kdf": "argon2id-3-65536-4")
  • Per i nuovi upload, usa Argon2id
  • Alla decrittografia, leggi l'identificatore della KDF e usa la funzione corrispondente
  • Non aggiornare ciecamente i vecchi ciphertext; avresti bisogno della password per ri-derivare

LastPass, 1Password e Bitwarden hanno tutti attraversato questa migrazione. Tutti e tre ora usano PBKDF2 a 600.000+ iterazioni come default, con Argon2id disponibile nelle versioni più recenti. Sì, anche i password manager sono stati lenti ad adottare Argon2 — si sta diffondendo gradualmente perché l'ecosistema degli strumenti ha impiegato tempo a maturare.

Cosa succede con bcrypt?

bcrypt (1999) è una buona funzione di hashing delle password con una modesta memory-hardness. Tronca l'input a 72 byte (un famoso comportamento problematico — le password lunghe vengono troncate silenziosamente prima dei round 2011). È il default storico in Ruby on Rails e molti framework PHP. Per nuovo codice nel 2026, preferisci Argon2id; bcrypt va bene per mantenere sistemi esistenti.

La raccomandazione realistica

Per un nuovo servizio di trasferimento file nel 2026:

  • Se puoi includere una dipendenza da 15–200 KB: Argon2id via @noble/hashes o libsodium.js
  • Se hai bisogno di zero dipendenze e massima riduzione del bundle: PBKDF2-SHA-256 a 600.000 iterazioni via Web Crypto
  • Se stai scrivendo un wallet di criptovalute o qualcosa che eredita scrypt legacy: scrypt con N=2^17

Per le app di produzione che gestiscono file sensibili, Argon2id vale la dipendenza WASM. Per semplici condivisioni protette da password in cui gli utenti generano comunque una password casuale, PBKDF2 è adeguato perché l'entropia è nella password, non nella KDF.

Provalo su hexatransfer.com — gratis, senza registrazione, fino a 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