Come posso archiviare le password dei miei utenti in modo sicuro?


169

Quanto è più sicuro di questo semplice MD5 ? Ho appena iniziato a esaminare la sicurezza delle password. Sono abbastanza nuovo in PHP.

$salt = 'csdnfgksdgojnmfnb';

$password = md5($salt.$_POST['password']);
$result = mysql_query("SELECT id FROM users
                       WHERE username = '".mysql_real_escape_string($_POST['username'])."'
                       AND password = '$password'");

if (mysql_num_rows($result) < 1) {
    /* Access denied */
    echo "The username or password you entered is incorrect.";
} 
else {
    $_SESSION['id'] = mysql_result($result, 0, 'id');
    #header("Location: ./");
    echo "Hello $_SESSION[id]!";
}

Nota php 5.4+ ha questo integrato
Benjamin Gruenbaum,

Vedi anche il framework di hashing delle password PHP di Openwall (PHPass). È portatile e indurito contro una serie di attacchi comuni alle password degli utenti.
jww

1
"Usa i DOP obbligatoria invece dell'interpolazione di stringhe", per le persone che oggi si imbattono in questa domanda.
Finanzi la causa di Monica

Risposte:


270

Il modo più semplice per proteggere lo schema di archiviazione delle password è utilizzare una libreria standard .

Poiché la sicurezza tende ad essere molto più complicata e con più possibilità invisibili di problemi che la maggior parte dei programmatori potrebbe affrontare da sola, l'utilizzo di una libreria standard è quasi sempre l'opzione più semplice e sicura (se non l'unica) disponibile.


La nuova API password PHP (5.5.0+)

Se stai utilizzando PHP versione 5.5.0 o successiva, puoi utilizzare la nuova API di hashing password semplificata

Esempio di codice usando l'API della password di PHP:

<?php
// $hash is what you would store in your database
$hash = password_hash($_POST['password'], PASSWORD_DEFAULT, ['cost' => 12]);

// $hash would be the $hash (above) stored in your database for this user
$checked = password_verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

(Nel caso in cui si stia ancora utilizzando legacy 5.3.7 o versioni successive, è possibile installare ircmaxell / password_compat per avere accesso alle funzioni integrate)


Miglioramento su hash salati: aggiungi pepe

Se desideri maggiore sicurezza, i responsabili della sicurezza ora (2017) consigliano di aggiungere un ' pepe ' agli hash delle password (automaticamente) salati.

Esiste un semplice drop in class che implementa in modo sicuro questo modello, che consiglio: Netsilik / PepperedPasswords ( github ).
Viene fornito con una licenza MIT, quindi puoi usarlo come vuoi, anche in progetti proprietari.

Esempio di codice usando Netsilik/PepperedPasswords:

<?php
use Netsilik/Lib/PepperedPasswords;

// Some long, random, binary string, encoded as hexadecimal; stored in your configuration (NOT in your Database, as that would defeat the entire purpose of the pepper).
$config['pepper'] = hex2bin('012345679ABCDEF012345679ABCDEF012345679ABCDEF012345679ABCDEF');

$hasher = new PepperedPasswords($config['pepper']);

// $hash is what you would store in your database
$hash = $hasher->hash($_POST['password']);

// $hash would be the $hash (above) stored in your database for this user
$checked = $hasher->verify($_POST['password'], $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}


La libreria standard OLD

Nota: non dovresti più averne bisogno! Questo è solo qui per scopi storici.

Dai un'occhiata a: Framework di hashing della password PHP portatile : phpass e assicurati di utilizzare l' CRYPT_BLOWFISHalgoritmo, se possibile.

Esempio di codice usando phpass (v0.2):

<?php
require('PasswordHash.php');

$pwdHasher = new PasswordHash(8, FALSE);

// $hash is what you would store in your database
$hash = $pwdHasher->HashPassword( $password );

// $hash would be the $hash (above) stored in your database for this user
$checked = $pwdHasher->CheckPassword($password, $hash);
if ($checked) {
    echo 'password correct';
} else {
    echo 'wrong credentials';
}

PHPass è stato implementato in alcuni progetti abbastanza noti:

  • phpBB3
  • WordPress 2.5+ e bbPress
  • la versione Drupal 7, (modulo disponibile per Drupal 5 e 6)
  • altri

La cosa buona è che non devi preoccuparti dei dettagli, quei dettagli sono stati programmati da persone con esperienza e recensiti da molte persone su Internet.

Per ulteriori informazioni sugli schemi di memorizzazione delle password, leggi il post sul blog di Jeff : Probabilmente stai memorizzando le password in modo errato

Qualunque cosa tu faccia se scegli l'approccio " Lo farò io, grazie ", non usarlo MD5o SHA1più . Sono un bel algoritmo di hashing, ma considerato rotto per motivi di sicurezza .

Attualmente, usando crypt , con CRYPT_BLOWFISH è la migliore pratica.
CRYPT_BLOWFISH in PHP è un'implementazione dell'hash Bcrypt. Bcrypt si basa sul codice a blocchi Blowfish, facendo uso della sua costosa configurazione dei tasti per rallentare l'algoritmo.


29

I tuoi utenti saranno molto più sicuri se hai utilizzato query con parametri invece di concatenare istruzioni SQL. E il sale dovrebbe essere unico per ogni utente e deve essere archiviato insieme all'hash della password.


1
C'è un buon articolo sulla sicurezza in PHP su Nettuts +, viene menzionato anche il salting delle password. Forse dovresti dare un'occhiata a: net.tutsplus.com/tutorials/php/…
Fábio Antunes

3
Nettuts + è un articolo pessimo da usare come modello: include l'uso dell'MD5 che può essere forzato molto facilmente anche con il sale. Invece, basta utilizzare la libreria PHPass che è molto, molto meglio di qualsiasi codice che si può trovare su un sito di tutorial, vale a dire questa risposta: stackoverflow.com/questions/1581610/...
RichVel

11

Un modo migliore sarebbe per ogni utente di avere un sale unico.

Il vantaggio di avere un sale è che rende più difficile per un utente malintenzionato pre-generare la firma MD5 di ogni parola del dizionario. Ma se un attaccante viene a sapere che hai un salt fisso, allora potrebbe pre-generare la firma MD5 di ogni parola del dizionario con il prefisso salt fisso.

Un modo migliore è ogni volta che un utente cambia la propria password, il sistema genera un salt casuale e lo memorizza insieme al record dell'utente. È un po 'più costoso controllare la password (poiché è necessario cercare il sale prima di poter generare la firma MD5) ma rende molto più difficile per un utente malintenzionato pre-generare MD5.


3
I sali vengono generalmente memorizzati insieme all'hash della password (ad es. L'output della crypt()funzione). E poiché devi comunque recuperare l'hash della password, l'utilizzo di un salt specifico per l'utente non renderà la procedura più costosa. (O intendevi dire che generare un nuovo sale casuale è costoso? Non credo proprio.) Altrimenti +1.
Inshallah,

Per motivi di sicurezza, è possibile che si desideri fornire l'accesso alla tabella solo tramite stored procedure e impedire che l'hash venga mai restituito. Al contrario, il client passa quello che pensa sia l'hash e ottiene un flag di successo o fallimento. Ciò consente al proc memorizzato di registrare il tentativo, creare una sessione, ecc.
Steven Sudit

@Inshallah - se tutti gli utenti hanno lo stesso sale, allora puoi riutilizzare l'attacco del dizionario che usi su user1 contro user2. Ma se ogni utente ha un sale unico, dovrai generare un nuovo dizionario per ogni utente che vuoi attaccare.
R Samuel Klatchko,

@R Samuel - è esattamente per questo che ho votato la tua risposta, perché raccomanda la strategia delle migliori pratiche per evitare tali attacchi. Il mio commento aveva lo scopo di esprimere la mia perplessità su ciò che hai detto in merito al costo aggiuntivo di un sale per utente, che non ho capito affatto. (dal momento che "i sali sono generalmente archiviati insieme all'hash della password" tutti i requisiti di memoria e CPU aggiuntivi per un sale per utente sono così microscopici, che non è nemmeno necessario menzionarli ...)
Inshallah

@Inshallah - Stavo pensando al caso in cui hai controllato il database se la password con hash è a posto (quindi hai un recupero db per ottenere il sale e un secondo accesso db per controllare la password con hash). Hai ragione sul caso in cui scarichi la password salt / hash in un unico recupero e poi fai il confronto sul client. Dispiace per la confusione.
R Samuel Klatchko,

11

Con PHP 5.5 (quello che descrivo è disponibile anche per le versioni precedenti, vedi sotto) dietro l'angolo vorrei suggerire di usare la sua nuova soluzione integrata: password_hash()e password_verify(). Offre diverse opzioni per raggiungere il livello di sicurezza della password necessario (ad esempio specificando un parametro "cost" tramite l' $optionsarray)

<?php
var_dump(password_hash("my-secret-password", PASSWORD_DEFAULT));

$options = array(
    'cost' => 7, // this is the number of rounds for bcrypt
    // 'salt' => 'TphfsM82o1uEKlfP9vf1f', // you could specify a salt but it is not recommended
);
var_dump(password_hash("my-secret-password", PASSWORD_BCRYPT, $options));
?>

sarà di ritorno

string(60) "$2y$10$w2LxXdIcqJpD6idFTNn.eeZbKesdu5y41ksL22iI8C4/6EweI7OK."
string(60) "$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d."

Come si può vedere, la stringa contiene sia il sale che il costo specificato nelle opzioni. Contiene inoltre l'algoritmo utilizzato.

Pertanto, quando si controlla la password (ad esempio quando l'utente accede), quando si utilizza la password_verify()funzione gratuita estrarrà i parametri crittografici necessari dall'hash della password stessa.

Quando non si specifica un salt, l'hash della password generato sarà diverso ad ogni chiamata password_hash()perché il salt viene generato in modo casuale. Pertanto il confronto di un hash precedente con uno appena generato non riuscirà, anche per una password corretta.

La verifica funziona in questo modo:

var_dump(password_verify("my-secret-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));
var_dump(password_verify("wrong-password", '$2y$10$BjHJbMCNWIJq7xiAeyFaHOGaO0jjNoE11e0YAer6Zu01OZHN/gk6K'));

var_dump(password_verify("my-secret-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));
var_dump(password_verify("wrong-password", '$2y$07$TphfsM82o1uEKlfP9vf1fOKohBqGVXOJEmnUtQu7Y1UMft1R4D3d.'));

Spero che fornire queste funzioni integrate fornisca presto una migliore sicurezza delle password in caso di furto di dati, in quanto riduce la quantità di pensiero che il programmatore deve mettere in una corretta implementazione.

Esiste una piccola libreria (un file PHP) che ti fornirà PHP 5.5 password_hashin PHP 5.3.7+: https://github.com/ircmaxell/password_compat


2
Nella maggior parte dei casi è meglio omettere il parametro salt. La funzione crea un sale dalla fonte casuale del sistema operativo, ci sono poche possibilità che tu possa fornire un sale migliore da solo.
martinstoeckli,

1
È quello che ho scritto, vero? "se non viene specificato alcun sale, viene generato casualmente, per questo motivo è preferibile non specificare un sale"
akirk,

La maggior parte degli esempi mostra come aggiungere entrambi i parametri, anche quando non è consigliabile aggiungere un sale, quindi mi chiedo perché? E ad essere sincero, ho letto solo il commento dietro il codice, non nella riga successiva. Ad ogni modo, non sarebbe meglio quando l'esempio mostra come utilizzare al meglio la funzione?
martinstoeckli,

Hai ragione, sono d'accordo. Ho modificato la mia risposta di conseguenza e ho commentato la riga. Grazie
akirk il

come devo verificare se la password salvata e la password immessa sono uguali? Sto usando password_hash()e password_verifyindipendentemente dalla password (corretta o no) che ho usato finisco con la password corretta
Brownman Revival

0

A me va bene. Atwood ha scritto della forza di MD5 contro i tavoli arcobaleno , e fondamentalmente con un lungo sale come quello sei seduto abbastanza (anche se alcuni segni di punteggiatura / numeri casuali, potrebbe migliorarlo).

Potresti anche guardare SHA-1, che sembra essere diventato più popolare in questi giorni.


6
La nota in fondo al post di Atwood (in rosso) si collega a un altro post di un professionista della sicurezza che afferma di usare MD5, SHA1 e altri hash veloci per archiviare le password è molto sbagliato.
sipwiz,

2
@Matthew Scharley: non sono d'accordo sul fatto che lo sforzo aggiuntivo imposto dai costosi algoritmi di hashing delle password sia la falsa sicurezza. Serve a evitare la forzatura bruta di password facilmente indovinabili. Se stai limitando i tentativi di accesso, stai proteggendo dalla stessa cosa (anche se un po 'più efficacemente). Ma se un avversario ha accesso agli hash archiviati nel DB, sarà in grado di forzare tali password (facilmente indovinabili) abbastanza rapidamente (a seconda di quanto facilmente indovinabile). L'impostazione predefinita per l'algoritmo di crittografia SHA-256 è 10000 round, quindi ciò renderebbe 10000 volte più difficile.
Inshallah,

3
Gli hash lenti vengono effettivamente eseguiti ripetendo uno veloce un numero molto elevato di volte e mescolando i dati tra ogni iterazione. L'obiettivo è garantire che, anche se il cattivo ottiene una copia degli hash delle password, deve bruciare una notevole quantità di tempo della CPU per testare il suo dizionario contro i tuoi hash.
CAF

4
@caf: credo che l'algoritmo bcrypt sfrutti l'ampiezza parametrizzabile della programmazione delle chiavi di Eksblowfish; non del tutto sicuro di come funzioni, ma la pianificazione delle chiavi è spesso un'operazione molto costosa eseguita durante l'inizializzazione di un oggetto di contesto di cifratura, prima che venga eseguita qualsiasi crittografia.
Inshallah,

3
Inshallah: questo è vero: l'algoritmo bcrypt ha un design diverso, in cui la cripto-primitiva sottostante è un codice a blocchi piuttosto che una funzione di hash. Mi riferivo a schemi basati su funzioni hash, come MDK crypt () di PHK.
Caf,

0

Voglio aggiungere:

  • Non limitare le password degli utenti per lunghezza

Per la compatibilità con i vecchi sistemi spesso impostare un limite per la lunghezza massima della password. Questa è una cattiva politica di sicurezza: se si imposta la restrizione, impostarla solo per la lunghezza minima delle password.

  • Non inviare password utente via e-mail

Per recuperare una password dimenticata, è necessario inviare l'indirizzo con il quale l'utente può modificare la password.

  • Aggiorna gli hash delle password degli utenti

L'hash della password potrebbe non essere aggiornato (i parametri dell'algoritmo potrebbero essere aggiornati). Utilizzando la funzione password_needs_rehash()è possibile verificarlo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.