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. Dokonce ani zašifrovaná hesla s možností dešifrování není doporučováno ukládat do databáze. 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č.
Doporučený postup je ukládat hesla jako výtah zprávy (otisk zprávy), hash (v doslovném překladu
„zmrvení zprávy“). Je to jednosměrné upravení zprávy (řetězce), které již zpětně nejde dešifrovat, Secure Hash Algorithm = SHA512, nověji ukládat hesla pomocí password_hash("moje tajne heslo", PASSWORD_DEFAULT).
Ale bezpečnost hashí se mění, dříve používané hashe MD5, SHA1 jsou již označené jako překonané a jsou bezpečnostním rizikem. Doporučuje se pro hesla již SHA512. Ale ještě lepším, bezpečnějsím a jednodušším zůsobem je použití PHP funkcí password_hash a password_verify.
Ukázka výtahu zprávy SHA512 v PHP
<?php
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).
Například heslo 123456 má 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. Tento problém se řešil(ší) solením. Solením se dosáhne toho, že stejná hesla různých uživatelů mají jiné „výtahy zprávy“ (hashes). Duhové (Rainbow) tabulky fungují pouze proti nesoleným hashům.
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
//...
$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í.
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
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:
ba8dbaecd78d762799715e5fe4559428a1e4b14c68ada9f5581b868c3102243fd485ef6a3239...
ba8dbaecd78d762799715e5fe4559428a1e4b14c68ada9f5581b868c3102243fd485ef6a3239...
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).
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.
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
Kde:
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.