password_hash návod, příklad a ukázka (password_verify)
Pro ukládání hesel do MariaDB databáze (dříve MySQL) se rozhodně nedoporučuje ukládat tato hesla v otevřeném (tedy čitelném) tvaru. Obecně to platí pro ukládání hesel v SQL databázi. Dokonce ani zašifrovaná hesla s možností dešifrování není doporučováno ukládat do databáze (takové heslo lze dešifrovat, což není bezpečné). Také pro čtení a zápis dat do databáze se doporučuje (hlavně jako ochrana před SQL injekcí) používat parametrizované SQL dotazy.
Proč je zde zmiňována MariaDB a ne MySQL? Je to proto, že MySQL koupila jedna velká společnost (ORACLE) a zřejmě bude z toho chtít mít zisk. Proto vznikla MariaDB a funguje velmi dobře, údajně na ní jede i GOOGLE vyhledávač.
otisk hesla
Žádná hesla do databáze nikdy neukládejte! Do databáze se ukládá jen otisk hesla (otisk) - hash.
Doporučený postup je ukládat hesla jako výtah zprávy - otisk, hash hesla (v doslovném překladu „zmrvení zprávy“). Hash je digitální otisk textu, který je výsledkem hašovací funkce. Je to jednosměrné upravení-výtah zprávy, řetězce, hesla, které (to heslo) již zpětně nejde dešifrovat.
Secure Hash Algorithm = SHA-512 se solí již nepoužívat (r. 2025), nověji ukládat hesla pomocí password_hash("moje tajne heslo", PASSWORD_DEFAULT). Pro ještě vyšší míru zabezpečení doporučuje NÚKIB používat „kvantově odolná hesla - otisky“, v PHP např.:
echo password_hash('tajné 56.Heslíčko', PASSWORD_ARGON2ID);
Bezpečnost otisků se mění, dříve používané hashe MD5, SHA1, SHA2, SHA3 se solí jsou již označené jako překonané, zastaralé. Proto nejbezpečnějsím a taky jednodušším zůsobem je použití PHP funkcí password_hash a password_verify.
Výsledkem je, že do databáze se žádná hesla neukládají, ukládá se jen otisk hesla (hash).
Ukázka výtahu zprávy SHA512 v PHP
<?php
// ZASTARALÉ!!!
echo hash("sha512","moje tajne heslo"); // hash hesla
?>
Výstupem je hash hesla:
ba8dbaecd78d762799715e5fe4559428a1e4b14c68ada9f5581b868c3102243fd485ef6a3239c27bb0636f72e330325792c29ab28405dfe9b62e2678a6a0a9df
Funkce hash("sha512","moje tajne heslo") při stejném vstupu na výstupu dává vždy stejný hash!
Mohlo by se zdát, že nikdo nemůže přijít na to, jaké bylo zadáno původní heslo. Ale hackeři a podobná individua nezahálejí, vyvinuli „duhový seznam“ předhashovaných hesel (rainbow list).
česká duhová tabulka, seznam (rainbow table)
Heslo 123456 má otisk (hash) SHA512:
ba3253876aed6bc22d4a6ff53d8406c6ad864195ed144ab5c87621b6c233b548baeae6956df346ec8c17f5ea10f35ee3cbc514797ed7ddd3145464e2a0bab413
Záškodník, například pomocí SQL injection, zjistí hash hesla, použije seznam předhešovaných hesel (Rainbow tables) a zjistí heslo 123456. Můžete namítnout, že si dám silné heslo, frázi. To je určitě dobrý nápad, ale je tu další problém. Stejná hesla mají stejný hash, toho využívají tzv. narozeninové útoky. Duhové tabulky i narozeninové útoky se řešily(ší) solením. Solením se dosáhne toho, že stejná hesla různých uživatelů mají jiné „výtahy zprávy“ (hashes). Duhové tabulky (rainbow tables) fungovaly pouze proti nesoleným hashům a dnes (r. 2025) už patří do historie.
U algoritmů určených k ukládání hesel, jako je v PHP password_hash(), již (rok 2025) duhové tabulky nefungují.
ba3253876aed6bc22d4a6ff53d8406c6ad864195ed144ab5c87621b6c233b548baeae6956df346ec8c17f5ea10f35ee3cbc514797ed7ddd3145464e2a0bab413
4aaec1c5e8c60370f95d0935efcaa3245736439203e91742d4686aa50c3fba96a567909567be623f033500591132dc5bdb8ddb27e0587db97a986ec92245fc80
adb2a55c6d8c76f17468f0165c344239d45c397bfdac2e6998d72daf6cebf09b07caa91356ebd20dd8c035cccc85ed202303cb66de1aa440fb9959214dbde1d8
4aaec1c5e8c60370f95d0935efcaa3245736439203e91742d4686aa50c3fba96a567909567be623f033500591132dc5bdb8ddb27e0587db97a986ec92245fc80
8cb315dc5b5c2ec3c11b2d754ba306776bb89bf8bee8b3fc4a8727431a2c571da21b2a9df61a45a34c2dd51ecfd3fff6c409beb14f7d607f3e32d1152aa3061a
c0c9f675a88f5f7911344badb6b9fa2e3eb61d38bf4c82c6c95dfc0643544c845c913922c2a83557b324f8d63c5050cca154d9dcbd0a6134e5914ad9645dc21c
db75c90dc15060c673ce99a67f7ac4cc4aa94d9faa12cf8d8bf3ab33e635808535bc126965e90744507da7a7b5b2a2301e4ede9d1c83dddff272555593fc82a3
Jsou to sha512 hashe a použitá hesla jsou: 123456, qwertz, lopata, Petr, Jitka, Jana. Poznáte, která hesla mají dva uživatelé ve výše uvedeném seznamu stejné? (sha512 výpočet)
Redukční funkce pro každý sloupec má jinou barvu, ve výsledku to vypadá jako duha.
Máme v databázi například tabulku „uzivatele“. Sloupec uzivatel, heslo a sul. Ve sloupci heslo je hash (heslo + sůl). To má za následek to, že i stejná hesla mají jiný hash.
MariaDB [redakcnisystem]> select uziv,left(heslicko,60) as heslo,sul from uzivatele;
+----------+--------------------------------------------------------------+-----------------+
| uziv | heslicko | sul |
+----------+--------------------------------------------------------------+-----------------+
| adm | 08421v39cak4db7995b33cee9a2525e7b8162524b235x6a809e540f6e... |l3vNhKGglrocs13< |
+----------+--------------------------------------------------------------+-----------------+
Zde máme sloupce (položky) „uziv“, „heslicko“ a „sul“. Při požadavku na přihlášení (autorizace), se po zadání účtu (uživatel) a hesla do přihlašovacího formuláře, vyhledá uživatel adm, jeho heslo a sůl. Ze zadaného hesla a soli se vytvoří SHA512 výtah zprávy a ten se porovná s uloženým sloupcem „heslicko“ na totožnost. Navíc se pozdrží autorizace o např. 1 vteřinu, nebo se chybné autorizace omezí na X pokusů, popřípadě se kombinují oba způsoby.
<?php
// ZASTARALÉ!!!
$p = hash("sha512",$_POST['heslo'].$zaznam['sul']);
sleep(1); // cekej 1 vteřinu
//...
?>
Takový postup je bezpečný, ale vyvstává tu další problém. Co když je solení použito nesprávně nebo chybně? Co když v budoucnu nebude stačit ani SHA512 a velikost soli? Navíc je potřeba k sloupci heslo ještě sloupec sůl. Je třeba použít také zpomalení.
Poznámka: SHA-512 patří do SHA-2 rodiny kryptohašů. Vytváří 512 bitový (128 znaků) dlouhý výtah zprávy.
Ano, jde. Vývojáři přišli s PHP funkcí na ukládání hesel password_hash() a funkcí na ověřování password_verify().
Funkce password_hash("moje tajne heslo", PASSWORD_DEFAULT) vytvoří osolenou hash. To si můžeme vyzkoušet:
<?php
// MODERNÍ ZPŮSOB
echo password_hash("moje tajne heslo", PASSWORD_DEFAULT);
echo password_hash("moje tajne heslo", PASSWORD_DEFAULT);
?>
Výstupem password_hash() při zadání stejného hesla je vždy jiná hash:
$2y$10$eZkEdPvx/NqV6yVLGL/mSOP4hEqnd.M9prXBQ4qXbM2Q6U4KgvuLC
$2y$10$QRPl6kpudSyxW8/04Ci5n.PCox2oHht5zguKb7bzrvoHO45m0J3wC
Kdy výstup z password_hash("moje tajne heslo",PASSWORD_DEFAULT) se skládá z:
Viz srovnání SHA512, výstupem 2 x hash("sha512","moje tajne heslo") je vždy stejná hash:
ba8dbaecd78d762799715e5fe4559428a1e4b14c68ada9f5581b868c3102243fd485ef6a3239c27bb0636f72e330325792c29ab28405dfe9b62e2678a6a0a9df
ba8dbaecd78d762799715e5fe4559428a1e4b14c68ada9f5581b868c3102243fd485ef6a3239c27bb0636f72e330325792c29ab28405dfe9b62e2678a6a0a9df
Funkce password_hash("moje tajne heslo", PASSWORD_DEFAULT) s konstantou PASSWORD_DEFAULT vrací hash o délce 60 znaků (rok 2022), ale doporučený sloupec v tabulce databáze je varchar(255). Pravděpodobně se délka hashe v budoucnu prodlouží.
Také solení a zdržení je funkcí password_hash() vyřešeno, lépe řečeno funkcí password_verify(), protože při vytváření hashe zdržovat nepotřebujeme.
Informace o vytvořené hashi lze získat z password_get_info($hash).
<?php
$heslo = "moje tajne heslo";
$hash_heslo=password_hash($heslo, PASSWORD_DEFAULT);
echo $hash_heslo."<br>";
$hashInfo = password_get_info($hash_heslo);
echo var_dump($hashInfo)."<br>";
echo "algo: ".$hashInfo['algo']."<br>";
echo "algoName: ".$hashInfo['algoName']."<br>";
echo "option['cost']: ".$hashInfo['options']['cost']."<br>";
echo "délka hashe: ".mb_strlen($hash_heslo,"utf-8")."<br>";
?>
Získáme takovýto výstup:
$2y$10$XhgNEAn5uVDyAU.0yqV.dOsyGDkJ7qtIZogBdbAwKTqLtiji7FQw2
array(3) {["algo"]=> string(2) "2y" ["algoName"]=>string(6) "bcrypt" ["options"]=>array(1) { ["cost"]=>int(10) } }
algo: 2y
algoName: bcrypt
option['cost']: 10
délka hashe: 60
Varování: Možnost soli je zastaralá. Nyní je upřednostňováno jednoduše použít sůl, která je generována ve výchozím nastavení password_hash(). Od PHP 8.0.0 je explicitně daná sůl ignorována.
Použitý algoritmus, typ a sůl jsou vráceny jako součást hashe. Proto jsou v něm zahrnuty všechny informace potřebné k ověření hodnoty hash. To umožňuje funkci password_verify() ověřit hash bez nutnosti samostatného ukládání informací o soli nebo algoritmu.
Na první pohled se to může zdát složitější, ale ve skutečnosti je to zjednodušení a většinou bezpečnější. Už nemusíme v tabulce mít sloupec „sul“, přihlašování se zjednodušuje. Ale není nad praktický příklad:
<?php
echo "<!DOCTYPE html>\n";
echo "<html lang=\"cs\">\n";
echo "<head>\n";
echo "<meta charset=\"utf-8\" />\n";
echo "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n";
echo "<meta name=\"robots\" content=\"all\" />\n";
echo "<meta name=\"keywords\" content=\"password_hash() -> password_verify()\" />\n";
echo "<meta name=\"description\" content=\"password_hash() -> password_verify()\" />\n";
echo "<meta name=\"autor\" content=\"Kocour\" />\n";
echo "<title>password_hash() -> password_verify()</title>\n";
echo "</head>\n";
echo "<body>\n";
echo "<h1>password_hash() -> password_verify()</h1>\n";
$heslo1="moje tajne heslo"; // heslo v otevřeném tvaru
$hash_heslo=password_hash($heslo1, PASSWORD_DEFAULT); // hash hesla (zadaným v otevřeném tvaru), obsahuje i sůl, toto se uloží do databáze
$submit="poslat";
echo "<form action=\"\" method=\"post\">\n";
echo "<fieldset>\n";
echo "<legend>Zadání hesla</legend>\n";
echo "<table>\n";
echo "<tr>\n";
echo "<td>Zadej heslo</td><td><input type=\"password\" name=\"heslo\" value=\"\" size=\"\" /></td>\n";
echo "</tr>\n";
echo "</table>\n";
echo "<br /><input type=\"submit\" name=\"".$submit."\" value=\"odeslat\" />\n";
echo "</fieldset>\n";
echo "</form>\n";
// zpracování formuláře, výpis výsledků zadání hesla
if (!empty($_POST[$submit])) {
// tady načteme heslo z databáze a dáme do proměnné $hash_heslo
$heslo2 = $_POST['heslo'];
// porovnání hesel password_hash()-> password_verify()
// $heslo2 se zadá do formuláře, $hash_heslo se načte z databáze
if (password_verify($heslo2, $hash_heslo)) { // porovnání hesla zadaného z formuláře (zároveň password_hash($heslo2) se správnou solí)
echo 'Správné heslo!';
} else {
echo 'Špatné heslo.';
}
}
echo "</body>\n";
echo "</html>\n";
?>
Password_hash s defaultním nastavením ukládá hesla pomocí bcrypt.
Ověřování podle otisku hesla načteného v databázi již probíhá automaticky
funkcí password_verify($heslo2, $hash_heslo), kde $heslo2='zadané heslo z formuláře v čitelném tvaru', $hash_heslo='načtený otisk hesla z databáze'.
Takže funkce porovnává
'zadané tajné heslo' a '$2y$10$eZkEdPvx/NqV6yVLGL/mSOP4hEqnd.M9prXBQ4qXbM2Q6U4KgvuLC'.
Ta funkce již sama automaticky porovná otisky (automaticky udělá z $heslo2 otisk a pak to porovná s druhým otiskem).
password_verify($heslo2, $hash_heslo) vrací hodnotu true, pokud se zadané heslo v čitelné formě a hash (načtený z databáze) shodují, v opačném případě vrací hodnotu false
Dříve se to dělalo takto, ale nyní už by to byla chyba: Ze zadaného hesla do formuláře spočítáme otisk. Ten porovnáme s otiskem, který je uložený databázi. Pokud jsou otisky stejné, bylo zadáno správné heslo!
V dnešní době je požadována „kvantově odolná“ hash, což algoritmus Argon2id splňuje a proto ho doporučuje i Národní úřad pro kybernetickou a informační bezpečnost - NÚKIB.
kvantově odolné heslo
Podle doporučení NÚKIB je nejlepší používat hesla odolná proti prolamování kvantovými superpočítači, kvantově odolná hesla, což je třeba ARGON2ID. Je to doporučený hybrid argon2d a argon2i, zdroj: NÚKIB (viz odkazy níže).
echo password_hash('tajné heslo', PASSWORD_ARGON2ID);
$argon2id$v=19$m=1024,t=2,p=2$MEhSZkJLQXUxRzljNE5hMw$33pvelMsxqOn/1VV2pnjmKJUECBhilzOZ2+Gq/FxCP4
nebo
echo password_hash('somepassword', PASSWORD_ARGON2I, ['memory_cost' => 2048, 'time_cost' => 4, 'threads' => 3]);
Kde:
Pokud je přihlašovací účet například Zdeněk, tak uživatel časem může psát Zdenek, zdenek, zděněk...
Pokud pak zapomene heslo, bude podpoře tvrdit, že přihlašovací jméno je Zdenek (místo Zdeněk). Pokud je rozsáhlá podpora o mnoha lidech a oprávněních, může to dost potrápit. Je to ale vlastnost databáze (utf8_czech_ci). Přihlašovací účet musí být vždy nastaven jako unikátní.
Pokud chceme vynutit zadávání přesných jmen (účtů) od uživatelů, lze to v PHP vynutit následujícím kódem:
if ($pocz==1) { // nalezen záznam prezdivka
$zaznam = $vysledek->fetch_assoc();
// je case-sensitive, tedy přezdívka přesně, ne Zdeněk, zdenek, zděněk...
if (strcmp($ucet2, $zaznam['prezdivka']) === 0) {
$heslo = $zaznam['pasvorto'];
}
}
Jak vidíme z ukázky, autorizační logika se značně zjednodušila.
Proto je dobrý nápad již používat k ukládání hesel funkci password_hash() a k ověřování hesel sesterskou funkci password_verify(). Pokud to spojíme s parametrizovanými dotazy v MySQLi, tak bude bezpečnost již na slušné úrovni.
Zajistíme si tím:
Komentáře
Kdokoliv může přidávat komentáře ke článkům bez registrace. Zadá si libovolnou přezdívku a napíše komentář.
SSL pro weby od 11/2015 zdarma
MS WINDOWS 10, 11 - sběr informací o uživateli
DEBIAN 12 (bookworm) - OS zdarma debian vyšel 10.6.2023
debian - stáhnout nejnovější DEBIAN pro PC
debian edu - debian pro školy a školní prostředí, stažení DEBedu (torrent)
Zranitelnost „ROM-0“ routerů
Předali data tajným službám
Americké bezpečnostní agentuře (NSA) předali data Microsoft, Yahoo, Google, Facebook...
Itálie preferuje open source
Italský parlament schválil zákon, který nařizuje státním institucím pořizovat otevřený software před komerčním. To znamená LINUX místo MS-WINDOWS, LIBRE OFFICE místo MS OFFICE atd.