Jak správně ukládat hesla

(publikováno 22.11.2016) PHP, Bezpečnost, Tipy & triky

Ukládání hesel je dnes důležitá otázka, každou chvíli je zveřejněn nějaký únik hesel. Dnešní článek se věnuje ukládání hesel pomocí hashovací funkce bcrypt, nově i Aegon2. Ukázkový kód je v PHP, použití je ale shodné i v dalších jazycích.

Jak správně ukládat hesla

To, že MD5 nebo SHA již nejsou bezpečné pro ukládání hesel, asi všichni ví. V MD5 dokonce našli i kolizi. Jenže obě funkce jsou navrženy, aby fungovaly rychle, a to je jejich nevýhoda. Extreme GPU Bruteforcer dokáže otestovat až 450 000 000 hesel za sekundu.

Heslo o délce 8 znaků velkých, malých písmen a čísel rozlouskne za 5 dní. Dnes už možná i rychleji, odkazovaný článek je trochu starší. Pokud ale k tomu přidáme masku, když je známo nějaké info o heslu, lze rozlousknout heslo i za pár hodin či minut. Velmi populární nástroj je také Hashcat.

Složitost přechodu na novější hashovací funkci může někomu bránit v učinění této akce. V novějším článku je popsána technika, která rozhodně složitá není.

Bcrypt, Argon2 a password_hash

Výhoda algoritmu Bcrypt spočívá v tom, že ho lze zpomalit. Optimální cena v dnešní době je 10, při které zahashování trvá mezi 50 - 100ms. Z toho už lze usoudit, že se brute force nebude vyznačovat velkou rychlostí. Navíc se zvyšující se výkonem PC lze i cenu Bcrypt zvyšovat. Následující text se týká PHP, v jiných jazycích bude ale implementace podobná a bezpečná, pokud bude využit Bcrypt (alespoň v roce 2016).

Vývojáři PHP šli ještě o krok dále a položili si otázku: Co když se v budoucnu objeví v Bcrypt chyba nebo bude vyvinut nový bezpečnější algoritmus?. A proto navrhli funkci password_hash. Ta do výsledného hashe uloží také verzi algoritmu, cenu i tzv. salt. Klidně se poté může stát, že v databázi bude uloženo každé heslo pomocí jiného algoritmu. Viz update a nový algoritmus Argon2.

UPDATE 27.12 - PHP od verze 7.2 podporuje další hashovací funkci a tou je Argon2. Nyní již stačí změnit PASSWORD_DEFAULT na PASSWORD_ARGON2I pokud server disponuje verzí PHP 7.2 nebo vyšší.

Logicky lze asi uhodnout, že používat porovnání pomocí == asi u těchto hashů nepůjde, pokud se algoritmy či jejich nastavení mohou měnit. Pro zpětnou kontrolu se využije funkce password_verify. A jak poznat, že heslo lze zahashovat bezpečněji? K tomu je funkce password_needs_rehash. Více snad řekne ukázka kódu:

// Vytvoření hash pomocí defaultního algoritmu, aktuálně Bcrypt
$hash = password_hash("mojeTajnéHeslo", PASSWORD_DEFAULT);


// Kontrola shodnosti hashe a zadaného hesla
if(password_verify ( "mojeTajnéHeslo" , $hash )){
    // heslo odpovídá

    // Kontrola, jestli lze vytvořit kvalitnější hash
    if(password_needs_rehash ( $hash , PASSWORD_DEFAULT )){
        // Uložit do DB nový hash
        $hash = password_hash("mojeTajnéHeslo", PASSWORD_DEFAULT);
    }
}

Bcrypt a náhodný salt jako součástí hashe

Funkce password_hash generuje náhodný salt pro Bcrypt, který poté je uložen ve výsledném hashi, ale je také veřejně čitelný. Někdo by si mohl klást otázku, jestli to není nebezpečné. Odpověď je, Není. Výslednou sílu Bcrypt to nesníží, a jedině to minimalizuje možnost, že si někdo vytvoří rainbow table. Salt by měl být náhodný pro každý hash, aby si případný útočník nemohl vytvořit rainbow table pro celou ukradenou DB.

Výsledný hash password_hash

Informace o hashi

Již bylo řečeno, že ve výsledném stringu vráceném funkcí password_hash je uložena verze algoritmu a cena. Tyto informace se dají vypsat pomocí funkce password_get_info.

Jak kdo ukládá hesla, a co vy?

Michal Špaček zveřejnil a průběžně aktualizuje seznam, jak kdo ukládá hesla. Byl jsem zděšen, kolik systémů využívá plaintext. Ne že bych osobně všude využíval Bcrypt, ale do plaintextu jsem hesla neukládal ani jako začátečník. Doufám, že vás tento článek nebo seznam od Michala, inspiroval k využití bezpečnější cesty při ukládání hesel.


V komentářích se můžete podělit o své zkušenosti s ukládáním hesel. Pokud někdo dokonce zkoušel brute force, nechť se podělí.

K tomuto článku již není možné přidávat další komentáře