Přihlášení uživatele a ukládání hesla v databázi
Přihlášení uživatele je na většině webů realizováno pomocí kombinace uživatelské jméno / heslo. V databázi je uživatelské jméno uloženo typicky jako plaintext a heslo jako nějaký hash. Většina starších systémů používá md5(heslo)
(třeba i tento web, MyEgo.cz, protože hesla jsou zde z roku 2003, kdy md5 bylo považováno za dostatečné; nicméně budu to měnit), novější třeba sha1(heslo)
, moderní například sha1(SALT.heslo)
. Sůl je v tomto případě náhodný řetězec, třeba |L$k!#Lcj29+RbXA=*N)
, unikátní pro každou instalaci. Důvodem pro použití soli je stížit možnou rekonstrukci skutečného hesla z hashe, pokud by tento byl odcizen.
Tento postup je jednoduchý na realizaci a všeobecně známý. Zajímavější je, jak poznat, že je klient přihlášen. Osobně toto řeším uložením náhodně vygenerovaného hashe do cookie. Tento náhodný řetězec přitom nijak nesouvisí s heslem. Generuje se třeba takto:
mt_srand( (double) microtime() * 100000000);
$this->cookiekey = sha1(COOKIE_SALT.uniqid(mt_rand()));
Poté, co je ověřeno jméno a heslo klienta, vygeneruje se a zapíše do databáze a do cookie tento hash s tajnou solí. Při návratu klienta na web je tato hodnota v cookies porovnána na databázi, a pokud se shoduje, je uživatel přihlášen. Navíc je více než vhodné kontrolovat IP adresu.
Tento postup samozřejmě nezamezí tomu, že někdo odposlechne danou cookie a má identickou IP adresu (zejména za NATem to není nemožné), ale pro běžné (nebankovní) redakční systémy je to rozumný kompromis mezi bezpečností, pohodlím a náklady na zabezpečení.
Celé je to samozřejmě možné ukládat do session namísto cookies, což dále zvýší bezpečnost, ale sníží komfort.
Pokud používáte ve svém CMS jiný postup, budu rád když se o něj podělíte v komentářích.
Já raději užívám SESSIONS, protože se veškerá data ukládají na serveru a nedají se tak odposlouchat (alespoň pokud vím, rád se poučím). Předává se pouze ID, jehož spolehlivost se dá rozšířit třeba o kontrolu IP. Přímo při autentizačním procesu pak používám session_regenerate_id().
Nejslabším místem je pak samotné přihlašování, kdy bez použití šifrovaného HTTPS je možné získat citlivé údaje. To alespoň částečně řeším technikou "výzva-odpověď", kdy odesílané heslo ručně zašifruji v JavaScriptem. Držel jsem se tohoto článku http://php.vrana.cz/bezpecne-prihlasovani-uzivatelu.php...
Mám MD5 ale tiež chystám prechod. Asi použijem toto:
hash("sha256",$salt.":".$password.":".$email)
[1] jasně, v článku i píšu o možnosti session namísto cookie, zbytek ale zůstává identický. Session je vhodná tam, kde uživatel nemusí být přihlášen každý den, ale jen v dané relaci. Třeba pro eshop je to vhodnější, pro komentování na blogu asi ne.
[3] Nevýhoda Cookies mi přijde v tom, že musím pokaždé ověřovat platnost a sahat kvůli tomu do databáze. Takže v případě blogu bych to spíše řešil tak, že bych si popisovaným způsobem při přihlašování vytvořil Cookie, ale zároveň i session. Dokud by byl uživatel v relaci, kontroloval bych pouze session. V okamžiku, kdy by relaci opustil a pak se po čase na web vrátil, vytvořil bych na základě Cookie relaci novou. Čímž by se zkombinovaly výhody obou postupů.
Mj. když už se bavíme o tom přihlašování, přihlašovací formulář na tomto webu pořád zobrazuje heslo v normální viditelné podobě. U políčka 'password' je pořád type 'text' a nikoliv 'password'. Nevím, jestli to má nějaký účel, ale rozhodně je to pro uživatele dost nepříjemné.
[4] je to estetický účel, já se ale přihlašuji na https://myego.cz/admin/ :) Jinak ten dotaz do DB člověk stejně dělá kvůli jiným hodnotám, takže o to nejde, výkonnostně.
[5] Když už tam musí být type=text, přidal bych aspoň autocomplete=off.
Já teda kontrolu IP moc nemusím. Sice to přispívá bezpečnosti, ale zase na druhou stranu se musíte znovu přihlašovat, pokud se změní IP (např. použití notebooku na jiném AP). Postup ukládání náhodného hasha do databáze po přihlášení zase brání být přihlášen na notebooku a PC zároveň. Chápu to např. u někoho s vyššími právy (např. správce/pisatel blogu), ale u uživatelů, co mají jen nárok na přispívání do diskuse je to poměrně zbytečné a někdy pro ně obtěžující.
Já osobně používám SHA512 soli a hesla, přičemž sůl mám navíc unikátní pro každého uživatele. Ochráníte tím i blbce, kteří si jako heslo dají '1234' nebo 'heslo' (a je jich docela dost), protože pokud máte pro každého uživatele jinou sůl, bud mít každý z nich jiný hash, i když mají stejné heslo. Potenciální útočník by si určitě nejdřív našel duplikátní hashe a zkoušel by nejběžnější hesla.
[8] a jak si tu sůl ukládáš? Nebo je na ni nějaký algoritmus? Ten se dá také odvodit...
[8] Zabraňuješ tak rainbow tables, ale nie slovníkovému útoku, pretože beztak predpokladám, že máš tú soľ uložené hneď vedľa kompletného hashu. Iná vec by to bola, keby tá soľ bola v nejakej úplne oddelenej databáze.
[9] Tipujem pseudonáhodný generátor s vstupmi typu čas na serveri, login, email a to celé cez hashovaciu funkciu... to spätne neodvodíš si myslím.
[10] nemůžeš použít čas na serveru, musíš použít konzistentně stejnou sůl při uložení hesla i při jeho pozdějším ověření. Není problém mít různou sůl pro různé loginy, ale musí to být algoritmus, co produkuje stejné výsledky v čase. Tedy odvoditelný.
[11] Ehm... myslím, že nie. Soľ predsa nie je tajná hodnota, ktorá by sa vždy znovu tvorila. Soľ je väčšinou voľne dostupná v databáze (napríklad Joomla). Užívateľ pošle heslo, server podľa loginu nájde danú soľ v databáze, pridá ju k heslu, ktoré poslal užívateľ, vypľuje hash a ten porovná s uloženým hashom v databáze. Soľ sa netvorí vždy nanovo. Aspoň som si doteraz teda myslel, že to funguje tak ako píšem ja, ak je tu niekto kto si je istý, že nemám pravdu, tak nech to napíše, ja si to overím.
[5] Ok, uznávám, že předepsaný text "Heslo" v šedivé barvě je hezký a graficky zapadá do webu:-) Nicméně pořád by mělo platit, že grafika slouží obsahu a účelu, nikoliv obráceně. Je poněkud paradoxní, že se tady řeší pokročilá ochrana přihlašovacích údajů, když přitom kdokoliv, kdo si stoupne při přihlašování za mě, je schopný to heslo přečíst.
[13] Na nápravu by stačila by trocha javascriptu, aby při focusu na daný input se změnil z type="text" na type="password". Už jsem to kdysi řešil tenhle problém :-P
Podle mě je nejlepší použít
suff = "wa&@#8af" // konstatní řetězec, který je mimo databázi
hash.funkce(username + password + suff)
výhody oproti pouhému hash(salt + password): každý uživatel má v systému unikátní sůl (username + suff), takže při úniku dat z databáze není možné sestavit hashe nejběžnějších hesel a vyhledávat v databázi, kteří uživatelé tyto hesla mají
výhoda oproti pouhému hash(username + password): uživatel "petr" s heslem "123" se i přes hloupou kombinaci přihlašovacích údajů nemůže dohledat přes nějaké rainbow tables :-) Navíc útočník, který nezíská přístup k konstatě suff, neví, jak se z údajů v databázi vygeneruje hash. Což může být výhoda.
[9] [10] Sůl je normálně uložená vedle hashe v databázi - vzhledem k tomu, že má 20 znaků, rainbow tables si ani neškrtnou, protože s heslem, které má ještě pár znaků navíc, je to prostě zatím dost dlouhé. Sůl obecně není důvod tajit, pokud je dostatečně dlouhá a pokud používáte rozumné hashovací algoritmy.
Pro snížení možnosti rekonstrukce hesla může být taky fajn zkombinovat více algoritmů pro hashování hesla :) Třeba vzít md5, někde doprostřed vložit sha1, pak ještě jeden a pak to celé ořezat na x znaků a útočník má o zábavu postaráno :-)
[16] já myslím, že i tak má cenu sůl tajit. Můžu vzít nejpoužívanější heslo, vyzkoušet vygenerovat hash s tímto heslem pro všechny uživatele... a pár účtů má útočník jistých. Takto může posbírat dost účtů i na Intel Atomu... skrýt sůl někam mimo databázi nic nestojí (pokud je konstatní, nekonstatní se dá vykouzlit z username, jak už jsem říkal).
Jeden můj kunčaft pronesl krutou pravdu: "S kódováním hesel to nepřehánějte, pokud se někdo dostane až k databázi, tak heslo už krást nemusí."
Mnohem důležitější je tedy zajistit bezpečnost cesty hesla, a ne úložiště.
Nevím jak to má řešené Vodafone, ale při přihlašování do Samoobsluhy je v poli password "heslo" a při vkládání se mění text (čísla) na tečky...
[20] Javascriptem, stačí se podívat do zdrojového kódu stránky.
[16] pokud je sůl v databázi, tak je to ještě horší, než pokud je někde v konfiguraci PHP souboru starajícího se o autentizaci uživatele..
[16] To zas bolo múdro :) Soľ síce zabraňuje použitiu rainbow tables, ale pôvodný účel saltovania bola ochrana proti slovníkovým útokom a pri tých je jedno aká dlhá je soľ.
[17] To moc nepomôže, keďže ten zdroj bude zrejme verejne dostupný a zas útočník zobere slovník a bude vyrábať hashe tvojou metódou.
[22] To je zas relatívne si myslím. Ideálny stav by bola spätne neodvoditeľná jedinečná soľ pre každého užívateľa, ktorá by ale zároveň bola tajná :) Alebo skrátka zruš heslá a začni posielať jednorázové sms kódy :D
[23] No tak se to dá saltovat dvakrát - jednou user-specific solí a ještě nějakou konstantní tajnou z konfigurace, to problém už myslím řeší. Já jsem spíš chtěl poukázat na to, že je vhodné mít i různou sůl pro každého uživatele, aby nebylo vidět, že půlka lidí má stejné heslo.
[24] No áno, akurát si písal, ako keby jedna dostatočne dlhá soľ bola všemocná, iba na to som reagoval.
[24] to ani mít nebude, v tomto případě. Heslo je generováno v CMS a zasláno na email uživatele a téměř nikdo si je poté nezmění na "standardně oblíbené". Hesla jsou tedy různá i bez různých solí, neexistuje sto hesel typu "123456" ;)
[26] ale co když si ten uživatel to heslo změní? (bavme se o normálním uživateli, ne IT geekovi). I když je heslo solené, tak stejné heslo + stejná sůl = stejný hash. Proto si beru jako sůl jiný údaj o uživateli.
[27] když ti někdo ukradne obsah DB a v ní je i sůl, ukradne ti i tuto. "Solit" hashí uloženou vedle hashe samotného je zbytečnost a ne nutnost. Více to nemá smysl probírat.
[28] Možno u teba áno, keď tu máš registrovaných iba pár stoviek až tisícok účtov. Vtedy sa dá ísť na každé heslo individuálnym slovníkovým útokom. Ale ak ide o obrovské databázy, kde sú stovky tisíc hesiel, tak to veľmi výrazne predĺži čas potrebný na získanie týchto hesiel oproti nesoleným hashom.
Jen poznámka.
Nevím, jak uložení ověřovacího hashe do session zvýší bezpečnost. Session_id se ve finále také uloží do cookie, takže to přece vyjde na stejno, nebo ne?
Já osobně bych se použití session vyhnul. Session data se často na sdílených hostingách ukládají do stejného sdíleného prostoru. Tudíž je potom třeba počítat s tím, že se někomu povede session hodnoty přepsat, možná přečíst.
No, md5 i SHA1 je minulosti, dnes nejmene hashovat hesla SHA2 (256).
Zatim hashuju a nesolim, ale kdyz resit problem se soli, pak zvolit neco jako
hash_hesla = sha256((sha256(heslo) XOR heslo) - osoli se to samo sebou...