Ukládání hesel

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

Pokud se uživatelé ve webové aplikaci mohou registrovat, obvykle pro jejich přihlášení požadujeme heslo, které se typicky ukládá do databáze. Jednou ze závažných chyb je ukládat toto heslo bez jakéhokoliv kódování tak, jak ho uživatel zadal. Umožňuje to sice jednoduchou implementaci funkce poslání zapomenutého hesla, ale skrývá to riziko hromadného získání všech uživatelských údajů – a to přinejmenším autorem aplikace a správcem serveru. Proto je vhodné místo celého hesla ukládat pouze jeho hash a při ověřování kontrolovat hash zadaného řetězce s hashem uloženým.

Připomínám, že hashovací funkce převedou jakýkoliv řetězec na hash pevné délky (např. sha1 vrací vždy 40 znaků), ze kterého se původní řetězec nedá v rozumném čase zpátky zjistit. Navíc by nemělo být možné v rozumném čase najít jiný řetězec, pro který funkce vrátí stejný hash.

<?php
// kontrola nebezpečně uloženého hesla
mysql_query("SELECT * FROM uzivatele WHERE login = '" . mysql_real_escape_string($_POST["login"]) . "' AND heslo = '" . mysql_real_escape_string($_POST["heslo"]) . "'");

// kontrola bezpečně uloženého hesla
mysql_query("SELECT * FROM uzivatele WHERE login = '" . mysql_real_escape_string($_POST["login"]) . "' AND heslo_sha1 = '" . sha1($_POST["heslo"]) . "'");
?>

Byť se tento způsob ukládání hesla dá v podstatě považovat za bezpečný, skrývá ještě jedno riziko. Tím je kolize hesel – když dva uživatelé zadají stejné heslo, tak má i stejný hash a když to jeden z uživatelů jakkoliv zjistí, může se přihlásit i na druhého uživatele. Řešit se to dá např. tak, že se pro každého uživatele vygeneruje náhodný řetězec, který se s heslem vhodně smíchá. Metoda smíchání není kritická a nebylo prokázáno, že např. pouhé zřetězení náhodného řetězce s heslem sníží bezpečnost celého řešení.

<?php
// zjištění bezpečně uloženého hesla spojeného s náhodným řetězcem
mysql_query("SELECT * FROM uzivatele WHERE login = '" . mysql_real_escape_string($_POST["login"]) . "' AND heslo_sha1 = SHA1(CONCAT('" . mysql_real_escape_string($_POST["heslo"]) . "', salt))");
?>

Všimněte si, že SHA1 je nyní volaná jako MySQL funkce (byla přidána v MySQL 4.0.2). Ve sloupci seal salt je uložen náhodný řetězec, který jsme vygenerovali při vytváření uživatele.

sha1 není jediná hashovací funkce, v PHP je k dispozici např. také md5. U této funkce ale byly objeveny bezpečnostní trhliny, které sice přímo nenarušují tento způsob použití funkce (to, že k jednomu řetězci lze objevit jiný řetězec se stejným hashem, vadí spíše při využití funkce k podepisování zpráv), ale já osobně funkci už nepovažuji za bezpečnou.

Při změně hesla je vždy vhodné požadovat i heslo původní. Tím zabráníme tomu, že přihlášenému uživateli během jeho chvilkové nepřítomnosti u počítače někdo změní heslo a tím získá k aplikaci pohodlný přístup.

<?php
// změna hesla uživatele
mysql_query("UPDATE uzivatele SET heslo_sha1 = SHA1(CONCAT('" . mysql_real_escape_string($_POST["heslo"]) . "', salt)) WHERE login = '" . mysql_real_escape_string($_POST["login"]) . "' AND heslo_sha1 = SHA1(CONCAT('" . mysql_real_escape_string($_POST["heslo_puvodni"]) . "', salt))");
?>

Pokud uživatel zapomene heslo, nemůžeme mu původní heslo samozřejmě nijak poslat. Řešit se to dá tak, že se uživateli vygeneruje a pošle heslo nové, to ale nahrává vtipálkům, kteří dostanou do ruky nástroj, jak změnit hesla ostatních uživatelů. Lepší je proto uživateli vygenerovat a poslat náhodný řetězec, pomocí kterého si bude moci zadat nové heslo. Pokud to neudělá, zůstává v platnosti heslo staré.

Viz také novější článek Ukládání hesel bezpečně.

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, 13.4.2005, diskuse: 76 (nové: 0)

Diskuse

ikona dgx:

seal může být teoreticky i login, protože ten se lišit musí vždy

Solvina:

Seal, seal... Nemyslis nahodou salt? :-) Takove male hnidopistvi po ranu potesi a zahreje.

Jinak sem rad, ze pises tyhle spoty. Nekdy mam vylozene chut tady ty, mozna triviality, mozna absolutni zaklady (kazdeho) dobreho programovani a zvlast v PHP, nekomu omlatit o hlavu. Zvlast lidem co se PHP pred trema rokama za tyden naucili a do ted nezmenili nic ze svyho prgovaciho stylu.

ikona Jakub Vrána OpenID:

Jasně, salt, opravil jsem to.

RD:

Já používám dotaz na heslo PASSWORD($_POST[heslo]) ukládám jej do DB stejným způsobem, je to bezepečné?

ikona Jakub Vrána OpenID:

Ano, to je stejný princip jako hashování bez saltu. Jenom pozor na to, že v MySQL 4.1 se chování této funkce změnilo - měla by být bezpečnější a vrací delší řetězec.

ikona spaze:

SHA-1, SHA-1.. http://crypto-world.info/news/index.php?prispevek=1251 a http://crypto-world.info/news/index.php?q=…=vyhledavani :)

Kdyz uz _neosoleny_ hashe (do kterych patri i hash, se kterym je _sul_ ulozena hned vedle nej -- mimochodem, uplne mi unika, jak tento zpusob muze zvysit bezpecnost), tak snad SHA160 a vejs (Mhash extenze), ale i presto bych jednoznacne doporucoval pouziti crypt() (pouziti viz manual).

"původní řetězec nedá v rozumném čase zpátky zjistit." za tim bych si taky tak nestal, bezny hesla, ktery uzivatele maji, se daji zjistit v rozumnym case. Pochopitelne zalezi na delce a na pouzitych znacich, ale hesla typu [a-zA-Z0-9]{6} jsou otazkou nekolika minut. Samozrejme, zalezi na vypocetni sile, ale v dnesni dobe botnetu to neni zas takovej problem :)

cili zaver: jedine crypt()

ikona spaze:

jeste k ty vypocetni sile: na ty hesla, ktery jsem psal, zas nemusi bejt nijak brutalni. Util mdcrack mi zvladla generovat na W2k na P2.4 tusim neco kolem 4mega hashu/sec (brat s rezervou, uz je to nejakej patek ;) a s tim uz se da neco delat.

ikona Jakub Vrána OpenID:

To, že SHA-1 má také problémy, mi je známo. Ale schopnost během 2^66 operací generovat dvě kolidující zprávy se pokud vím pro uhádnutí hesla z hashe nedá nijak použít (nehledě na to, že 2^66 operací je pořád dost).

Poznámka o možnosti uhodnutí jakkoliv zahashovaného _špatného_ hesla je zcela na místě a v článku by to mělo být zmíněno.

Smíchání hesla se solí (byť uloženou hned vedle) vede k tomu, že útočník ani podle dat v databázi nemůže najít uživatele se stejným heslem. V článku je to myslím popsáno zřetelně.

Funkce crypt() je možná alternativa, ale principiální výhody oproti jiným hashovacím funkcím pokud vím nemá.

ikona spaze:

Ono hlavne vsechno tohle hrani je o case. Plati pravidlo asi takovy, ze pokud lze ze zasifrovaneho textu dostat informaci pozdeji, nez je jeji platnost, tak je sifra dobra (priklad, poslu dealerovi zpravu, ze si zbozi ma vyzvednout v 16:10 tam a tam, tu info chyti ji nejakej organ, zacne lustit a lustit, jenze to rozlusti az v 16:15 a to uz mu je k nicemu), takze idelane stav je nastavit nejakou rozumnou expiraci hesla (ale je mi jasny, jak by uzivatele reagovali ;)

navic vsechno snazeni muze byt v haji ve chvili, kdy utocnik pusti UPDATE users SET passwd = '';, coz ty tady neuvadis. Jinak bych asi dopsal (a taky dopisu ;), ze ochrana hashem pomuze jen tehdy, kdyz utocnik ziska napr. dump a pouze dump (obecne pak read-only pristup k db), bez moznosti zapisu, coz je malokdy.

ad crypt(), vyhody ma ty, ze nemusis ukladat salt k udajum, proste ulozis jen vyslednej hash a pri porovnavani pouzijes heslo jako sul. OTOH, nekdo mel nejaky platformni problemy s crypt(), na nejakym systemu se to chovalo jinak, nez na jinym, podrobnosti se uz bohuzel nenachazi v me hlave :/ :)

Namet pro clanek, zkus napsat neco o prenosu hesel over-the-wire, tedy moznost SSL (na tom v podstate neni co popisovat;), ale treba prenos pres obycejny draty s vyuzitim challenge-response implementovanym na klientovi v JS. Mam working example v ostrym provozu, kdybys potreboval pomoci, najdi si me :)

ikona Jakub Vrána OpenID:

Díky za námět na článek. Já to mám dokonce už delší dobu napsaný, ale ještě jsem se neodhodlal to publikovat - chtěl bych to dát do nějakého většího média, ne jen sem.

Bezpečný přenos hesel po HTTP mám taky napsaný a v ostrým provozu, konkrétně na http://www.mysms.cz. Pokud bys k tomu měl jakékoliv postřehy, tak samozřejmě uvítám.

ikona spaze:

jak tak rychle koukam do zdrojaku, mas to pp stejne jako ja, ale radeji si pockam na clanek a zdrbnu pak ten ;P (doufam, ze tu obcasnou kritiku neberes moc vazne a mas klidny noci ;) I kdyz pravda, radeji komentuju tady nez na rootu..

ikona Jakub Vrána OpenID:

Z tvé kritiky mám víceméně radost, protože je v drtivé většině konstruktivní. Když si mám vylít srdíčko, tak mě vadí jenom komentáře typu "článek je o hovně" a ještě víc "v článku to je špatně" bez uvedení důkazů (diskutující také nakonec připustil, že to je v článku dobře a mýlil se on, ale špínu kydat neváhal). To je ale na Rootu.

Jan Tichý:

"ochrana hashem pomuze jen tehdy, kdyz utocnik ziska napr. dump a pouze dump (obecne pak read-only pristup k db), bez moznosti zapisu, coz je malokdy."

To není zase až taková pravda, je tu ještě jedna dost silná výhoda. Drtivá většina uživatelů používá jedno a totéž heslo všude. Pokud mám nějakou naprosto bezvýznamnou aplikaci a někdo se mi dostane do databáze s oteřenýma heslama, získává možnost se většině mých uživatelů přihlásit i do úplně jiných aplikací, jako jsou webmaily nebo nedejbůch internetové banky. Pokud hesla ve své aplikaci zahashuju, chráním své bfu uživatele a jejich případná další konta.

barguzin:

SPAZE
Ten prenos hesla po obycajnom drôte ma celkom zaujal (JavaScriptom), existuju nejaké Hash alebo Crypt f-cie aj v JavaScripte?Aký je princíp,môžeš mi poslať zdroják (ak nie je súčasťou obch.tajomstva),pkk@post.sk Vďaka

ikona spaze:

Huh, tak koukám, že jsem si něčeho nevšiml :P Za to se omlouvám, i když tuhle omluvu si barguzin už asi nepřečte.

Kdyby to stále někoho zajímalo, tak ano, hashovací funkce v JS existují, např. http://anmar.eu.org/projects/jssha2/ a někde existuje i ten crypt (asi tohle http://whereswalden.com/tech/internet/javacrypt/) a princip je takovej, že server vygeneruje výzvu (challenge), zapamatuje si ji a pošle na klienta. Ten vezme výzvu, přidá k ní heslo, udělá hash a odešle jen ten hash (response) a ne heslo. Server dostane hash, stejným způsobem si vypočítá hash z výzvy a jemu známému heslu a tento musí sedět s odpovědí z prohlížeče.

Jan Tichý:

"Kdyz uz _neosoleny_ hashe (do kterych patri i hash, se kterym je _sul_ ulozena hned vedle nej -- mimochodem, uplne mi unika, jak tento zpusob muze zvysit bezpecnost)"

A jak funguje systém se solí, která není uložena vedle? Kde tu sůl vezmu?

"cili zaver: jedine crypt()"

Ano, PHPčkovský crypt() neukládá sůl vedle, ale ukládá si svoji dvouznakovou sůl jako první dva znaky výsledného hashe. Čili sůl je u něj úplně stejně čitelná, jako bych ji uložil do separátního atributu.

ikona spaze:

Ups, svatá pravda. Teď mi je zas pár věcí jasnějších ;) Díky.

Jen jako doplnění, crypt() nemusí mít salt jen dvouznakovej, záleží na tom, co je k dispozici (viz konstanty CRYPT_STD_DES, CRYPT_EXT_DES, CRYPT_MD5, CRYPT_BLOWFISH, CRYPT_SALT_LENGTH).

jaja:

asi sem blbej, ale nejak mi nedochazi to s tou soli. Vezmu heslo pridam k nemu nahodne vygenerovanou sul a to si ulozim do databaze. Potom se nekdo pokusi zadat heslo ja k nemu pridam nahodne vygenerovanou sul, ale jinou nez tu puvodni a hash kody mi nebudou souhlasit. Pokud bych si puvodni sul v db ukladal spolu se zahesovanym heslem tak nevidim zadne zvyseni bezpecnosti. Ale nejspis jsem neco nekde nepochopil.

ikona Jakub Vrána OpenID:

Sůl se k jednotlivým záznamům uloží. Zvýšení bezpečnosti spočívá v tom, že není poznat, kteří uživatelé mají stejné heslo. Pokud by útočník znal nějaká hesla, tak bez soli zjistí i uživatele, kteří mají stejné heslo.

Krispin:

toto mi teda taky nebylo moc jasny o tom se stejnym heslem...jakym zpusobem to utocnik muze zjistit? Dejme to mu, ze sem utocnik a znam svoje jmeno a heslo...a to s tou soli je ochrana proti tomu, abych nezadaval nahodne vygenerovany jmena (nebo jinak ziskany) se svym heslem??

ikona spaze:

zaregistruju se jako user/heslo (l/p), kouknu do prave ziskaneho dumpu db a vidim, ze se to ulozilo jako user/ABCDEF, jednoduchym hledanim zjistim, kdo dalsi ma v db ABCDEF a presne ten ma heslo "heslo".

Pokud to _nejakym_ zpusobem osolim, dvakrat zadany "heslo" bude pokazdy jinak ulozeny (jednou jako ABCDEF, podruhy jako DEADBEAF, pak jako CAFEBABE apod.)

Poky:

A taky pokud hrubou silou vygeneruje řekněme milióny hashů pro danou sůl, tak je nemůže použít u jiných uživatelů, protože ty mají jinou sůl.

sirt2:

POKY: Ale když se podívá do PHP-zdrojáku (a proč by nemohl, když už si prohlíží DB :-)), jak se s hesly pracuje, uvidí, jak se to tam "solí", a může stejně tak "osolit" svoje generování hashů.
Každopádně je ale jeho postup ztížený - a o to tu jde.

p:

davat do podminky v SQL dotazu heslo neni dobry zpusob. ne kazdy vi co je injection, ne kazdy se ji snazi eliminovat atd. vynechanim testovani hesla v prubehu dotazu a prostym dotazem pouze na uzivatelske jmeno (heslo je porovnano skriptem - pokud byl vracen prave jeden zaznam) je v dusledku bezpecnejsi metoda. nelibi se mi ucit lidi spatnym navykum. nerejpal bych, kdyby autor blogu vybral jine motto :). takhle mi to neda, sorry ;)

ikona Jakub Vrána OpenID:

O nic bezbečnější to není. Pokud je skript náchylný k SQL injection, tak ho nezachrání, jestli je tam neošetřená proměnná použitá jednou místo dvakrát.

Jan Tichý:

To není zase až tak pravda, sic nejde vůbec o SQL injection. V některých případech běží SQL server například na jiném stroji než aplikační server a komunikace mezi nimi probíhá občas i nešifrovaně, kdy je tedy možné posílaná hesla odchytit. Lepší je tedy, pokud heslo vůbec neopustí aplikaci, prostě počítat hash přímo v aplikaci a do databáze posílat vždy už jen ten hash.

Krispin:

jinak, ja to delam takto, je to spravne, nebo spatne?

v DB mam hesla ulozena v poli typu BLOB, ukladam a ziskavam je pomoci funkci ENCODE a DECODE, hesla jsou v DB v necitelnym binarnim tvaru, vsechna jsou na nej prevedena pomoci nejakyho textovyho klice, kterej mam ulozen treba nekde v definovni paramtru stranek, nebo kdekoli jinde, fantazii ze semeze nekladou...:)

...je pravda, ze takto to delam vlastne uz od zacatku, ale nikdy nebyl duvod to menit (aspon doted ten duvod neznam)...klasickym SQL dotazem (at uzu nedejhoze pres nejakej bug ve strankach, ziskanim pristupu k DB (ne vsem adminum je radno verit, ze.:)), nebo neopatrnym zachazenim s SQL dumpem) mi hesla z toho nikdo nevytahne (pokud nezna klic), ale ja kdyz znam klic muzu zase zpetne hesla precist a odpada tak tedy posledni cast clanku se zjistenim zapomenuteho hesla...

ikona Jakub Vrána OpenID:

Takovýto způsob bych označil za špatný z několika důvodů:

1. Administrátor má přístup k heslům uživatelů a může ho zneužít (pokud má uživatel ve zvyku používat všude stejná hesla, může administrátor heslo zneužít pro přístup k jiné službě).

2. Když útočník získá master-heslo, má rázem hesla všech uživatelů, což je problém jako vrata. Master-heslo může získat např. bezpečnostní chybou v aplikaci nebo třeba nekvalitně zabezpečeným webhostingem.

3. Pokud má útočník přístup k zakódovaným heslům a master-heslo není dostatečně dlouhé a složité, může ho útočník získat hrubou silou. Tím opět získá všechna hesla v plain-textu.

Krispin:

1/ no...myslim, ze bod 1 nema smysl resit, to je o necem jinym...to nema nic spolecneho s bezpecnym uchovanim hesla...kdyz budu autor stranek a ty hesla budu chtiti, tak je proste budu mit i kdyz si je ulozim do DB zahasovany pres SHA1, nebo MD5...nikdo mi v tom nezabrani, do zdrojaku nikdo nevidi, takze muzu tvrdit cokoli...ale jak rikam, o tomto muj prispevek nebyl...jen sem chtel vedet nazor na hesla uchovavana pres ENCODE / DECODE

2/ toto jsem asi moc spravne nepochopil, jaky master-heslo? Stale jsem presvedcen o tom, ze bude-li chtit nekdo hesla ziskat, tak bude muset prolomit dve veci: musi se dosta do DB (nebo k SQL dumpu) k tem heslum a pak se musi dostat do adresarove struktury pro ten textovy klic...

3/ pokud nekdo ma pristup k heslum muze to samozrejme zkusit hrubou silou, ale bez toho aby znal aspon jedno heslo na to nikdy neprijde...krom toho muzu mit klic nejak zkombinovany treba s datem registrace uzivatele, coz ale utocnik bez prohlednuti zdrojovych kodu nebude tusit a pak je tedy klic pro kazde heslo jiny a myslim, ze prakticky nezjistitelne heslo...

Vas zpusob ukladani hesel je pravdepodobne nejbezpecnejsi, ovsem nese sebou prave problemy s nemoznosti zpetneho precteni hesla v pripade potreby, at uz je jakakoli a to myslim resi muj zpusob, samozrejme dokud budou hesla zpetne citelna neni to nikdy bezpecne, ale i tak si stale myslim, ze tento zpusob zpetne citelnych hesel uchovavani hesel je dostatecne bezpecny. nebo ne?

ikona Jakub Vrána OpenID:

Z bezpečnostního hlediska je tento koncept opravdu špatně. Pokud má někdo možnost získat přístup k heslům všech uživatelů v systému (a je jedno, jestli to je autor aplikace, správce stroje nebo zdatný útočník), je to slabina, kterou bych si nikde nedovolil. Pořád to je samozřejmě lepší než uchování hesla v plain-textu, ale za bezpečné bych se to označit neodvážil.

Hrubou silou může útočník master-heslo získat i bez znalosti byť jediného otevřeného hesla. Pokud nemá možnost se v systému sám registrovat, dá se vyjít např. z toho, že hesla nebudou obsahovat znaky s ASCII hodnotou menší než 32. Pro dostatečný počet hesel bude tuto podmínku splňovat právě jen správné master-heslo. (Útočník samozřejmě musí mít přístup k zakódovaným heslům, aby si to mohl ověřovat.)

honzar:

Ještě k tomu 1.bodu.
Logicky je nesmysl aby uživatele chránil tvůrce webu sám před sebou. V tomhle směru je podle mě potřeba důvěry. Pominu-li to že lidi používají pořád to samé heslo, tak to heslo má chránit nějaké moje data, nebo identitu. K tomu asi admin stejně přístup má...(jasně - né nezbytně)

Pokud někdo získá přístup ke skriptům, tak zase nepomůžou ani dokonalé šifry, protože si útočník může nechat posílat jméno+heslo ve chvíli kdy ho (napadený)skript přijme z formuláře (nijak nezašifrované).

ikona Jakub Vrána OpenID:

Systém má být navržen tak, aby se k heslům nemohl dostat nikdo, ani jejich správce. Spoléhat se na uvědomělost uživatelů je chyba.

Brouk:

V případě potřeby...kdy nastává případ potřeby zjištění něčího hesla? Když ho zapomene? Tento případ doporučuji řešit spíše generováním nového hesla a zasláním na e-mail. Je to lepší než mít přístup ke všem heslům mých uživatelů, někdy to svádí ;-)

p:

a proc by nemel? v pripade, ze se vrati ne jeden zaznam, je to spatne, a heslo se kontroluje spolu s uzivatelskym jmenem (vracenym dotazem) ve skriptu. nechci se mylit, ale jak tohle podvrhnout?

peta:

> ...když dva uživatelé zadají stejné heslo, tak má i stejný hash a když to jeden z uživatelů jakkoliv zjistí, může se přihlásit i na druhého uživatele. Řešit se to dá např. tak, že se pro každého uživatele vygeneruje náhodný řetězec, který se s heslem vhodně smíchá... ...heslo_sha1 = SHA1(CONCAT('$_POST[heslo]', salt))....

Chápu to správně, že přidání SALTu k heslu před spočítáním hashe v podstatě potencionálnímu útočníkovi POUZE O NĚCO ZTÍŽÍ nalezení uživatele, který používá stejné heslo (jako např. útočník nebo uživatel, jehož heslo se již útočníkovi podařilo nějakým způsobem získat)?

Pokud útočník bude postupovat tak, že pro každého uživatele v databázi (samozřejmě předpokládám, že se mu podařilo získat dump databáze) zkusí porovnat "hash(získané heslo + salt)" s uloženým heslem, tak během chvilky zjistí uživatele, kteří také používají dané heslo, tj.:

<?php
// nalezení uživatelů s heslem $ziskane_heslo
// (téměr shodné s kódem "zjištění bezpečně uloženého hesla spojeného s náhodným řetězcem")
mysql_query("SELECT * FROM uzivatele WHERE heslo_sha1 = SHA1(CONCAT('$ziskane_heslo', salt))");
?>

Nebo se mýlím? Přínos saltu z hlediska zvýšení bezpečnosti tedy vidím spíše v tom, že při získání dumpu databáze nelze prostým porovnáním hashů jednoduše zjistit, že někteří uživatelé používají stejné (dosud neznámé) heslo. I když je otázka, do jaké míry může takováto informace útočníkovi při případném odhalování hesel pomoci...

Jinak já osobně zatím používám pouze zahashovaná hesla bez saltu, o implementaci se saltem uvažuju, ale nejdříve bych rád věděl, zda se to opravdu vyplatí (přínos/cena). Pokud mě přesvědčíte, budu rád :-)

PS: Nějaký článek o tom, jak se bránit brute-force útokům na heslo, by nebyl? ;-) Něco málo obecného o tom vím (např. logování neúspěšných pokusů o login - simple, dočasné nepřijímání požadavků z dané IP adresy - už ne tak simple), ale nějak se mi nedaří na netu najít návody na konkrétní implementaci např. v PHP... (možná jen špatně hledám ;-))

Předem děkuji za reakce!

ikona Jakub Vrána OpenID:

Bez saltu jsou možnosti útoku širší: 1. Chci heslo uživatele X a vidím, že Y má stejné heslo - tak se ho jednoduše zeptám (třeba za úplatu a s tím, ať si následně heslo změní, takže nebude cílem útoku). 2. Dejme tomu, že každý uživatel zná svůj login a hash (nebo si ho na základě znalosti algoritmu může bez saltu spočítat z hesla), svůj salt ale nezná. Pokud zjistí, že má někdo stejný hash, tak při použití saltu mu to je k ničemu, bez něj má rovnou i jeho heslo.

Při získání celé databáze je přínos saltu relativně malý, ale informace o dvou uživatelích se stejným heslem se použít výše zmíněným způsobem dá.

peta:

Tak jsi mě přesvědčil :-) Ten salt brzy naimplementuju. Ta 1. možnost mě napadla dnes v noci, ještě než jsem si přečetl tvůj příspěvek, tak jsem rád, žes mi to potvrdil :-) K té 2. variantě: asi chápu, jen se mi nedaří vymyslet případ, kdy by se k hashi hesla jiného uživatele dostal jinak, než tím, že získá obsah databáze, kdy již samozřejmě s pomocí v minulém příspěvku uvedeném postupu může nalézt uživatele se stejným heslem... (?)

barguzin:

<?php

mysql_query
("SELECT * FROM uzivatele WHERE login = '$_POST[login]' AND heslo_sha1 = SHA1(CONCAT('$_POST[heslo]', salt))");

?>

Ak použijeme SHA1 spolu so saltom nahodne vygenerovaným, bude bude takto vzniknutý hash úplne iný, ako keby sme ho získali jednoduchým použitím f-cie SHA1. Preto ak užívateľ zadá svoje heslo, skončí jeho prihlásenie neúspešne... Tento  MySQL príkaz som pochopil, stále však nechápem ako sa týmto spôsobom môže zhodovať hash hesla, ktoré pošle užívateľ s hashom , ktorý je v DB. Jedine tak, že užívateľovi pošleme ešte pri registrácii späť jeho heslo , ku ktorému je pripojený ten náhodne generovaný reťazec.Je to tak?

ikona Jakub Vrána OpenID:

Registrace v tomto případě vypadá takhle:
<?php
$salt
= rand_chars();
mysql_query("INSERT INTO uzivatele (login, salt, heslo_sha1) VALUES ('$_POST[login]', '$salt', SHA1('$_POST[heslo]$salt')");
?>
SHA1 bez saltu se nikde nepoužívá.

qwertz:

Mohl bych poprosit o zdroj funkce, která vytvoří daný salt? Díky.

Filip:

když ukládám heslo do db takto MD5('$heslo') je to bezpečné?

ikona Jakub Vrána OpenID:

Bezpečnost není vlastnost, ale veličina. Záleží na tom, kam v této veličině umístíme hodnotu dostačující bezpečnosti. Ve většině případů je MD5(heslo) za touto hodnotou a je tedy dostatečně bezpečné.

Fany:

mohu se zeptat ten "salt" by se melo asi kolik znaků pridavat? jestli salt budou jen dva znaky, nebo deset, ci dvacet, tricet... ono asi cim vic tim lip...ale i tak by melo byt nejaky optimum..muzete poradit pls?

ikona Jakub Vrána OpenID:

Je pravda, že čím víc, tím líp. Ještě více ale záleží na pestrosti. Při 94 různých znacích stačí délka 8 znaků.

jenda:

mam podla hore uvedeneho prikladu

<?php
$sql
= "SELECT user FROM web2 WHERE user = 'aa' AND pass = SHA1(CONCAT('ddd',salt))";
?>

ked sa pripojim cez conzolu na mysql server dotaz mi vrati 1 riadok ale ked to mam v phpku na stranke proste nefunguje :(
neviete poradit?

bohdy:

Omlouvam se za mozna hloupy dotaz - u te zmeny hesla, nemelo by se to heslo take zahashovat?

ikona Jakub Vrána OpenID:

Jistě, díky za povšimnutí, opravil jsem to.

martin:

ahoj, zabyval jsem tim samym problemem co pri ztrate hesla a napadla mne jedna vcelku elegantni moznost, ktera zatim funguje bez problemu...  tou je zaslani hash podoby hesla uzivateli ktery se s tim jednoduse prihlasi, protoze kontrola existujiciho uzivatele je ve tvaru typu:  select from tabulka where jmeno=neco and heslo=md5(heslo) or heslo=poslany hash  ... a po prihlaseni si to muze zmenit

ikona Jakub Vrána OpenID:

Toto řešení je velmi nebezpečné. Když někdo získá databázi hesel (třeba ze staré zálohy nebo z vyhozeného poškozeného disku nebo třeba zhrzený vyhozený správce), tak se může bez potíží pod kýmkoliv přihlásit.

kubakista:

Diskuze je už asi mrtvá, ale stejně...

Možná mi něco uniklo, ale neměl by být při změně hesla připojen k hashi i salt? :-)

ikona Jakub Vrána OpenID:

Tady diskuse není nikdy mrtvá :-).

Práci se saltem jsem doplnil – ukázka byla zaměřena na kontrolu starého hesla a ne každý salt používá, proto v ukázce salt nebyl. Ale když se o něm v článku o odstavec výš hovoří, tak jsem ho doplnil i sem.

Marian:

Nebolo by dobre keby kazdemu uzivatelovi skript prideli heslo?
Myslim si ze by to bolo omnoho bezpecnejsie a este by tam mohla byt aj kontrola ci uz take heslo existuje.

Lord-DarthWADER:

vůbec to nechapu, nemuzete mi nekdo napsat ten kod i s html kodem registrace?

Jan Němeček:

Rekneme ze jsem paranoik, kteremu se nelibi zasilani hesla v sql query, aby ho pak db zahashovala sama db.

Pak mne napada toto reseni:

Dle uzivatelskeho jmena dotazovat na databazi hash a salt,
v php samotnem pak spojit heslo a sul, zahashovat, porovnat..

Nebo by se to dalo provest lepe?

Mimo predeslou otazku pak jeste uvaha:
Co heslo zahashovat, k hashi pridat sul, a znovu zahashovat. Melo by to smysl, ci je to zbytecnost, nebo mozna nebezpecnost?

ikona v6ak:

"Dle uzivatelskeho jmena dotazovat na databazi hash a salt,
v php samotnem pak spojit heslo a sul, zahashovat, porovnat.."
Taky to tak dělám, už kvůli implementaci mi nic jinýho nezbývá.

"Co heslo zahashovat, k hashi pridat sul, a znovu zahashovat. Melo by to smysl, ci je to zbytecnost, nebo mozna nebezpecnost?"
IMHO je to zbytečnost, navíc teoreticky se tím možná zvyšuje možnost kolize (možná ne, nechce se mi nad tím dopodrobna přemýšlet), ale nebezpečností bych to nenazval.

ikona Emkei:

Osobne bych resil ukladani hesel v databazi nasledovne:
http://www.soom.cz/index.php?name=webforum/show&thread_id=34294
v theardu se nachazi i oduvodneni, proc bych tento problem resil tak, jak jsem popsal.

Filip:

Vše snad chápu, kromě přesného významu té přísady salt. Při registraci to tak udělám, to je jasný chápu. Jak se ale pak lidi mohou přihlásit? Zadají stejné heslo jako při registraci, ale při ověření se zase vloží jiná přísada, takže to fungovat nebude, ne? Nebo se automaticky při tom ověřování vloží stejná přísada? ale to je blbost ne...
Vysvětlete mi prosím jak by pak fungovalo přihlášení

ikona Jakub Vrána OpenID:

Při ověřování se použije stejný salt jako při registraci.

majo:

Moj sposob ochrany hesiel uzivatelov:

Hash hesla vygenerujem totalnym skomolenim prihlasovacieho mena a hesla (pridanim dalsich specialnych znakov) pricom vzdy ziskam min. 32 znakovy string ktory potom "zaheshujem". Skomolenie hesla sa deje podla urcitych pravidiel v PHP, teda nie je nutne tuto "sol" zapisovat do db. Dalsie obmedzenie je v podobe 5 zlych zadani hesla na jedneho uzivatela v jednej hodiny. Teda po zadani posledneho 5-teho pokusu sa ucet zablokuje na hodinu.

Myslite si ze je tento sposob dostatocne bezpecny? Na co si mam dat este pozor?

ikona Jakub Vrána OpenID:

Obecně řečeno se to jeví jako vyhovující postup, problém by mohl být v implementaci. Prý asi polovina bezpečnostních rizik tohoto druhu je způsobena právě nevhodnou implementací spolehlivých kryptografických algoritmů. Proto se doporučuje používat spíše jednoduché prověřené postupy.

Kontrola počtu zadání hesla je v pořádku, pět pokusů je možná trochu málo - na útok hrubou silou to je přísné až dost a pro zapomnětlivého uživatele to může být moc přísné.

majo:

Priklad:
meno: vrana
heslo: jakub

vysledny string = "5vrana--&jakub!-ran5--rna{]bkUJA---[vran555" a toto zaheshujem pomocou sha1..

teda z jednoducheho hesla vygenerujem podobny "zhluk" znakov, pricom vzdy kazdy uzivatel ma uplne ine pravidla na jeho vytvorenie.. teda silne pochybujem ze by niekto bez ziskania suboru v ktorom to generujem prisiel na spravny algoritmus.. uz jedine co mi zostava je dany subor skryt niekde mimo suborovy system apacha..

uprimne som clovek ktory si skuma vlastne riesenia, teda nepouzivam primoc overene postupy, lebo takym naopak nedoverujem (ani newiem preco :), asi som moc paranoidny) a ohladom tych pokusom uz rozmyslam zvysit limit na 10 na hodinu

Marek Zelenka:

V aplikaci kde bych znal loginy ostatních uživatelů by se tato "ochrana" po pěti neúspěšných pokusech mohla proměnit v DOSt dobrý útok :o) Souhlas?

majo:

ip adresa sa automaticky zablokuje a odblokovanie uctu je mozne cez mail...

bohuzial, kazda restrikcia ma svoje proti... :(

Mirek:

Ahoj, je možné, že funkce crypt() vytvoří hash jen z několika prvních znaků? Mně to vychází z prvních 8. Pak, když je heslo delší a změním znaky na 9. a další pozici, je hash stále shodný!

crypt('tojealehezkyden', 'bt');
//výsledek: bt2z3XA2ydT5g

crypt('tojealehnusnyden', 'bt');
//výsledek: bt2z3XA2ydT5g

Pozn.: První znaky 'bt' je salt.

Nebo jsem celou funkci crypt() nepochopil!?

Mirek:

Aha, už jsem to objevil na www.php.net. V případě použití salt se kryptuje opravdu jen prvních 8 znaků :-)

"The standard DES-based encryption crypt() returns the salt as the first two characters of the output. It also only uses the first eight characters of str , so longer strings that start with the same eight characters will generate the same result (when the same salt is used)."

Shuster:

Pokial je DB na inom serveri ako PHP odporucal by som odosielat heslo do DB uz zakodovane. Cize nevyuzivat SHA1 v DB ale v PHP.

zeminem:

Ahoj, potřeboval bych vědět jestli to co jsem napsal je dostatečně silné, nebo ne...
Pro přihlašování používám:
<?php $heslo = sha1(sha1($heslo).$random); ?>
s tím, že $random je při registraci náhodně vygenerovaný salt uložený pro každého uživatele v databázi. Je to silné, nebo je silnější:
<?php $heslo = sha1($heslo.$random); ?>
Druhá věc co mě zajímá a potřeboval bych vědět je, jestli je možné se znalostí šifrovací metody a saltu složit původní heslo, nebo to není technicky možné? (nemyslím útok hrubou silou, nebo tak něco, ale vyloženě matematickou metodou)
Díky...

ikona Jakub Vrána OpenID:

Vyjde to nastejno. Heslo lze zrekonstruovat jen hrubou silou. Doporučoval bych přečíst také článek http://php.vrana.cz/slozitost-hesel.php – aby to bylo opravdu bezpečné, musím mít uživatelé dost dlouhá a složitá hesla.

Budulínek:

Díky za článek, pomohl mi.
Chápu to správně tak, že SALT si vytváříme my a nikoliv skript automaticky?

Tak, jako např. zeminem (příspěvek nade mnou) vygerenuje random salt pro každého uživatele zvlášť. Jenže sůl ukládá opět do databáze, tak mi to přijde k ničemu, protože když se útočník dostane do databáze, aby se mohl podívat na hashe hesel, dostane se i k soli. Jedině, že by zahashoval i tu sůl ukládanou do databáze mně teď napadlo.

ikona Jakub Vrána OpenID:

Originál soli potřebujeme proto, abychom mohli ověřit heslo, nestačí tedy ukládat její haš. Sůl slouží k tomu, aby nebyla poznat dvě stejná hesla a aby se pro prolomení hesel nedaly použít předpočítané tabulky.

Ľubomír Tóth:

Ak niekto získa databázu s sha1 + salt, môže do prepočítaných tabuliek/slovníka pridať ten salt, a získa heslo.

Úlohou saltu je i, aby neboli v databáze 2 zhodné hesla. Ale to sa dá docieliť celkom jednoducho, a to tak, že sa zahashuje  spolu "meno + heslo". Nakoľko je meno jedinečné, tak nebudú dve zahashované heslá zhodné.

A ešte by som to do doplnil o jeden prvok. A to tak, že salt uložiť mimo databázu. proste do nejakého php konfiguračného súbora. Hash by bol potom "meno + heslo + salt". Salt by bol síce pre všetkých zhodný, ale docielilo by sa toto:
- V prípade prieniku do databázy by chýbal útočníkovi salt, ktorý by bol v php súbore. Takže by musel získať i php konfiguračný súbor.
- Vďaka menu v hashi by neboli dve zahashované heslá zhodné.
- V prípade podozrenia z úniku by sa jednoducho zmenila hodnota saltu v php, a tým by sa nikto neprihlásil so starým heslom (nesedelo by porovnanie), A po neúspešnom prihlásení by sa ponúkla funkcia "Zabudnuté heslo", kde by sa užívateľovi vygenerovalo nové.

ikona v6ak OpenID:

"Ak niekto získa databázu s sha1 + salt, môže do prepočítaných tabuliek/slovníka pridať ten salt, a získa heslo."

Záleží to na kvalitách hashovací funkce a způsobu míchání saltu. V obecném případě to asi moc nepůjde.

A i pokud to půjde (napadá mě pouze length extension attack[1]), člověk nejspíš zkrátí čas pouze konstantněkrát. Těžko si tudy může pomoci v asymptotické složitosti.

[1] https://en.wikipedia.org/wiki/Length_extension_attack

ikona v6ak OpenID:

Tak to dopadá, když člověk nedočte ten příspěvek do konce, a odešle komentář. Pak píše dva komentáře po sobě, jako já teď :(

'A to tak, že salt uložiť mimo databázu. proste do nejakého php konfiguračného súbora. Hash by bol potom "meno + heslo + salt".'

Saltem se zde stává jméno. Plus je tu druhý salt, který je mimo DB.

Nic proti druhému saltu, znesnadňuje to práci útočníkovi, pokud udělá třeba jen SQL injection. Proč ale ten první salt nahradit uživatelským jménem a nepoužít klasický náhodný salt? Hashovalo by se pak userHash+sharedHash+userPass místo klasického userHash+userPass. Případně by se dal postup upravit tak, aby používal bcrypt nebo dokonce tu novinku z PHP 5.5.

ikona Jakub Vrána OpenID:

Úlohou saltu je i to, aby ve dvou různých databázích nebyly stejné haše. Proto se používá náhodný řetězec, nikoliv uživatelské jméno.

qwe777:

Nie je salt len ďalší stĺpec v tabuľke s náhodným reťazcom?

Ak áno, tak ak niekto použije SQL injection, tak získa i salt, alebo nie?

A ak áno, prečo by ním nemohlo byť užívateľské meno, ktoré je unikátne?

ikona Jakub Vrána OpenID:

Uživatelské jméno se nepoužívá mimo jiné proto, že když by měl uživatel stejné jméno a heslo na dvou různých službách, tak bude mít i stejný haš.

Vložit příspěvek

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-2014 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.