Password PHP memorizzarle in maniera sicura con password_hash

Pubblicato da Flavio Biscaldi Aggiornato il

La sicurezza di un'applicazione web in cui è presente un form di registrazione e login, passa soprattutto dalla corretta gestione delle password utenti.

Nell'articolo precedente abbiamo visto come grazie alla crittografia sia possibile proteggere la privacy utente su Internet. Anche in questo caso la crittografia ci viene in aiuto nella gestione delle password utenti.

Scopriamo quindi quali sono le best practice per memorizzare in sicurezza dati sensibili come le password, e poi vediamo come farlo in PHP.

Memorizzare password utenti in modo sicuro

Indipendentemente dal linguaggio di programmazione utilizzato, una gestione corretta delle password è legata essenzialmente a due fattori:

  1. Come viene memorizzata la password nel database (cifrata o in chiaro)
  2. Quale funzione di hash viene utilizzata per proteggere la password

Purtroppo oggi esistono ancora diverse applicazioni web che non utilizzano le tecniche di protezione adeguate, salvando le password utenti in chiaro oppure con funzioni di hash deboli.

Ma vediamo meglio cosa sono e a cosa servono le funzioni di hash e quali utilizzare per salvare le nostre password utenti in sicurezza.

Funzioni di hash, cosa sono e a cosa servono

La funzioni di hash sono algoritmi matematici in grado di convertire una stringa di dimensioni variabili (nel nostro caso la password) in una di dimensioni fisse detta hash, composta da caratteri alfanumerici casuali.

Una funzione di hash sicura deve soddisfare 3 proprietà:

  • Non deve essere possibile risalire alla stringa originale di partenza, a partire dal suo hash
  • Non deve essere possibile trovare due stringhe differenti che abbiano lo stesso valore di hash
  • Deve essere resistente ad attacchi di forza bruta

Come vedremo più avanti la terza proprietà viene garantita dalla "lentezza" dell'algoritmo.

Esistono numerose funzioni crittografiche per generare l'hash di una stringa, in passato le più utilizzate per la protezione delle password utenti erano MD5 e SHA-1.

Vediamo perché non sono più considerate affidabili, quali sono le alternative che garantiscono maggiore sicurezza e come implementarle in PHP.

MD5 e SHA-1

MD5 e SHA-1 sono funzioni progettate per creare hash rapidamente e con una quantità minima di calcolo. È proprio questa caratteristica che le rende vulnerabili ad attacchi di forza bruta.

Infatti la GPU di una moderna scheda video è in grado di calcolare ogni secondo milioni di combinazioni di hash MD5, dunque aumenta la possibilità di mettere a segno l'attacco.

Giusto per la cronaca PHP offre la funzione md5() per calcolare l'hash MD5 di una data stringa.

Come già detto però MD5 non rispetta le terza proprietà (e nemmeno la seconda) che una funzione hash sicura deve soddisfare, pertanto non usare MD5 per proteggere le password utenti, nè tantomeno le funzioni che fanno parte della famiglia SHA come SHA-1.

bcrypt e Argon2

Le funzioni bcrypt e Argon2 hanno la caratteristica di essere "lente", ovvero richiedono molto più tempo e calcolo per generare un hash. Questo rappresenta un vantaggio in funzione di attacchi brute force rispetto a MD5 e SHA-1.

Come si potrebbe erroneamente pensare infatti non è la lunghezza dell'hash generato da una funzione crittografica a determinare la sua forza, ma il costo computazionale ovvero le risorse (cicli di CPU e RAM) necessarie a calcolare l'hash.

In particolare Argon2 si rivela essere più sicuro di bcrypt in quanto per il calcolo dell'hash, oltre ai cicli di CPU, impiega anche un'elevata quantità di RAM, aumentando quindi il carico di lavoro necessario per generare l'hash e sferrare un attacco di forza bruta.

Vediamo ora come sfruttare queste due funzioni di hash sicure in PHP.

Come proteggere le password PHP

PHP offre la possibilità di proteggere le password utenti attraverso l'uso delle funzioni password_hash() e password_verify(). Di seguito è mostrato un esempio:

<?php

$password = "lamiapassword";

$hash = password_hash($password, PASSWORD_BCRYPT);

printf("Hash password \"%s\": %s\n", $password, $hash);

if (password_verify($password, $hash)) {
   echo "Password corretta!";
} else {
   echo "Password errata!";
}

// Hash password "lamiapassword": $2y$10$NxOho6yYlKw7PXNhy14KeelQ6GrE7RrKPMw2me3238e.6kyQYbmmK
// Password corretta!

La funzione PHP password_hash()

La funzione password_hash() accetta come primo parametro la password "in chiaro", memorizzata nella variabile $password e come secondo parametro una costante che rappresenta il tipo di algoritmo da utilizzare. Il valore hash che ne risulta è quindi memorizzato nella variabile $hash.

In questo caso abbiamo scelto la costante PASSWORD_BCRYPT per indicare a PHP che vogliamo utilizzare l'algoritmo bcrypt, mentre la costante PASSWORD_ARGON2I ci consente di utilizzare Argon2.

Da notare come il valore di $hash cambi ad ogni esecuzione dello script. Ciò è dovuto all'uso del salt, un valore casuale aggiunto per aumentare la sicurezza dell'algoritmo.

La funzione consente anche di impostare il valore cost, che rappresenta il costo algoritmico della funzione. In un'applicazione web un valore di cost pari a 10 rappresenta un buon compromesso tra sicurezza e costi di esecuzione della funzione, un valore troppo elevato rischierebbe infatti di rallentare eccessivamente l'applicazione, viceversa un valore troppo basso ne diminuirebbe la sicurezza.

<?php

$options = [
   'cost' => 10
];

$hash = password_hash("lamiapassword", PASSWORD_BCRYPT, $options);

La funzione PHP password_verify()

Una volta generato l'hash possiamo verificare la correttezza della password utente tramite la funzione password_verify().

Questa funzione accetta come primo parametro la password utente e come secondo il valore hash da verificare, e non fa altro che confrontare l'hash del secondo parametro con quello ottenuto applicando l'algoritmo bcrypt al primo parametro, restituendo true se il confronto va a buon fine, false altrimenti.

Conclusioni

In sintesi, per memorizzare password utenti in PHP in maniera sicura utilizza la funzione password_hash() con bcrypt o Argon2, o qualunque altra funzione di hash con un costo computazionale elevato, evitando quindi MD5, SHA-1, SHA-224, SHA-256, SHA-384 e SHA-512, SHA-3.

Risorse utili

p