L'Evoluzione della Sicurezza delle Password: dalle Basi fino ad Argon2

L'Evoluzione della Sicurezza delle Password: dalle Basi fino ad Argon2

Scritto da Francesco Di Donato 18 luglio 2025 10 minuti di lettura

Che il codice lo scriva tu o un’Intelligenza Artificiale, è fondamentale avere ben chiaro il quadro dell’implementazione. Questo è ancora più vero per una parte così cruciale di un’applicazione come l’autenticazione.

Oggi esistono molti metodi di autenticazione moderni, come OAuth, Magic Link e WebAuthn. Sono scelte validissime e, anzi, spesso consigliate. Tuttavia, esistono ancora tantissimi applicativi che preferiscono le password, come ad esempio i gestionali aziendali su misura. La combinazione di email e password è, in questo senso, il “coccodrillo” dell’autenticazione: esisteva prima degli altri metodi e probabilmente ci sarà anche dopo.

Ciò non significa, però, che anche l’email e la password non si siano evolute nel tempo. Come qualsiasi sistema esposto all’ambiente e alle sue minacce (gli hacker), questa coppia è costretta a mutare alla ricerca di soluzioni.

Password in Chiaro

Partiamo dal principio, dalla soluzione più semplice e “ingenua”. Immagina: tu sei il server, l’utente vede un form sulla pagina. Inserisce email e password, preme invio e quei dati arrivano al tuo server. Li prendi e, così come sono arrivati, li metti nel database.

Quando l’utente vuole fare login, ti fornisce nuovamente i dati e tu controlli che le due password siano uguali.

# Registrazione
password_registrazione = "123456" # <- Salva in DB

# Login
password_login = "123456"

is_auth = password_login == password_registrazione # Yeah!

Ma sto salvando in chiaro. Molto male. Perché?

Database Leaking

Server e database sono come castelli, che possono essere protetti sempre meglio. Tuttavia, interagiscono con l’esterno e perciò presentano sempre una “feritoia”. I database vengono “leakati” molto più spesso di quanto si pensi.

Se un server salva le password in chiaro in un Database e i contenuti di tale vengono esposti, chiunque in possesso dei leak potrà, senza alcun tipo di ostacolo, accedere al posto dei tuoi utenti.

Il vero disastro è che la gente riutilizza le password. Le statistiche mostrano che oltre il 50% delle persone usa la stessa password per più servizi. Ciò significa che se il tuo piccolo sito subisce un data leak, gli hacker proveranno quelle stesse credenziali (email e password) su piattaforme come banche, social network o servizi di posta elettronica… un vero disastro a catena.

Hashing

La soluzione sta nel non salvare le password direttamente nel Database, ma un loro “fingerprint”. Una lunga stringa di caratteri pseudocasuali, che sembrano casuali ma in realtà dipendono direttamente dall’input (la password originale).

È importante non confondere l’hashing con la crittografia (encryption). La crittografia è un processo reversibile: ciò che viene crittografato può essere decrittografato. L’hashing, invece, è a senso unico. Ed è proprio quello che vogliamo per le password: un sistema che può verificarle, ma mai rivelarle.

Questi hash sono prodotti utilizzando funzioni crittografiche di hashing. Tali funzioni sono “a senso unico”: è facile e veloce calcolare l’hash partendo dalla password, ma è computazionalmente impossibile risalire alla password originale dall’hash. Se sei curioso di capire intuitivamente come funziona una di queste, puoi leggere il mio articolo interattivo sullo SHA256.

import os
import hashlib

# Registrazione
p1 = "123456"
p1_hashed = hashlib.sha256(p1.encode()).hexdigest() # "8d9...c92" <- Salva in DB

# Login
p2 = "123456"
p2_hashed = hashlib.sha256(p2.encode()).hexdigest() # "8d9...c92"

is_auth = p2_hashed == p1_hashed # Yeah!

Quando l’utente ti invia la password, non la registri in chiaro da nessuna parte. La passi direttamente alla tua funzione di hashing che, molto velocemente (ma vedremo che questa velocità può essere un problema), genererà il fingerprint risultante. Questo viene salvato nel database insieme all’email.

Quando l’utente tenta di accedere, con la password fornita esegui la stessa operazione di hashing. La comparazione avverrà quindi sui due fingerprint.

Quando l’hacker entra in possesso del contenuto del Database, non potrà andare semplicemente al login e inserire l’hash. Il sistema si aspetta la password originale. Se l’hacker invia l’hash stesso, il sistema (che non “pensa”, ma esegue e basta) prenderà quello che è già un hash e ne calcolerà un altro, che ovviamente non combacerà mai con quello generato dalla password originale.

Qui, il concetto di password si evolve in una terna:

  • La password originale, in chiaro
  • L’algoritmo scelto per fare hashing (es. SHA-256 in realtà non è ottimale a questo scopo, più sotto è spiegato il perché)
  • Tecnicamente anche il seed della funzione di hashing

Bene, le password sono ora protette da hash. Ma il tuo utente è al sicuro? Non ancora.

Rainbow Table

Il problema è che una funzione di hash è deterministica: allo stesso input corrisponde sempre lo stesso output. Se due utenti usano la stessa password “password123”, avranno lo stesso identico hash nel database.

La funzione SHA-256, inoltre, appartiene alla famiglia di quelle veloci ed efficienti. E in questo caso, la velocità diventa una vulnerabilità.

Si racconta che “mio cugino” (o, più realisticamente, aggressori ben equipaggiati) abbia utilizzato un gran numero di GPU in parallelo su una quantità enorme di password comuni che la gente usa abitualmente (come 123456, password1!, qwerty e anche combinazioni più complesse).

In modalità offline, precomputano tutti gli hash di questa mole di password. Una volta generata, la tabella non richiede più consumo di elettricità per le consultazioni. Il risultato è una gigantesca tabella di consultazione, nota come Rainbow Table.

Adesso, l’hacker prende gli hash dal tuo database, ne sceglie uno e va a vedere nella Rainbow Table se quell’hash è stato precomputato. Se lo trova (e lo fa in tempo costante grazie all’implementazione di una hash map) potrà risalire in un attimo all’input originale che ha generato quell’hash. Input che, ovviamente, coincide con la password in chiaro! Ora l’hacker può accedere con i dati dell’utente al tuo login!

Questo è esattamente ciò che è successo nel famoso data breach di LinkedIn del 2012.

Salt

Come si rende inutile una tabella pre-calcolata come questa? Semplice: facendo in modo che ogni password, anche se identica a un’altra, produca un hash completamente diverso.

La soluzione si chiama Salt (sale). È una stringa di caratteri casuali e unica, generata per ogni utente al momento della registrazione. Questa stringa viene aggiunta alla password prima di eseguire l’hashing.

import os
import hashlib

# Registrazione
p1 = "123456"
salt1 = os.urandom(16).hex() # -> 'a4b8c7...' (un salt casuale e unico)
p1_salted = p1 + salt1
p1_hashed = hashlib.sha256(p1_salted.encode()).hexdigest() # <- Salva in DB p1_hashed e salt1

# Login
p_login = "123456"
# Recupera salt1 dal DB per questo utente
p_login_salted = p_login + salt1
p_login_hashed = hashlib.sha256(p_login_salted.encode()).hexdigest()

is_auth = p_login_hashed == p1_hashed # Yeah!

Il processo diventa:

“Ma aspetta,” dirai tu, “se l’hacker ruba il database, ruba sia gli hash che i salt. A che serve?”

Ottima domanda! Il salt, infatti, non è un segreto. La sua forza non risiede nella segretezza, ma nella sua unicità. Poiché ogni utente ha un salt diverso, la Rainbow Table di “mio cugino” diventa un costoso fermacarte. Per attaccare il tuo database, l’hacker dovrebbe creare una nuova e gigantesca Rainbow Table per ogni singolo utente, utilizzando il suo specifico salt. Questo rende l’attacco economicamente e computazionalmente insostenibile, riducendolo a un attacco di forza bruta individuale, password per password, che è infinitamente più lento.

Slower Hashing Function

Abbiamo reso le Rainbow Table inutili, ma non abbiamo ancora finito. L’hacker può ancora prendere un singolo utente (con il suo hash e il suo salt) e provare a indovinare la sua password con un attacco a forza bruta.

Qui torna il problema della velocità. Algoritmi come SHA-256 sono progettati per essere fulminei. Con una buona GPU, un attaccante può testare miliardi di combinazioni al secondo. Se la password di un utente è “juventus1990”, anche con il salt verrà trovata in pochi minuti.

La soluzione è un cambio di paradigma: usare algoritmi di hashing deliberatamente lenti e “costosi” in termini computazionali.

Immagina di non usare più un frullatore da cucina, ma una macchina industriale complessa che impiega mezzo secondo per processare una singola password. Per un utente che effettua il login, mezzo secondo è impercettibile. Per un hacker che deve provare miliardi di password, invece, questo rallentamento rende l’attacco proibitivo.

Qui entrano in gioco algoritmi specifici per le password:

Prevenzione Timing Attack

La semplice comparazione hash1 == hash2 può esporre a un rischio sottile ma reale chiamato Timing Attack. Per evitarlo, è fondamentale usare una funzione di comparazione a “tempo costante”, che impiega sempre la stessa quantità di tempo per restituire un risultato. In Python, ad esempio, la libreria hmac offre la funzione compare_digest.

import hmac
is_auth = hmac.compare_digest(p_login_hashed, p1_hashed) # Molto più sicuro!

Pepper

Esiste un ulteriore livello di “paranoia” che possiamo aggiungere per aumentare la sicurezza. Abbiamo l’hash e il salt nel database. Cosa succede se un hacker riesce a bucare il server e a fare un dump completo del DB? Ha tutto ciò che gli serve per iniziare i suoi lenti attacchi a forza bruta offline.

Possiamo rendergli la vita ancora più difficile con un Pepper (pepe). Il pepper è un valore segreto, una stringa statica conosciuta solo dall’applicazione. A differenza del salt, il pepper non viene salvato nel database [11, 35, 36], ma è nascosto nel codice sorgente dell’applicazione o in un file di configurazione sicuro sul server.

Il calcolo diventa: hash(password + salt + pepper).

Se l’hacker ruba il database, gli manca ancora un pezzo del puzzle: il pepper segreto. Senza di esso, non può replicare gli hash, rendendo il suo dump del database quasi inutile. Questo rappresenta un’ulteriore linea di difesa contro la compromissione totale del database.

Il pepper aumenta la sicurezza, ma non è una panacea. Se un hacker ottiene accesso non solo al database, ma anche al codice sorgente o ai file di configurazione del server, troverà anche il pepper, vanificandone l’utilità. Inoltre, la gestione del pepper (la sua rotazione, la sua distribuzione in ambienti con più server) può essere complessa. È un ottimo strato aggiuntivo, ma non sostituisce la necessità di un algoritmo di hashing lento e di un salt.

Passwordless

Abbiamo costruito una fortezza quasi inespugnabile per le nostre password. Ma la verità è che la fortezza più sicura è quella che non ha bisogno di essere difesa. L’evoluzione finale è l’autenticazione passwordless.

L’idea è eliminare del tutto la necessità per l’utente di creare e ricordare una password. Le tecniche includono:

Finché la classica combinazione email/password continuerà a esistere, però, il nostro dovere è proteggerla nel modo più robusto possibile: usando un algoritmo di hashing moderno, lento e memory-hard come Argon2id, un salt unico per ogni utente e, per la massima sicurezza, un pepper segreto.