Molte delle moderne applicazioni in PHP accedono a informazioni importanti dell’utente e le memorizzano in un database. Ad esempio, una web app potrebbe avere un sistema di registrazione per i nuovi utenti. Ma come si dovrebbe memorizzare lo username e la password nel database?
Si deve sempre pensare alla sicurezza. Se le password vengono memorizzate in formato di testo piano, che cosa succede se un utente malintenzionato accede al database? Può facilmente leggere tutte le password degli utenti. Ecco perché si usa una tecnica chiamata hashing delle password per evitare che un intruso ottenega le password degli utenti.

Questo post descrive come memorizzare le password in modo sicuro nel database in modo che, anche se il database cade in mani sbagliate, nessun danno sarà fatto.

Che cosa è lo hashing delle password?
Lo hashing non è un concetto nuovo. È stato usato per un tempo abbastanza lungo. Per capire lo hashing, bisogna pensare alle impronte digitali. Ogni persona ha un’impronta digitale unica. Allo stesso modo, ogni stringa può avere un unica dimensione fissa come “impronta digitale” chiamato hash. Per un buon algoritmo di hashing, è molto raro che due stringhe differenti abbiano lo stesso hash (che si chiama collisione).
La caratteristica più importante degli hash è che il processo di generazione di hash ha solo una sola direzione. Il modo in cui una proprietà indica che è impossibile recuperare il testo originale dal suo hash, cioè irreversibile. Pertanto la password hashing si adatta perfettamente al nostro bisogno di memorizzazione le password in modo sicuro. Invece di memorizzare una password in testo semplice, possiamo usare lo hash della password e memorizzare il risultato. Se un utente malintenzionato ottiene successivamente l’accesso al database, non gli sarà possibile recuperare la password originale dallo hash.
Ma, per quanto riguarda l’autenticazione? Non è possibile confrontare la password inserita dall’utente in un form di accesso con lo hash memorizzato nel database. Si deve fare preventivamente lo hash alla password di accesso e confrontare il risultato con lo hash memorizzato nel database.
Come fare lo hash con PHP
Esistono diversi algoritmi per generare uno hash da un testo. I più popolari sono: MD5, SHA1 e Bcrypt. Ognuno di questi algoritmi sono supportati in PHP. È davvero importante usare Bcrypt, ma qui si presentano prima altre alternative, perché aiutano a illustrare ciò che è necessario fare per proteggere le password.
Cominciamo con la funzione PHP md5() che può fare lo hash della password con l’algoritmo di hashing MD5. Nell’esempio seguente viene illustrato il processo di registrazione:
[code lang=”php”] <?php

$username = $_POST["username"];
$password = $_POST["password"];

// creare la connessione al database
// …

// sanitizzare gli input
// …

// creare un hash MD5 della password
$password = md5($password);

// salvare i valori nel database
$sql = "INSERT INTO users (username, password) VALUES (:username, :password)";

$stmt = $db->prepare($sql);

$stmt->execute(array(
":username" => $username,
":password" => $password
));

?>
[/code]

E, nell’esempio riportato di seguito viene illustrato il processo di autenticazione:
[code lang=”php”] <?php

$username = $_POST["username"];
$password = $_POST["password"];

// creare la connessione al database
// …

// sanitizzare gli input
// …

// reare un hash MD5 della password
$password = md5($password);

// recuperare le informazioni dal database
$sql = "SELECT * FROM users WHERE username=:username AND password=:password";
$stmt = $db->prepare($sql);
$stmt->execute(array(
":username" => $username,
":password" => $password
));

$row = $stmt->fetch();

?>
[/code]

Nell’esempio precedente, md5() crea uno hash a 128 bit dalla password inserita. È anche possibile utilizzare sha1(), anziché md5(), che produce uno hash di 160 bit (il che significa che ci saranno meno possibilità di collisione).

Se si genera uno hash MD5 della stringa “MySecretPassword” l’output al browser, sarà simile al seguente:

[code lang=”xml”] 7315a012ecad1059a3634f8be1347846
[/code]
Quando si fa lo hash con SHA1 a “MySecretPassword”, produrrà il seguente output:
[code lang=”xml”] 952729c61cab7e01e4b5f5ba7b95830d2075f74b
[/code]
Mai fare lo hash delle password due volte. Questo non aggiunge una maggiore sicurezza, anzi, rende lo hash debole e inefficiente. Ad esempio, non tentare di creare uno hash MD5 di una password fornendo come input uno hash sha1(). Aumenta semplicemente la probabilità di collisioni degli hash.
Ottenere lo hashing delle password - livello superiore
I ricercatori hanno trovato diversi difetti negli algoritmi SHA1 e MD5. Ecco perché le moderne applicazioni PHP non dovrebbero usare queste due funzioni per creare degli hash. Piuttosto, si dovrebbe usare algoritmi di hash dalla famiglia SHA2 come SHA256 o SHA512. Come il nome suggerisce, producono degli hash di lunghezza 256 e 512 bit. Sono più recenti e molto più forti di MD5. Poiché il numero di bit aumenta, la probabilità di una collisione diminuisce. Da entrambi gli esempi precedenti il secondo è più che sufficiente per mantenere l’applicazione protetta.

Il codice riportato di seguito viene illustrato come utilizzare lo hashing SHA256 in PHP:

[code lang=”php”] <?php
$password = hash("sha256", $password);
?>
[/code]
PHP offre una funzione integrata hash(). Il primo argomento della funzione è il nome dell’algoritmo (si può passare nomi come algoritmo SHA256, SHA512, MD5, SHA1, e molti altri). Il secondo argomento è la stringa cui verrà fatta lo hash. Il risultato che viene restituito è la stringa hash.
La buona paranoia è buona - Usare il sale per sicurezza
Essere paranoici sulla sicurezza del sistema è buona cosa. Quindi, consideriamo un altro caso qui. È stato generato uno hash dalla password di un utente e memorizzato nella tabella del database. Anche se un utente malintenzionato ha accesso al nostro database, non sarà in grado di determinare la password originale. Ma cosa succede se si confronta tutti gli hash delle password con un altro e trova alcuni di loro con lo stesso hash? Che cosa indica questo?
Sappiamo già che due stringhe avranno stesso hash solo se entrambi sono le stesse (in assenza di collisioni). Quindi, se l’attaccante vede gli stessi hash, si può dedurre che le password per tali account sono gli stessi. Se conosce già la password su un account, poi si può semplicemente utilizzarla per ottenere l’accesso su tutti gli account con la stessa password.
La soluzione è quella di utilizzare un numero casuale mentre si genera lo hash, denominato sale. Ogni volta che si generano uno hash di una password, si usa un sale casuale. Si ha solo bisogno di generare un numero casuale di una particolare lunghezza e aggiungerla in formato testo alla password, e quindi si genera lo hash. In questo modo, anche se le password di due account sono gli stessi, gli hash generati non saranno uguali perché i sali utilizzati in entrambi i casi sono diversi.

Di seguito viene illustrato l’utilizzo del sale:

[code lang=”php”] <?php
define("MAX_LENGTH", 6);

function generateHashWithSalt($password) {
$intermediateSalt = md5(uniqid(rand(), true));
$salt = substr($intermediateSalt, 0, MAX_LENGTH);
return hash("sha256", $password . $salt);
}
?>
[/code]

Per creare un sale usiamo la funzione uniqid(). Il primo argomento è rand() che genera un numero intero casuale. Il secondo argomento è il booleano true (vero) per aumentare la probabilità che il numero generato possa essere unico.

Per autenticare l’utente, è necessario memorizzare il sale usato per generare lo hashing della password (è possibile memorizzare il sale in un’altra colonna nella stessa tabella in cui si dispone di username e password memorizzate). Quando l’utente tenta di accedere, aggiungere il sale alla password inserita e poi fare lo hash della stringa con la funzione di hash.

Andando ancora oltre: L'uso di BCrypt
Imparare a conoscere MD5/SHA1 e i sali giova per guadagnare una comprensione su ciò che è necessario per l’archiviazione sicura. Ma per l’implementazione di un serio piano di sicurezza, Bcrypt è la tecnica di hashing che si dovrebbe usare.
Bcrypt si basa sul cifrario a blocco simmetrico Blowfish. Idealmente, vogliamo un algoritmo di hashing che lavora molto lentamente per evitare i tentativi automatici di cracking di un attaccante, ma non troppo lento perché non può essere utilizzato in applicazioni reali. Con Bcrypt, possiamo fare il lavoro con l’algoritmo n di volte più lento durante la regolazione n, in modo che non superi le nostre risorse. Inoltre, se si utilizza Bcrypt allora non c’è bisogno di memorizzare i sali nel database.

Diamo un’occhiata a un esempio che utilizza la funzione crypt() per fare lo hash della password:

[code lang=”php”] <?php
function generateHash($password) {
if (defined("CRYPT_BLOWFISH") && CRYPT_BLOWFISH) {
$salt = ‘$2y$11$E’. substr(md5(uniqid(rand(), true)), 0, 22);
return crypt($password, $salt);
}
}
?>
[/code]
La funzione controlla se la cifra di cui sopra il Blowfish è disponibile attraverso la costante CRYPT_BLOWFISH. Se è così, allora si genera un sale casuale. Il requisito è che il sale inizia con “$2a$” (o “$2y$” vedere questo avviso su php.net) per indicare all’algoritmo Blowfish ch’è seguito da un numero a due cifre da 4 a 31. Questo numero è un parametro che rende gli attacchi di forza bruta a richiedere più tempo per trovarla. Poi si è aggiunta una stringa alfanumerica contenente 22 caratteri come porzione principale del nostro sale. La stringa alfanumerica può includere anche ‘. ‘ o ‘/ ‘.

Ora è il momento di autenticare gli utenti:

[code lang=”php”] <?php
function verify($password, $hashedPassword) {
return crypt($password, $hashedPassword) == $hashedPassword;
}
?>
[/code]
Si noti che non abbiamo bisogno del sale per la password per l’autenticazione perché fa parte dell’uscita dello hash.
Riepilogo
Una misura di sicurezza importante da seguire è sempre fare lo hash delle password degli utenti prima di riporli nel database e utilizzare gli algoritmi di hashing moderni come Bcrypt, SHA256, SHA512. Quando si esegue, anche se un utente malintenzionato vuole accedere al database, non avrà le password reali degli utenti. Questo articolo spiega i principi alla base dello hashing, sali e Bcrypt.