Složitost hesel

Školení, která pořádám

Při ukládání hesel je velice důležité neukládat hesla v čistém textu, ale nějak zakódovaná. Pokud by se totiž někdo dostal k databázi uživatelů (což může být např. oprávněný správce aplikace), získal by všechna hesla. Pro kódování hesel se obvykle používají hašovací funkce md5 nebo sha1.

Aby nebylo možné odhalit uživatele se stejným heslem a aby se nedaly použít databáze předpočítaných hašů, připojuje se k heslu ještě dostatečně dlouhý náhodný řetězec (nejlépe algoritmem HMAC).

S narůstajícím výkonem počítačů je ale velice důležitá také složitost samotného hesla. Na průměrném dnešním počítači trvá vypočtení hašů všech řetězců sestavených z malých písmen o délce 5 znaků pouhých 20 sekund. Zhruba stejně dlouho zabere vypočítání všech hašů sestavených z číslic a malých a velkých písmen o délce 4 znaky. Za jednu sekundu lze tedy v PHP bez jakékoliv optimalizace ověřit kolem půl miliónu hašů.

<?php
/** Nalezení MD5 otisků dané délky
* @param array pole testovaných znaků
* @param int délka hledaného řetězce
* @param array pole s hledanými otisky (otisk => hodnota)
* @param string společný začátek otisků
* @return null vypsání nalezených otisků
* @copyright Jakub Vrána, https://php.vrana.cz/
*/
function test_md5($characters, $depth, $search = array(), $prefix = "") {
    foreach ($characters as $ch) {
        if ($depth > 1) {
            test_md5($characters, $depth - 1, $search, $prefix . $ch);
        } elseif (isset($search[md5($prefix . $ch)])) {
            echo $search[md5($prefix . $ch)] . ": $prefix$ch\n";
        }
    }
}
$search = array(md5("heslo") => "login");
$characters = range("a", "z");
for ($i=1; $i <= 5; $i++) {
    test_md5($characters, $i, $search);
}
?>

Algoritmus počítá haše vždy od začátku řetězce. Díky inkrementální povaze hašovacích funkcí by se dalo využít vypočteného společného prefixu (např. u hesel abcd1 a abcd2). V PHP by se k tomu mohla použít trojice funkcí hash_init, hash_update a hash_final, poslední jmenovaná ale bohužel další aktualizaci haše znemožní.

Pro alespoň základní bezpečnost hesla je nutné po uživatelích chtít heslo délky nejméně 7 znaků sestavené z číslic a malých a velkých písmen (jeho odhalení by na jednom počítači trvalo průměrně 40 dní). Pokud přidají i nějaké speciální znaky a případně diakritiku, požadavky na délku bezpečného hesla klesají.

Přijďte si o tomto tématu popovídat na školení Bezpečnost PHP aplikací.

Jakub Vrána, Dobře míněné rady, 26.5.2008, diskuse: 54 (nové: 0)

Diskuse

pepak:

Jenže donutit uživatele k použití dostatečně dlouhých hesel je obrovský problém - jediný větší je, jak je donutit k použití dostatečně dlouhých bezpečných hesel. Takže existují i cesty, jak ochránit krátká a slabá hesla. Například by šlo tu hashovací funkci postupně použít třeba 1000x - protože to bude muset udělat i útočník, pro každé zkoušené heslo, a urychlit se to nedá...

ikona Jakub Vrána OpenID:

Tisícinásobné zopakování hašovací funkce nesníží nároky na délku hesla ani o dva znaky. A naopak přináší možné další komplikace, především riziko vzniku cyklů (zatím jen teorie, ale to nemusí trvat věčně).

pepak:

To je sice pravda, ale narozdíl od přesvědčení uživatele, aby svoje heslo o ty dva znaky prodloužil, to je realizovatelné. Stejně drtivá většina uživatelů použije jednoduché slovníkové heslo, takže ho najdu tak do 10000 pokusů...

Petr Procházka:

Opakováním hashe se zvedá pravděpodobnost vzniku kolize. (Nejsem odborník ale někde jsem to četl ;], opravte mě někdo jestli to není pravda)

pepak:

Jednak to není pravda, a druhak zrovnav případě hesel by to bylo úplně jedno, i kdyby to pravda byla. Kolize v hashi jsou problém tehdy, když útočník původní zprávu (to, co se hashovalo) zná - např. při podepisování dokumentů. Pak může původní zprávu nahradit jinou a zfalšovat na ní podpis. U hesel na tom nezáleží.

ikona Jakub Vrána OpenID:

To je hodně odvážné tvrzení. Skutečnost je taková, že u současných funkcí se prostě neví, jestli při opakované aplikaci vznikají cykly nebo ne.

A vznik cyklů samozřejmě vadí i u hesel. Představ si, že po dostatečném počtu aplikací hašovací funkce by jakékoliv heslo dostalo jeden ze tří hašů A, B nebo C, protože md5(A) = B, md5(B) = C a md5(C) = A. Pak by na původním hesle ani nezáleželo.

pepak:

Já jsem ale nepsal o cyklech ale jen o kolizích. U těch _víme_, že na nich pro účely ukládání hesel nezáleží. U cyklů by na tom sice záleželo, ale u těch ani nevíme, jestli mohou vzniknout...

ikona Jakub Vrána OpenID:

Petr Procházka použil špatný termín a ty místo abys opravil ten termín, tak ses ho chytil a smysl sdělení zcela obrátil.

Navíc kolize v tomto rozsahu by vadily i pro ukládání hesel. Může mi být jedno, že dokážu najít dvě zprávy se stejným otiskem (klasická kolize), ale už mi nemůže být jedno, že tisíce hesel se hašuje do stejného otisku (kolize vzniklá opakováním hašovací funkce).

pepak:

Mimochodem, TrueCrypt používá pro vytváření klíče ke hlavičce souboru 1000 nebo 2000 iterací (podle toho, která hashovací funkce je použita). Většina používaných šifrovacích programů také používá podobné postupy, i když ne úplně stejné. Zatím jsem nikde nečetl, že by to negativně ovlivňovalo bezpečnost.

ikona Jakub Vrána OpenID:

Máš pravdu – nikdo zatím nepřišel na to, jak toho zneužít. Ale ví se, že by to zneužít jít mohlo (právě díky možnému vzniku cyklů).

Takže opakovaná aplikace hašovací funkce momentálně zvýší náročnost útoku hrubou silou, ale do budoucna může bezpečnost naopak podstatně zhoršit. Je to zkrátka poněkud riskantní.

pmg:

Nedalo by se riziko snížit postupným použitím více hašovacích algoritmů (http://cz2.php.net/hash_algos), kdy by se každý mezivýsledek znovu spojil s hašovanou hodnotou?

ikona Jakub Vrána OpenID:

Nijak zásadně. Útočník by hesla o délce 5 znaků nezjistil za 20 sekund, ale třeba za 40.

pmg:

Pro zpomalení útoku hrubou silou by se použilo vícero iterací. Kombinací několika algoritmů bych chtěl ztížit nalezení cyklu. Podobný účinek by podle mělo také přidávání původní hodnoty k haši. Nebo ne?

<?php
$password
= 'p4ßw0rD';
$result = sha1($password);

for (
$i = 0; $i < 100000; $i++) {
    $result = sha1($result . $password);
}

echo
$result;

Ondrej Ivanic:

jedine co staci je heslo osolit :). Bud cez zmieneny HMAC alebo len jednoducho:
$hash = md5($heslo.$login);

To ze nieco 1000x zahashujem mi nic nepomoze lebo pomocou predvypocitanych tabuliek najdem heslo napr. heslo '\x00\x35\Á4r\27' ale povodne bude 'superman' ktore ma hash rovnaky ako '\x00\x35\Á4r\27'...

pepak:

Akorát že to nalezené heslo ti bude k ničemu, protože potřebuješ, aby prošlo tou tisícovkou hashů, ne jen jedním. Solením se samozřejmě nic nezkazí, ale pomůže to právě jen proti vyhledání hesla v tabulkách, ne k zesílení slabého hesla.

ikona Jakub Vrána OpenID:

Tento článek pojednává o tom, že osolit heslo bohužel právě nestačí. Pak ho sice nenajdu v tabulkách, ale díky slušnému výkonu dnešních počítačů si ho můžu rychle najít sám.

Spíš než login (který bývá krátký a jednoduchý) je vhodné použít nějakou dostatečně dlouhou a pestrou sůl – nejlépe náhodně vygenerovanou. Jinak totiž hrozí, že v tabulkách najdu i $heslo.$login (obzvlášť při použití obyčejného zřetězení).

Opakované hašování použití tabulek zabrání.

ikona ehmo:

tento sposob sa nazyva saltovanie a je stary ako pocitacova kryptografia http://en.wikipedia.org/wiki/Salt_%28cryptography%29

kazdopadne sa zacal pouzivat az v poslednych mesiacoch (potazmo rokoch). heslo sa da samozrejme ochranit uplne inym sposobom, ked na dlzke nezalezi, chce to trosku vynaliezavosti a samozrejme, ze to uzivatelovi zneprijemni zivot.

saltovanie sa vyuziva hlavne preto, aby napriklad pri 32 miestom (256bit) md5 nebolo mozne dopocitat realnu hodnotu. kedze hash by mal byt len identifikatorom zdroja a nie jeho presnou kopiou (stale hovorime o zakryptovanej casti). preto ak je heslo kratsie ako 32 znakov, je ho mozne spatne dopocitat. na to sa vyuziva saltovanie, kedy pri uplne jednoduchej logike $heslo.$salt pri zachovani strlen($heslo) + strlen($salt) > 32. najcastejsie sa vklada do saltu veta, ci odstavec textu. takto nie je realne mozne ziskat akekolvek informacie z hashe, pretoze neobsahuje svoj zdroj. aby sa vsak utocnikovi nepodarilo vyskusat bruteforce ani hesla na formulari, je jednoducho potreba obmedzit pocet pristupov. aby ste co najmenej nicili uzivatela, tak to byva riesene cez ip. ak su viac nez tri zle prihlasenia z jednej ip, je tato ip zablokovana. dnes vsak nie je ziadny problem ziskat stovky ci tisice proxy, takze ip je dost slaba ochrana. potom je mozne samozrejme pouzit zablokovanie konta, ktore je mozne obnovit len ak uzivatel otvori linku v emaili, ktora mu konto obnovi. je to opruzujuce pre uzivatelov, ale funkcne. aj tu je vsak riziko sniffnutia emailu a tak je to mozne riesit bud zadanim prvotneho hesla, alebo dokonca doplnkoveho hesla, najlepsie meno matky za slobodna, alebo ina osobna informacia, ktoru vie len uzivatel. cim viac bude potom autor webu chranit svojich uzivatelov, tym viac sa budu stazovat podmienky nielen pre utocnikov, ale aj pre samotnych uzivatelov. dnes je v tomto najdalej paypal, ktory kazdy den straca radovo desiatky ci stovky zakaznikov, pretoze ich ochrana dnes uz pomaly ani neumoznuje vyuzivat aplikaciu (web).

Petr Procházka:

Jméno svojí matky za svobodna nepovažuju za bezpečné slovo. Zná ho přinejmenším celá moje rodina. a potencionálního útočníka to zdrží možná na pár minut. Zbožňuju když po mě nějaká registrace vyžaduje podobně triviální ověření, jako jméno matky za svobodna, jméno třídní učitelky na základní škole atd.

zipper:

Pokud na takovou otázku odpovíš "nesmyslně", tedy opět nějakým heslem, pak útočníkovi dost výrazně komplikuješ život, protože bude hledat (aspoň chvíli) smuslypnou odpověď.

Petr Procházka:

Jasně to udělám já. Jak ale při registraci odpoví běžný uživatel?

ikona ehmo:

to je samozrejme mozne, je to vsak bezna prax. ak chces skutocnu bezpecnost, bavme sa o vygenerovanych heslach dodrziavajucich pravidla bezpecneho hesla a samozrejme tokenoch, ktore vytvaraju docasne hesla pre overenie dalsej identity. existuje mnoho sposobov, kazdy dalsi vsak uzivatelov len znechuti a odradi. preto vzniklo openid (ktore sa dnes vsak este stale potyka s velkymi bezpecnostnymi problemami). kazdopadne za nejaky cas sa to snad zmeni a bude to o niecom inom.

icoach: prelomena ano, ale opat len z kryptografickeho hladiska. to ze bola najdena kolizia nie je vyznamne dolezite z pohladu webu. ak je heslo saltovane, jeho decryptovanie je nemozne.

martin: mas pravdu, neprijemne som sa pomylil.

icoach:

Samozřejmě, že kolize při hashování hesel nás nezajímají. Ty nás zajímají při podvrhování podepsaných dokumentu, certifikátů apod.

Zajímalo by mě, jak chcete dopočítat heslo kratší než 32 znaků?
Při použití hashovací funkce jako pseudonáhodné funkce se chová jako náhodné orákulum. Vyznačuje se právě tím, že není možné dopočítat z obrazu původní vzor.

ikona Axiss:

Pochybuju, že si potencionální útočník bude zjišťovat jméno tvé matky za svobodna :) Takový člověk prostě získá data a potom se snaží napáchat co nejvíc škody. Aby zjišťoval totožnost každého jednotlivého uživatele, na to asi mít čas nebude. Pokud tedy uvažuju klasickou situaci, kterých je ale drtivá většina.

ikona v6ak:

Tady jde asi o něco jiného: To se jménem matky za svobodna se netýká útoku, kdy někdo získal DB...

Martin:

Tech 32 znaku v MD5 je ctyrbitovych, takze jen "128bit"

ikona Jakub Vrána OpenID:

Význam a použití saltu je popsán v odkazovaném článku http://php.vrana.cz/ukladani-hesel.php, zde je proto jen krátké shrnutí motivace jeho použití.

Můžeš odkázat důkaz tvé věty „ak je heslo kratsie ako 32 znakov, je ho mozne spatne dopocitat“? To by byl skutečně převrat v kryptologii.

Při použití saltu musí platit nerovnice strlen($heslo) + strlen($salt) > X, kde X je maximální délka v Rainbow table v daném rozsahu znaků. Např. za 1000 USD se dají koupit MD5 otisky v rozsahu [a-z0-9] do délky 8 znaků. Tebou uvedená nerovnice je pomýlená.

Gimli2:

Proc bych mel neco kupovat za 1000USD, kdyz to zhruba za 120 hodin vyzkousim hrubou silou/nageneruju v dnesni dobe na obycejnem PC? ;-) (a s 50% sanci to najdu uz za 60 hodin)
(vychazim z rychlosti programku md5crack, ktery zvlada zhruba 6.5M hashu/s na 64bit athlonu na necelych 2GHz; celeronM 1.4GHz - okolo 2.5M hashu/s)

TT:

Už nikdy nenapíšu jméno svojí matky za svobodna !!!  Tuto větu napíšu 1000 krát !

icoach:

Čistě na okraj... MD5 je považována už dlouhou dobu za prolomenou a nedoporučuje se používat (samozřejmě neplatí to pro všechny případy kryptografie). Za bezpečné se považují hashovací funkce SHA-1, SHA-256, atd.
Zmínil jsem, že to neplatí pro všechny případy. Např. HMAC s použítím MD5 je bezpečný díky použití tajného klíče, ale to se netýká hashování hesel.
SHA-1 má tu nevýhodu, že je výpočetně pomalejší. MD5 je však už minulost...

pepak:

SHA1 je na tom jen o málo lépe než MD5. Ostatní hashe z rodiny SHA jsou zatím v pořádku.

Nalezení kolizí v hashi je zrovna pro případy hashování hesel zcela nepodstatná věc, která bezpečnost nijak neovlivňuje - i kdyby se používalo jenom triviální $hash = md5($heslo); (to se nedoporučuje používat z jiných důvodů - protože pro MD5 už existují rozsáhlé tabulky hashů k různým vstupům, ve kterých se dá snadno vyhledávat, ale od toho právě máme solení).

Robert Vlach:

Zajímavý článek, díky za něj! Docela by mne, Jakube, zajímal tvůj názor a případné doporučení pro ukládaní dešifrovatelných hesel do databáze. Myslím, že je to celkem běžná praxe i u větších serverů ("zaslání ztraceného hesla").

pepak:

"Zaslání zapomenutého hesla" je daleko lepší řešit tím, že se vygeneruje nové náhodné heslo a to se pošle uživateli. Klasická dešifrovatelná hesla stojí a padají s tím, že se nikdy nikdo nedostane k vaší databázi - a na to bych si tedy nevsadil (vždycky je tu admin webserveru, když už nikdo jiný...).

ikona Jakub Vrána OpenID:

Vygenerování náhodného hesla je problematické v tom, že útočník může legitimního uživatele otravovat tím, že se za něj bude vydávat a neustále prohlašovat, že zapomněl heslo. Pokud to bude dělat dostatečně často, znemožní oprávněnému uživateli přihlášení.

Místo toho se proto raději generuje token dovolující heslo změnit i bez znalosti starého. Ten nezabraňuje použít ani staré heslo.

ikona Jakub Vrána OpenID:

Jak píše pepak, obvykle se spíše posílá „návod pro nastavení zapomenutého hesla“. Co se ukládání dešifrovatelných dat týče, popsal jsem ho v článku http://php.vrana.cz/ukladani-citlivych-informaci.php.

Robert Vlach:

Díky synci, zamyslím se nad sebou a svojí aplikací :-)

Tomáš Fejfar:

Teď přemýšlím, jak by pomohlo něco jako <?php sha1(md5($password.$hmac).$hmac) ?>
Protože tim získáme vlastně <?php strlen(md5()) + strlen($hmac) ?> dlouhé heslo - tedy bezpečné, ne ?

ikona Jakub Vrána OpenID:

Heslo je bezpečné v tom smyslu, že ho nenajdu v předpočítaných tabulkách. Ale tento článek upozorňuje na to, že s výkonem dnešních počítačů není problém najít krátké heslo i bez tabulek.

D Jakub :

Nejsem žádný odborník ani na PHP, ani na šifrování, ale teď mě tak napadlo: co chtít po uživateli, aby používal dvě jednodušší hesla, než jedno těžko zapamatovatelné, k jehož používání ho stejně většinou nedonutíme? Při loginu by samozřejmě musel zadat obě hesla a určitě se hůř luští zašifrované "slunicko13kyticka15" než jen např. "kyticka15"

Marty:

Uz takhle uzivatele obtezuje pouzivani hesel a pridanim dalsi kolonky k vyplnovani by te rozhodne nemeli radi. Podle me by mohlo take prestat fungovat automaticke vyplnovani prihlasovacich udaju v prohlizeci (velka nevyhoda z meho pohledu).
A co nyni brani uzivateli, aby sve heslo spojil z nekolika jednodussich tim, ze je napise za sebe? ;-)

kozotvor:

Druhá Jakubova věta - .."např. oprávněný správce aplikace".. dá se vůbec napsat aplikace, která by byla 'útokuvzdorná' vůči oprávněnému správci aplikace (který dejme tomu může zasahovat do databáze ale ne do kódu) a současně měla nějaké automatizované opatření proti zapomenutí hesla?

ikona Jakub Vrána OpenID:

Pokud má správce přístup k databázi a ne ke kódu, tak se dá salt (nebo jeho část) umístit do kódu. Správce si pak nemůže vygenerovat nový otisk hesla, protože nezná salt.

ikona David Grudl:

Nepomohla by v PHP 5.3 nová funkce hash_copy? http://php.net/hash_copy

ikona Jakub Vrána OpenID:

To je přesně ono, díky za tip. Nicméně jsem to vyzkoušel a podle předpokladu je to asi o 80 % pomalejší než přímočaré řešení. Hodilo by se to tedy pouze v případě, kdy by byl kontext dostatečně velký.

Honzík:

Já se přiznám že jsem příliš nepochopil tyto komentáře

Když už se útočníkovi podaří prolomit zabezečení databáze, už je mu úplně jedno jestli vidí hesla v čitelném tvaru nebo zakodovaném. Prostě smaže co půjde

ikona Jakub Vrána OpenID:

1. Útočník může přístup k datům získat jen pro čtení, např. ze zálohy.

2. Smazání všech dat nemusí být největší problém (dají se obnovit ze zálohy). Kompromitace serveru resp. jeho uživatelů může být mnohem závažnější.

Pavel:

Napadla me jeste jedna takova mala moznost, jak lepe zabezpecit heslo. Vychazi z predpokladu, ze heslo samotne nema tri znaky.-) a zaroven utocnik nema pristup ke zdrojovemu kodu PHP. Coz by mu to ulehcilo.
Premyslel jsem ze, kdyz bude token umisteny v db u uzivatele, tak utocnika napadne vyzkouset tabulku hashu s tokeneme pred nebo za heslem. Tudiz jsem si rekl, ze uzivatelske heslo rozseknu na dve nebo klidne tri casti. A bude z toho napriklad ve vysledku sha1 nebo md5 tohoto vzorce (predpokladejme, ze me heslo je jarnikvetina). jaTOKENrnikvTOKENina

Tudiz se hash dostatecne zkomplikuje a jeho nalezeni preci jen bude pomerne slozitejsi.

Pan Hromádka:

Řekl bych, že hashování neznamená, že by měl útočník dostat do rukou jeho hash. Sloupec, kde jsou hashe je podle mě důležité pořádně schovat.

ikona Jakub Vrána OpenID:

Samozřejmě je vhodné data co nejlépe zabezpečit. Hašování řeší situaci, kdy se k nim útočník přesto dostane.

David:

Zdravím, co když použiji třeba SHA-256 a výsledek potom oříznu třeba o první 4 znaky přilepím na začátek unikátní ID a pak znova proženu SHA-256? Tak přece i když znám to unikátní ID tak se k původnímu heslu nedopočítám ani hrubou silou ne?

ikona Jakub Vrána OpenID:

Spoléháš se na neznalost algoritmu útočníkem. Jde o princip Security by Obscurity. Problém je ten, že útočník algoritmus může získat taky (nebo si ho odvodit).

Přijď si o tom popovídat na školení o bezpečnosti.

Radecek:

Nevim jeslti se mi to zda, ale pri spatne autentizaci linuxu trva delsi dobu nez se prihlasi pri spravnem hesle. Tedy pro omezeni robotu generujici a snazi se prolomit autentizace - usleep(par vterin). Co si myslite o takovem reseni?

ikona Jakub Vrána OpenID:

Navenek se to takhle chovat může. Problém nastane, když se někdo dostane přímo k uloženým datům.

Matej:

Ak má útočník plný prístup k databáze, je možné si vygenerovať hash známeho reťazca, ktorým nahradí heslo v databáze - a má plný prístup k užívateľovi.

ikona Jakub Vrána OpenID:

To samozřejmě ano, ale útočník často získá přístup jen pro čtení případně nějakou starší zálohu.

Vložit komentář

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2018 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.