Aktuálna situácia: PHP2Shell. Dve kritické zraniteľnosti PHP (CVSS 9.2) umožňujú spustenie škodlivého kódu bez prihlásenia. Záplaty od 21. 10. 2026.Čo treba urobiť teraz →

Bezpečné ukladanie hesiel v PHP: správne použitie password_hash

Ako vývojári ukladajú v PHP heslá pomocou password_hash a password_verify, vyberajú vhodný postup a nahrádzajú staré hashe MD5 alebo SHA-1 bez resetu hesla pre všetkých používateľov.

Stav 5 min čítaniaBezpečný vývoj

Stručne: Heslá ukladáte v PHP pomocou password_hash() a overujete ich pomocou password_verify(). Štandard PASSWORD_DEFAULT používa momentálne bcrypt, od PHP 8.4 s faktorom náročnosti 12; tam, kde je PHP skompilované s Argon2, je dobrou alternatívou PASSWORD_ARGON2ID. Staré hashe MD5 alebo SHA-1 prestavíte tak, že ich okamžite zabalíte do password_hash a pri ďalšom prihlásení ich nahradíte priamym hashom.

Čo má únik databázy spoločné s vašimi hashmi

Po vlámaní do webservera je databáza jedným z prvých cieľov. Kto môže, ako pri PHP2Shell, spúšťať kód na serveri, prečíta aj prístupové údaje aplikácie, a tým tabuľku s používateľskými účtami. Či sa heslá v nej stanú čitateľnými po hodinách alebo po rokoch, nerozhoduje sa v deň útoku, ale pri programovaní.

Prekvapivo veľa aplikácií ukladá heslá ešte postupmi, ktoré na tento účel nikdy neboli určené: MD5, SHA-1, jednoduchý SHA-256 alebo dokonca spätne čitateľne zašifrované. Tieto postupy sú rýchle a práve to je problém. Útočníci s nimi dokážu skúšať veľmi veľa kandidátov za sekundu.

PHP prináša už dávno riešenie, ktoré počíta pomaly, pre každé heslo automaticky vytvorí salt a postup aj parametre ukladá priamo v hashi. Stačí ho dôsledne používať.

password_hash v PHP: bcrypt alebo Argon2id?

password_hash() pozná viacero postupov. Výber podľa príručky PHP:

Konštanta Postup Poznámka
PASSWORD_DEFAULT momentálne bcrypt štandard, v budúcich verziách sa môže zmeniť
PASSWORD_BCRYPT bcrypt faktor náročnosti je od PHP 8.4 štandardne 12, predtým 10; rátajú sa len prvé 72 bajty
PASSWORD_ARGON2I Argon2i od PHP 7.2, len ak je PHP zostavené s podporou Argon2
PASSWORD_ARGON2ID Argon2id od PHP 7.3, rovnaký predpoklad

OWASP odporúča ako prvú voľbu Argon2id, s najmenej 19 MiB pamäte, dvoma prechodmi a jedným vláknom, a bcrypt najmä pre existujúce systémy. Pre väčšinu aplikácií PHP je rozdiel v praxi menší než rozdiel medzi ktorýmkoľvek z týchto postupov a MD5. Dôležitejšie je, že niektorý z nich vôbec používate a parametre pravidelne upravujete.

Či je Argon2id k dispozícii, ukáže krátky test:

<?php
var_dump(defined('PASSWORD_ARGON2ID'));
var_dump(password_algos());

Ukladanie a overovanie: úplný vzor

Nasledujúci príklad ukazuje registráciu a prihlásenie s automatickou aktualizáciou zastaraných hashov. Kontrola dĺžky chráni pred limitom 72 bajtov pri 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() počíta bajty, nie znaky. Tu je to zámer, pretože limit bcrypt sa vzťahuje na bajty. Stĺpec v databáze by mal podľa príručky pojať viac než 60 znakov, 255 je dobrá voľba. Nikdy nenastavujte vlastný salt: možnosť je zastaraná a od PHP 8.0 sa ignoruje.

S prechodom na PHP 8.4 stúpa štandardný faktor náročnosti pre bcrypt z 10 na 12. Existujúce hashe sa vďaka password_needs_rehash() pri ďalšom prihlásení automaticky zvýšia. Na silno vyťažených systémoch sa pred prechodom oplatí krátky záťažový test prihlásenia.

Tip

Faktor náročnosti zmerajte na svojom produkčnom serveri. Jedna kontrola by mala stáť citeľný výpočtový čas, ale zostať výrazne pod jednou sekundou, aby sa prihlásenia pri záťaži nestali úzkym miestom.

Nahradenie starých hashov MD5 a SHA-1

Skutočnou výzvou je zvyčajne existujúci stav. Samotné heslá nepoznáte, takže ich nemôžete jednoducho znova hashovať. Čakanie, kým sa každý používateľ opäť prihlási, nechá neaktívne účty nechránené. OWASP preto opisuje postup v dvoch etapách:

  1. Okamžite zabaliť: každú existujúcu hodnotu MD5 alebo SHA-1 obaliť skriptom pomocou password_hash() a uložiť s označením, napríklad v stĺpci hash_typ.
  2. Pri prihlásení overiť: pri označených účtoch najprv prepočítať zadanie starým postupom a potom ho overiť pomocou password_verify() voči obalenej hodnote.
  3. Priamo nahradiť: po úspešnom prihlásení vytvoriť z čistého textu normálny hash a odstrániť označenie.
  4. Vyčistiť zvyšok: účty, ktoré majú po stanovenej lehote ešte obalené hashe, nastaviť na reset hesla.
<?php
// Etapa 1 (jednorazovo skriptom): $neu = password_hash($alterMd5Wert, PASSWORD_DEFAULT);
// Etapa 2 a 3 pri prihlásení:
if ($konto['hash_typ'] === 'md5-umhuellt') {
    $ok = password_verify(md5($eingabe), $konto['passwort_hash']);
    if ($ok) {
        $speichere(password_hash($eingabe, PASSWORD_DEFAULT), 'direkt');
    }
}

Obalené hashe sú lepšie než holé hodnoty MD5, ale slabšie než priame hashe. Sú prechodným riešením, nie cieľom. Po incidente, pri ktorom mohla byť dotknutá databáza, táto zmena nenahrádza výzvu používateľom, aby zmenili svoje heslá. Aké stopy na taký incident poukazujú, opisuje článok Som už kompromitovaný?.

Pepper, šifrovanie a iné slepé uličky

Niektoré nápady znejú ako viac bezpečnosti, prinášajú však málo alebo škodia:

  • Vlastné konštrukcie hashov, ako viacnásobný SHA-256 s vlastnoručne vytvoreným saltom, sú rýchlejšie než bcrypt, a preto slabšie.
  • Zašifrované heslá sú spätne čitateľné. Ak kľúč leží na tom istom serveri, šifrovanie po vlámaní sotva chráni.
  • Pepper, teda tajná prídavná hodnota pre všetky heslá, môže byť dodatočnou vrstvou, ak sa uchováva oddelene od databázy. Nenahrádza však dobrý postup hashovania a sťažuje neskoršie zmeny.
  • Predbežné hashovanie pred bcrypt s cieľom obísť limit 72 bajtov prináša podľa OWASP vlastné riziká. Jasná hranica dĺžky alebo Argon2id je jednoduchšie.

Aj pri pravidlách pre nové heslá sa oplatí pozrieť na stav techniky: dĺžka chráni viac než komplikované požiadavky na špeciálne znaky a veľmi časté heslá alebo heslá, ktoré sa už objavili v únikoch údajov, by sa mali odmietať. Vynútené zmeny v pevných intervaloch bez dôvodu vedú podľa skúseností skôr k predvídateľným vzorom než k silnejším heslám.

Pre prihlasovacie systémy navyše heslá nikdy nestoja samy: ukladanie dopĺňa dvojfaktorové overenie pre administrátorov a brzda proti hromadným pokusom o prihlásenie. Obe témy rieši sprievodca Ochrana prihlásenia admina proti brute force. Pre odkazy na reset a jednorazové kódy platia iné pravidlá, pozri Bezpečné náhodné čísla a tokeny v PHP. Prehľad ďalších funkcií poskytuje Zabudované bezpečnostné funkcie PHP.

Ako konkrétne pomáha PHP SOS?

  • Preveríme, ako vaša aplikácia a pluginy ukladajú heslá, a nájdeme zastarané postupy aj vo vedľajších funkciách.
  • Naplánujeme prechod existujúceho stavu s obalením, migráciou pri prihlásení a lehotou pre zvyškové účty.
  • Faktor náročnosti alebo parametre Argon2 nastavíme podľa vašich serverov.
  • Po incidente podporíme rozhodovanie o resetoch hesiel a ich technickú realizáciu.

Kontrolu vlastného softvéru PHP ponúkame ako bezpečnostný audit a penetračný test.

Časté otázky

Aký algoritmus používa password_hash s PASSWORD_DEFAULT?

Momentálne bcrypt. Príručka PHP upozorňuje, že sa štandard môže zmeniť, ak pribudnú silnejšie postupy. Stĺpec v databáze by preto mal pojať viac než 60 znakov; príručka uvádza ako dobrú voľbu 255.

Mal by som heslá šifrovať namiesto hashovania?

Nie. Zašifrované heslá sa dajú pomocou kľúča opäť prečítať a tento kľúč zvyčajne leží na tom istom serveri. Hash hesla je jednosmerná ulica: aplikácia môže len overiť, či zadanie sedí.

Aký vysoký by mal byť faktor náročnosti pri bcrypt?

Od PHP 8.4 je štandard 12, predtým 10. OWASP uvádza ako spodnú hranicu 10 a odporúča ísť tak vysoko, ako to vaše servery zvládnu. Na vlastnom hardvéri zmerajte, ako dlho trvá jedna kontrola.

Čo znamená limit 72 bajtov pri bcrypt?

bcrypt zohľadňuje len prvých 72 bajtov hesla, všetko za tým sa odreže. Pri znakoch s diakritikou zaberá jeden znak často dva bajty. Dĺžku preto výslovne obmedzte, alebo použite Argon2id, ak je vaše PHP s ním skompilované.

Musia používatelia po vlámaní zmeniť heslá?

Ak sa nedá vylúčiť, že databáza unikla, mali by ste to zariadiť. Dobré hashe výrazne spomaľujú lámanie, slabé heslá však zostávajú napadnuteľné. Či navyše existuje oznamovacia povinnosť, vyjasnite so svojím zodpovedným pracovníkom pre ochranu údajov.

Zdroje

Okamžitý kontakt

Každá hodina sa počíta. Napíšte nám hneď.

Stručne opíšte systém, verziu PHP a svoje pozorovanie. Ozveme sa, vyjasníme situáciu a budeme pracovať na diaľku, kým nebude incident uzavretý a zdokumentovaný.

Okamžitá pomoc