Ogni divulgazione di violazione dei dati segue lo stesso arco: “Prendiamo la sicurezza sul serio.” Poi leggi i dettagli e scopri che le password erano memorizzate in testo puro, o hashate con MD5, o crittografate con una chiave che si trovava nello stesso database. Il problema dell’archiviazione delle password è risolto — gli algoritmi esistono, le librerie esistono, la documentazione esiste — eppure gli sviluppatori continuano a farlo male.
Questo post copre cosa memorizzare, cosa non memorizzare, la differenza tra hashing e crittografia e perché SHA-256(password) non è un hash di password.
La regola: non memorizzare mai in testo puro
Se il tuo database viene compromesso e hai password in testo puro, ogni utente che riutilizza quella password su altri siti è ora compromesso. Non solo il tuo sito — la loro email, la loro banca, i loro social media. L’archiviazione di password in testo puro non è un problema di sicurezza. È un problema morale.
Memorizza un hash. Sempre.
Hashing vs crittografia
L’hashing è una trasformazione unidirezionale. Non puoi invertirlo. Dato hash(password), non puoi recuperare password. Puoi solo verificare: hashare l’input e confrontare.
La crittografia è una trasformazione bidirezionale. Dato encrypt(password, key), puoi recuperare password con la chiave. Se la chiave viene compromessa, ogni password è compromessa.
Hasha le password. Non le crittografare mai. Se devi recuperare il testo puro (non devi), stai risolvendo il problema sbagliato.
Perché SHA-256 non è sufficiente
Un errore comune: hash = SHA-256(password). È veloce, è ben noto ed è sbagliato per le password.
Il problema è la velocità. SHA-256 su una GPU moderna processa miliardi di hash al secondo. Un attaccante con un database di hash di password SHA-256 può provare ogni password comune, ogni parola del dizionario, ogni permutazione, in pochi minuti.
Gli algoritmi di hash delle password sono deliberatamente lenti. Usano lo stretching delle chiavi — migliaia di iterazioni di una funzione di hash — per rendere gli attacchi a forza bruta impraticabili:
- bcrypt — lo standard dal 1999. Fattore di costo regolabile. Password + sale → hash. Ogni linguaggio principale ha una libreria.
- scrypt — resistente alla memoria. Richiede grandi quantità di RAM per calcolare, rendendo gli attacchi GPU più difficili.
- Argon2 — vincitore della Password Hashing Competition (2015). Resistente alla memoria, tempo e costi della memoria configurabili. La best practice attuale.
- PBKDF2 — raccomandato dal NIST, ampiamente disponibile, usa iterazioni HMAC. Meno resistente alla memoria di Argon2 ma ben testato.
Usa bcrypt, scrypt o Argon2. Non inventare il tuo schema. Non hashare una volta con SHA-256 e chiamarla fatta.
Sale: perché ogni password ne ha bisogno di uno unico
Un sale sono dati casuali aggiunti alla password prima dell’hashing. Senza sale, due utenti con la stessa password producono lo stesso hash. Un attaccante può pre-calcolare hash per password comuni (una tabella arcobaleno) e confrontarli con il tuo database.
Con un sale unico per utente, ogni hash è diverso anche se le password sono identiche. Le tabelle arcobaleno non funzionano. L’attaccante deve forzare bruta ogni singola password.
Le funzioni moderne di hash delle password (bcrypt, Argon2) generano automaticamente un sale casuale. Non hai bisogno di gestire i sale separatamente. Passa la password alla funzione di hash, e lei si occupa del resto.
Cosa memorizzare
Per ogni utente, memorizza:
- L’hash della password (l’output di bcrypt/Argon2, che include il sale e i parametri di costo)
- L’algoritmo di hash (bcrypt, argon2, ecc.)
- I parametri di costo (round di bcrypt, memoria/tempo di Argon2)
NON memorizzare:
- La password in testo puro
- Il sale separatamente (l’output della funzione di hash lo include)
- Una “chiave di crittografia” che potrebbe decriptare la password
- Indizi sulla password (rivelano informazioni)
L’output di hash di bcrypt sembra: $2b$12$LJ3m4ys3GzF4VQ.YOUR.HASH.HERE. Il $2b$ identifica l’algoritmo, 12 è il fattore di costo, e il resto è sale + hash. Per verificare un tentativo di login, hasha la password inviata con gli stessi parametri e confronta.
Fattori di costo
Il fattore di costo di bcrypt determina la velocità dell’hash. Ogni incremento raddoppia il tempo di calcolo:
- Costo 10: ~10ms per hash (veloce per gli utenti, veloce per gli attaccanti)
- Costo 12: ~40ms per hash (ragionevole)
- Costo 14: ~160ms per hash (lento per gli attaccanti, percettibile per gli utenti)
- Costo 16: ~640ms per hash (troppo lento per la maggior parte dei flussi di login)
Il punto dolce nel 2026: bcrypt costo 12-14, Argon2 con 64MB+ di memoria e 3+ iterazioni. L’obiettivo è rendere ogni tentativo di hash abbastanza lento da rendere la forza bruta di miliardi di password un processo che richiede anni, ma abbastanza veloce perché un utente non noti un ritardo al login.
Limitazione della velocità
L’hashing da solo non è sufficiente. Se un attaccante può provare milioni di password al secondo contro il tuo endpoint di login, nemmeno bcrypt ti salverà.
Limita i tentativi di login. Dopo 5-10 tentativi falliti dallo stesso IP o account, blocca temporaneamente l’account o richiedi un CAPTCHA. Questo limita gli attacchi a forza bruta a un rivolo.
Non rivelare se il nome utente o la password era sbagliato. “Nome utente o password non validi” è più sicuro di “Nessun account trovato con quella email.” Quest’ultimo dice all’attaccante quali nomi utente esistono.
Aggiungi il blocco dell’account dopo fallimenti prolungati. Dopo 10-20 tentativi falliti, blocca l’account per 15 minuti o richiedi la verifica via email per sbloccare.
Generazione delle password
Quando generi password (per utenti, chiavi API, token):
- Minimo 12 caratteri. 8 non è più sufficiente contro l’hardware moderno.
- Includi maiuscole, minuscole, cifre e simboli. Ma la lunghezza conta più della complessità.
- Usa un CSPRNG (generatore di numeri pseudocasuali crittograficamente sicuro).
Math.random()non è sicuro. - Non generare mai pattern prevedibili.
Password1!sembra complesso ma è trivialmente indovinabile.
La password migliore è una stringa generata casualmente da un CSPRNG. Usa un password manager per memorizzarla. Non chiedere agli utenti di ricordare password complesse — dagli una generata e lascia che il manager si occupi del resto.
Errori comuni
- Hashare una volta con SHA-256 o MD5 — troppo veloce, senza sale, trivialmente forzabile.
- Usare un sale statico — se ogni utente condivide lo stesso sale, le tabelle arcobaleno funzionano.
- Memorizzare il sale separatamente — se il sale è in una tabella diversa, un attaccante che compromette il database ottiene entrambi.
- Crittografare invece di hashare — se puoi decriptare la password, un attaccante con la chiave può farlo anche.
- Nessuna limitazione della velocità — nemmeno bcrypt è vulnerabile a tentativi di login illimitati.
- Rivelare se un nome utente esiste — aiuta gli attaccanti a enumerare account validi.
- Usare
Math.random()per la generazione delle password — prevedibile, non crittograficamente sicuro.
Riferimento rapido | Fa | Non fa | |––|——| | Usare bcrypt, Argon2 o scrypt | Usare SHA-256 o MD5 | | Lasciare che la libreria gestisca i sale | Gestire i sale da soli | | Limitare i tentativi di login | Consentire tentativi di password illimitati | | Generare password con CSPRNG | Usare Math.random() | | Memorizzare solo l’hash | Memorizzare password in testo puro o crittografate | | Dire “credenziali non valide” | Dire “password sbagliata” |
Prova
Se devi generare una password casuale sicura o hashare una stringa per test, usa uno strumento basato su browser affinché l’operazione resti locale. La tua password non dovrebbe mai toccare un server per un semplice compito di generazione.