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.
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.
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:$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]
$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]
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]
[/code]
sha1()
. Aumenta semplicemente la probabilità di collisioni degli hash.Il codice riportato di seguito viene illustrato come utilizzare lo hashing SHA256 in PHP:
$password = hash("sha256", $password);
?>
[/code]
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.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:
define("MAX_LENGTH", 6);
function generateHashWithSalt($password) {
$intermediateSalt = md5(uniqid(rand(), true));
$salt = substr($intermediateSalt, 0, MAX_LENGTH);
return hash("sha256", $password . $salt);
}
?>
[/code]
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.
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:
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]
Ora è il momento di autenticare gli utenti:
function verify($password, $hashedPassword) {
return crypt($password, $hashedPassword) == $hashedPassword;
}
?>
[/code]
Ancora nessun commento