Röviden: A jelszavakat PHP-ban a password_hash() függvénnyel tárolja, és a password_verify() függvénnyel ellenőrzi. A PASSWORD_DEFAULT alapértelmezés jelenleg bcryptet használ, a PHP 8.4 óta 12-es költségtényezővel; ahol a PHP Argon2-vel van fordítva, a PASSWORD_ARGON2ID jó alternatíva. A régi MD5- vagy SHA-1-hasheket úgy állítja át, hogy azonnal beágyazza őket a password_hash-be, és a következő bejelentkezéskor közvetlen hashre cseréli.
Mi köze egy adatbázis-kiszivárgásnak a hasheihez
Egy webszerver feltörése után az adatbázis az egyik első cél. Aki, ahogyan a PHP2Shellnél lehetséges, kódot tud végrehajtani a szerveren, az alkalmazás hozzáférési adatait is elolvassa, és ezzel a felhasználói fiókok táblázatát is. Hogy a benne lévő jelszavak órák vagy évek múlva válnak-e olvashatóvá, az nem a támadás napján dől el, hanem a programozáskor.
Meglepően sok alkalmazás ma is olyan eljárásokkal tárolja a jelszavakat, amelyeket erre a célra soha nem szántak: MD5, SHA-1, egyszerű SHA-256, vagy akár visszafordíthatóan titkosítva. Ezek az eljárások gyorsak, és éppen ez a baj. A támadók velük másodpercenként nagyon sok jelöltet végigpróbálhatnak.
A PHP régóta hoz magával egy megoldást, amely lassan számol, minden jelszóhoz automatikusan sót generál, az eljárást és a paramétereket pedig magában a hashben tárolja. Önnek csak következetesen használnia kell.
password_hash PHP-ban: bcrypt vagy Argon2id?
A password_hash() több eljárást ismer. A választék a PHP kézikönyve szerint:
| Konstans | Eljárás | Megjegyzés |
|---|---|---|
PASSWORD_DEFAULT |
jelenleg bcrypt | alapértelmezés, a jövőbeli verziókban változhat |
PASSWORD_BCRYPT |
bcrypt | költségtényező a PHP 8.4 óta alapból 12, korábban 10; csak az első 72 bájt számít |
PASSWORD_ARGON2I |
Argon2i | a PHP 7.2 óta, csak ha a PHP Argon2-támogatással készült |
PASSWORD_ARGON2ID |
Argon2id | a PHP 7.3 óta, ugyanez a feltétel |
Az OWASP az Argon2id-t ajánlja első választásként, legalább 19 MiB memóriával, két futással és egy szállal, a bcryptet pedig főleg meglévő rendszerekhez. A legtöbb PHP-alkalmazásnál a gyakorlatban kisebb a különbség, mint e eljárások bármelyike és az MD5 között. Fontosabb, hogy egyáltalán használja valamelyiket, és rendszeresen igazítsa a paramétereket.
Hogy elérhető-e az Argon2id, azt egy rövid teszt mutatja:
<?php
var_dump(defined('PASSWORD_ARGON2ID'));
var_dump(password_algos());
Tárolás és ellenőrzés: egy teljes minta
A következő példa a regisztrációt és a bejelentkezést mutatja a régi hashek automatikus frissítésével. A hosszellenőrzés védelmet nyújt a bcrypt 72 bájtos határa ellen:
<?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;
}
A strlen() bájtokat számol, nem karaktereket. Ez itt szándékos, mert a bcrypt határa bájtokra vonatkozik. Az adatbázisbeli oszlopnak a kézikönyv szerint 60 karakternél többet kell tartalmaznia, a 255 jó választás. Soha ne adjon meg saját sót: ez a beállítás elavult, és a PHP 8.0 óta figyelmen kívül marad.
A PHP 8.4-re váltással a bcrypt alapértelmezett költségtényezője 10-ről 12-re emelkedik. A meglévő hasheket a password_needs_rehash() révén a következő bejelentkezéskor automatikusan megemeli a rendszer. Erősen terhelt rendszereken az átállás előtt érdemes egy rövid terhelési tesztet futtatni a bejelentkezésen.
Mérje meg a költségtényezőt az éles szerverén. Egyetlen ellenőrzésnek érezhető számítási időbe kell kerülnie, de egy másodperc alatt kell maradnia, hogy a bejelentkezések terhelés alatt ne váljanak szűk keresztmetszetté.
A régi MD5- és SHA-1-hashek leváltása
Az igazi kihívás általában a meglévő állomány. Magukat a jelszavakat nem ismeri, ezért nem hasheli őket egyszerűen újra. Ha megvárja, amíg minden felhasználó újra bejelentkezik, az inaktív fiókok védelem nélkül maradnak. Az OWASP ezért kétlépcsős eljárást ír le:
- Azonnali beágyazás: minden meglévő MD5- vagy SHA-1-értéket szkripttel
password_hash()-be burkolni, és jelöléssel tárolni, például egyhash_typoszlopban. - Ellenőrzés bejelentkezéskor: a jelölt fiókoknál a bevitelt először a régi eljárással átszámolni, majd a
password_verify()függvénnyel a burkolt értékhez hasonlítani. - Közvetlen csere: sikeres bejelentkezés után a nyílt szövegből normál hasht létrehozni, és a jelölést eltávolítani.
- A maradék rendezése: azokat a fiókokat, amelyeknek egy meghatározott határidő után még burkolt hasheik vannak, jelszó-visszaállításra állítani.
<?php
// 1. szint (egyszeri szkripttel): $neu = password_hash($alterMd5Wert, PASSWORD_DEFAULT);
// 2. és 3. szint bejelentkezéskor:
if ($konto['hash_typ'] === 'md5-umhuellt') {
$ok = password_verify(md5($eingabe), $konto['passwort_hash']);
if ($ok) {
$speichere(password_hash($eingabe, PASSWORD_DEFAULT), 'direkt');
}
}
A burkolt hashek jobbak a puszta MD5-értékeknél, de gyengébbek a közvetlen hasheknél. Átmeneti megoldások, nem célok. Egy olyan incidens után, amelyben az adatbázis érintett lehet, ez az átállás nem helyettesíti a felhasználók felszólítását jelszavuk megváltoztatására. Hogy milyen nyomok utalnak ilyen incidensre, azt a Már feltörték a rendszeremet? írja le.
Pepper, titkosítás és más tévutak
Néhány ötlet több biztonságnak hangzik, de keveset hoz, vagy árt:
- Saját hash-konstrukciók, például többszörös SHA-256 saját sóval, gyorsabbak a bcryptnél, ezért gyengébbek.
- A titkosított jelszavak visszafordíthatók. Ha a kulcs ugyanazon a szerveren van, a titkosítás egy betörés után alig véd.
- A pepper, vagyis egy titkos kiegészítő érték az összes jelszóhoz, további réteg lehet, ha az adatbázistól elkülönítve őrzik. Jó hash-eljárást azonban nem helyettesít, és megnehezíti a későbbi váltásokat.
- A bcrypt előtti előzetes hashelés a 72 bájtos határ megkerülésére az OWASP szerint saját kockázatokat rejt. Egy egyértelmű hosszkorlát vagy az Argon2id egyszerűbb.
Az új jelszavakra vonatkozó szabályoknál is érdemes a technika állására nézni: a hossz többet véd, mint a különleges karakterekre vonatkozó bonyolult előírások, a nagyon gyakori vagy már adatszivárgásokban felbukkant jelszavakat pedig el kell utasítani. Az ok nélküli, rögzített időközönként kikényszerített jelszócserék tapasztalat szerint inkább kiszámítható mintákhoz vezetnek, mint erősebb jelszavakhoz.
A bejelentkező rendszereknél a jelszavak ráadásul soha nem állnak egyedül: az adminok kétfaktoros eljárása és a tömeges bejelentkezési kísérletek fékezése kiegészíti a tárolást. Mindkettővel foglalkozik az Admin bejelentkezések védelme a brute force ellen útmutató. A visszaállító linkekre és az egyszeri kódokra más szabályok vonatkoznak, lásd Biztonságos véletlenszámok és tokenek PHP-ban. További funkciókról áttekintést ad a A PHP beépített biztonsági funkciói.
Hogyan segít konkrétan a PHP SOS?
- Ellenőrizzük, hogyan tárolja a jelszavakat az alkalmazása és a bővítményei, és a mellékfunkciókban is megtaláljuk az elavult eljárásokat.
- Megtervezzük a meglévő állomány átállítását burkolással, bejelentkezéskori migrációval és határidővel a maradék fiókokra.
- A költségtényezőt vagy az Argon2-paramétereket a szervereire hangoljuk.
- Egy incidens után támogatást nyújtunk a jelszó-visszaállításokról szóló döntésben és a technikai megvalósításukban.
A saját PHP-szoftver ellenőrzését biztonsági auditként és penetrációs tesztként kínáljuk.
Gyakori kérdések
Melyik algoritmust használja a password_hash a PASSWORD_DEFAULT-tal?
Jelenleg a bcryptet. A PHP kézikönyve rámutat, hogy az alapértelmezés változhat, ha erősebb eljárások kerülnek be. Ezért az adatbázis-oszlopnak 60 karakternél többet kell tartalmaznia; a kézikönyv a 255-öt nevezi jó választásnak.
Titkosítsam a jelszavakat hashelés helyett?
Nem. A titkosított jelszavak a kulccsal újra olvashatóvá tehetők, ez a kulcs pedig többnyire ugyanazon a szerveren van. A jelszó-hash egyirányú utca: az alkalmazás csak azt tudja ellenőrizni, hogy egy bevitel megfelel-e.
Milyen magas legyen a költségtényező a bcryptnél?
A PHP 8.4 óta az alapértelmezés 12, korábban 10 volt. Az OWASP a 10-et nevezi alsó határnak, és azt javasolja, hogy olyan magasra menjen, amennyit a szerverei elbírnak. Mérje meg a saját hardverén, mennyi ideig tart egy ellenőrzés.
Mit jelent a 72 bájtos határ a bcryptnél?
A bcrypt csak egy jelszó első 72 bájtját veszi figyelembe, ami utána jön, azt levágja. Ékezetes karaktereknél egy karakter gyakran két bájtnak számít. Ezért kifejezetten korlátozza a hosszt, vagy használja az Argon2id-t, ha a PHP-je azzal lett fordítva.
Meg kell változtatniuk a felhasználóknak a jelszavukat egy betörés után?
Ha nem zárható ki, hogy az adatbázis kiszivárgott, ezt kezdeményeznie kell. A jó hashek jelentősen lassítják a törést, a gyenge jelszavak azonban támadhatók maradnak. Hogy van-e emellett bejelentési kötelezettség, azt egyeztesse az adatvédelmi tisztviselőjével.