Pe scurt: Parolele le stocați în PHP cu password_hash() și le verificați cu password_verify(). Valoarea implicită PASSWORD_DEFAULT folosește în prezent bcrypt, începând cu PHP 8.4 cu factor de cost 12; acolo unde PHP este compilat cu Argon2, PASSWORD_ARGON2ID este o alternativă bună. Hash-urile MD5 sau SHA-1 vechi le convertiți împachetându-le imediat în password_hash și înlocuindu-le la următoarea autentificare cu un hash direct.
Ce legătură are o exfiltrare din baza de date cu hash-urile dumneavoastră
După o intruziune într-un server web, baza de date este una dintre primele ținte. Cine poate executa cod pe server, așa cum este posibil la PHP2Shell, citește și datele de acces ale aplicației și, astfel, tabelul cu conturile de utilizator. Dacă parolele din el devin lizibile după ore sau după ani se decide nu în ziua atacului, ci la programare.
Surprinzător de multe aplicații stochează încă parolele cu procedee care nu au fost concepute niciodată pentru acest scop: MD5, SHA-1, un simplu SHA-256 sau chiar criptate reversibil. Aceste procedee sunt rapide și exact aceasta este problema. Atacatorii pot încerca cu ele foarte mulți candidați pe secundă.
PHP oferă de multă vreme o soluție care calculează lent, generează automat un salt pentru fiecare parolă și stochează procedeul și parametrii în hash-ul însuși. Trebuie doar să o folosiți consecvent.
password_hash în PHP: bcrypt sau Argon2id?
password_hash() cunoaște mai multe procedee. Alegerea conform manualului PHP:
| Constantă | Procedeu | Observație |
|---|---|---|
PASSWORD_DEFAULT |
în prezent bcrypt | Valoare implicită, se poate schimba în versiuni viitoare |
PASSWORD_BCRYPT |
bcrypt | Factor de cost implicit 12 începând cu PHP 8.4, înainte 10; contează doar primii 72 de octeți |
PASSWORD_ARGON2I |
Argon2i | Din PHP 7.2, doar dacă PHP este construit cu suport Argon2 |
PASSWORD_ARGON2ID |
Argon2id | Din PHP 7.3, aceeași condiție |
OWASP recomandă Argon2id ca prima alegere, cu cel puțin 19 MiB de memorie, două iterații și un fir de execuție, iar bcrypt mai ales pentru sistemele existente. Pentru majoritatea aplicațiilor PHP, diferența este în practică mai mică decât diferența dintre oricare dintre aceste procedee și MD5. Mai important este să folosiți măcar unul dintre ele și să ajustați regulat parametrii.
Dacă Argon2id este disponibil arată un test scurt:
<?php
var_dump(defined('PASSWORD_ARGON2ID'));
var_dump(password_algos());
Stocare și verificare: un model complet
Exemplul următor arată înregistrarea și autentificarea, cu actualizarea automată a hash-urilor învechite. Verificarea lungimii protejează împotriva limitei de 72 de octeți a bcrypt:
<?php
declare(strict_types=1);
const PASSWORT_ALGO = PASSWORD_DEFAULT;
function passwortSpeichern(string $passwort): string
{
if (strlen($passwort) > 72) {
throw new InvalidArgumentException('Passwort zu lang für das gewählte Verfahren.');
}
return password_hash($passwort, PASSWORT_ALGO);
}
function passwortPruefen(string $eingabe, string $gespeicherterHash, callable $hashAktualisieren): bool
{
if (!password_verify($eingabe, $gespeicherterHash)) {
return false;
}
if (password_needs_rehash($gespeicherterHash, PASSWORT_ALGO)) {
$hashAktualisieren(password_hash($eingabe, PASSWORT_ALGO));
}
return true;
}
strlen() numără octeți, nu caractere. Aici este intenționat, deoarece limita bcrypt se referă la octeți. Coloana din baza de date ar trebui, potrivit manualului, să încapă mai mult de 60 de caractere, 255 fiind o alegere bună. Nu setați niciodată un salt propriu: opțiunea este depășită și este ignorată începând cu PHP 8.0.
Odată cu trecerea la PHP 8.4, factorul de cost implicit pentru bcrypt crește de la 10 la 12. Hash-urile existente sunt ridicate automat la următoarea autentificare, datorită password_needs_rehash(). Pe sisteme foarte încărcate, înainte de trecere merită un scurt test de sarcină al autentificării.
Măsurați factorul de cost pe serverul dumneavoastră de producție. O singură verificare ar trebui să consume vizibil timp de calcul, dar să rămână clar sub o secundă, pentru ca autentificările să nu devină un punct de blocaj sub sarcină.
Înlocuirea hash-urilor MD5 și SHA-1 vechi
Adevărata provocare este de regulă stocul existent. Parolele propriu-zise nu le cunoașteți, deci nu le puteți pur și simplu re-hash-ui. Dacă așteptați până când fiecare utilizator se autentifică din nou, conturile inactive rămân neprotejate. OWASP descrie de aceea o procedură în doi pași:
- Împachetați imediat: Împachetați fiecare valoare MD5 sau SHA-1 existentă, printr-un script, cu
password_hash()și stocați-o cu o marcare, de exemplu într-o coloanăhash_typ. - Verificați la autentificare: Pentru conturile marcate, calculați mai întâi introducerea cu procedeul vechi și verificați apoi cu
password_verify()față de valoarea împachetată. - Înlocuiți direct: După o autentificare reușită, generați un hash normal din textul clar și eliminați marcarea.
- Curățați restul: Conturile care după un termen stabilit au încă hash-uri împachetate le setați pe resetare de parolă.
<?php
// Treapta 1 (o singură dată, printr-un script): $neu = password_hash($alterMd5Wert, PASSWORD_DEFAULT);
// Treptele 2 și 3 la autentificare:
if ($konto['hash_typ'] === 'md5-umhuellt') {
$ok = password_verify(md5($eingabe), $konto['passwort_hash']);
if ($ok) {
$speichere(password_hash($eingabe, PASSWORD_DEFAULT), 'direkt');
}
}
Hash-urile împachetate sunt mai bune decât valorile MD5 goale, dar mai slabe decât hash-urile directe. Ele sunt o soluție de tranziție, nu un scop. După un incident în care baza de date ar putea fi afectată, această trecere nu înlocuiește cererea către utilizatori de a-și schimba parolele. Ce urme indică un astfel de incident descrie Sunt deja compromis?.
Pepper, criptare și alte căi greșite
Unele idei sună a mai multă securitate, dar aduc puțin sau dăunează:
- Construcțiile proprii de hash, precum SHA-256 repetat cu salt generat de dumneavoastră, sunt mai rapide decât bcrypt și de aceea mai slabe.
- Parolele criptate sunt reversibile. Dacă cheia se află pe același server, criptarea protejează puțin după o intruziune.
- Un pepper, adică o valoare secretă suplimentară pentru toate parolele, poate fi un strat în plus dacă este păstrat separat de baza de date. Nu înlocuiește însă un procedeu de hash bun și îngreunează schimbările ulterioare.
- Pre-hash-ul înainte de bcrypt, pentru a ocoli limita de 72 de octeți, ascunde, potrivit OWASP, riscuri proprii. O limită clară de lungime sau Argon2id este mai simplă.
Și la regulile pentru parolele noi merită o privire asupra stadiului tehnicii: lungimea protejează mai mult decât cerințele complicate privind caracterele speciale, iar parolele foarte frecvente sau apărute deja în scurgeri de date ar trebui respinse. Schimbările forțate la intervale fixe, fără motiv, duc, potrivit experienței, mai degrabă la tipare previzibile decât la parole mai puternice.
Pentru sistemele de autentificare, parolele nu stau niciodată singure: procedeele în doi factori pentru administratori și o frână împotriva încercărilor de autentificare în masă completează stocarea. Ambele le tratează ghidul Protejarea autentificărilor de administrare împotriva atacurilor brute force. Pentru linkurile de resetare și codurile unice se aplică alte reguli, vedeți Numere aleatorii și token-uri sigure în PHP. O privire de ansamblu asupra altor funcții oferă Funcțiile de securitate integrate ale PHP.
Cum ajută PHP SOS concret?
- Verificăm cum stochează aplicația și pluginurile dumneavoastră parolele și găsim procedee învechite și în funcții secundare.
- Planificăm trecerea stocului existent cu împachetare, migrare la autentificare și termen pentru conturile rămase.
- Ajustăm factorul de cost sau parametrii Argon2 la serverele dumneavoastră.
- După un incident vă sprijinim la decizia privind resetările de parolă și la implementarea lor tehnică.
Verificarea software-ului PHP propriu o oferim ca audit de securitate și test de penetrare.
Întrebări frecvente
Ce algoritm folosește password_hash cu PASSWORD_DEFAULT?
În prezent bcrypt. Manualul PHP atrage atenția că valoarea implicită se poate schimba, pe măsură ce apar procedee mai puternice. De aceea coloana din baza de date ar trebui să încapă mai mult de 60 de caractere; manualul indică 255 ca alegere bună.
Ar trebui să criptez parolele în loc să le transform în hash?
Nu. Parolele criptate pot fi citite din nou cu cheia, iar această cheie se află de regulă pe același server. Un hash de parolă este un sens unic: aplicația poate verifica doar dacă o introducere se potrivește.
Cât de mare ar trebui să fie factorul de cost la bcrypt?
Începând cu PHP 8.4, valoarea implicită este 12, anterior era 10. OWASP indică 10 ca limită inferioară și recomandă să mergeți atât de sus cât suportă serverele dumneavoastră. Măsurați pe propriul hardware cât durează o verificare.
Ce înseamnă limita de 72 de octeți la bcrypt?
bcrypt ia în considerare doar primii 72 de octeți ai unei parole, tot ce urmează este tăiat. La caracterele cu diacritice, un caracter ocupă adesea doi octeți. De aceea limitați explicit lungimea sau folosiți Argon2id, dacă PHP-ul dumneavoastră este compilat cu acesta.
Trebuie utilizatorii să-și schimbe parolele după o intruziune?
Dacă nu se poate exclude că baza de date a fost exfiltrată, ar trebui să dispuneți acest lucru. Hash-urile bune încetinesc considerabil spargerea, dar parolele slabe rămân vulnerabile. Dacă există în plus o obligație de notificare, lămuriți cu responsabilul dumneavoastră cu protecția datelor.