Píšeme-li webovou aplikaci (nebo zodpovídáme za její bezpečnost), dříve nebo později narazíme na potřebu ověřit identitu uživatele a k tomu budeme chtít jeho heslo. Pojďme si říct, jak hesla ve webové aplikaci ukládat bezpečně.
Proces ověření identity uživatele ve webové aplikaci může
vypadat nějak takto:
- Uživatel požádá o přístup k nějakému
prostředku (webové stránce), kam anonymní uživatel nemá přístup. Aplikace buď
zareaguje chybu autorizace (např. 401 – Unauthorized), nebo uživatele
přesměruje na přihlašovací stránku. To bude náš případ.
-
Na přihlašovací stránce uživatel vyplní
formulář, kam do příslušných kolonek zadá uživatelské jméno (identifikace
uživatele) a heslo (pro jeho autentizaci).
-
Systém podle uživatelského jména najde příslušný
záznam v databázi, obsahující data o uživateli.
-
Systém ověří shodu hesla zadaného s heslem
uživatele.
-
Podle výsledku ověření shody je následně
uživatel autentizovaný, nebo není.
Poznámka: Pokud si nejste
jistí výrazy jako identifikace, autentizace a autorizace, podívejte se na
článek AAA, aneb Ťuk, ťuk! Kdo tam? věnovaný právě těmto pojmům.
Co odnesl čas?
V hluboké minulosti nebylo neobvyklé, že se hesla do
databáze ukládala v prostém textu (plaintext). Kdokoli, kdo měl přístup do
příslušné tabulky, si mohl hesla zobrazit. Takže se ukázalo, že to není úplně
ideální, a začal se hledat nový způsob ukládání. A co udělám, když nechci, aby
kdokoli mohl data číst? Správně, zašifruju je. Teď už si hesla nemohl přečíst
každý, kdo k nim získal přístup.
Přesto ani šifrování nebylo v mnoha scénářích
optimální. Aby mohla aplikace ověřit shodu hesla zadaného do přihlašovacího
formuláře a hesla uloženého v databázi, musí mít někde uložený šifrovací
klíč. Vždycky tak existovalo riziko, že se k šifrovacímu klíči, kterým
jsou hesla zašifrovaná, dostane nepovolaná osoba. Stačí mít přístup
k serveru, k aplikaci, konfiguračním souborům nebo zdrojovým kódům.
Když databáze unikla, útočníci mohli zkusit hesla dešifrovat.
A co se osvědčilo?
Jako dosud nejlepší varianta se nakonec ukázalo ukládání hashů
hesel. Má to nepopiratelnou výhodu: hashování je jednosměrné, z hashe
nelze zpětně získat heslo (resp. vstup obecně). Takže super, jdeme ukládat do
databáze místo hesla jen jeho hash! Když se uživatel přihlásí, z hesla,
které zadal do přihlašovacího formuláře, spočítáme hash a ten porovnáme
s hashem v databázi. Souhlasí? Prima, pustíme ho dál. Nesouhlasí?
Lituji, zkus to znovu.
Problém hashe je, že dva stejné vstupy mají i stejný výstup.
Když dva uživatelé použijí stejné heslo, v databázi se objeví stejný hash.
V případě úniku databáze útočník vidí, kdo všechno má stejné heslo. Nezná
ho, ale může začít vymýšlet další postup. Může vyzkoušet hashe běžných hesel.
Může zkusit heslo vylákat od některého jiného uživatele - phishing, social
engineering, hesla stejného uživatele (podle e-mailu) z jiných uniklých
databází. A jakmile získá heslo jednoho uživatele, podle hashe ví, kdo další má
stejné heslo.
Aby se zamezilo možnosti zjišťovat shodná hesla, začalo se
k heslu přidávat náhodný řetězec, tzv. salt, kryptografická sůl. Salt je
náhodný unikátní řetězec, který – přidáním k heslu – způsobí, že dvě
stejná hesla už nejsou stejná, tím pádem už nemají ani stejný hash.
Alice i Bob mají oba stejné heslo „tajnéheslo“. Aplikace
k němu ale přidává salt. Pro Alici to je 6qi8pG, pro Boba yWocnD. Vstupem
pro hashovací funkci se tak u Alice stane řetězec „6qi8pGtajnéheslo“, u Boba „yWocnDtajnéheslo“.
Oba řetězce budou mít jiný hash. Ačkoli si Alice a Bob pamatují jen své heslo,
pro hashování a porovnání s hashem v databázi se použijí odlišné
řetězce.
Poznámka: Jestli aplikace
připojí salt na začátek, konec nebo nějak zkombinuje, to je závislé na
konkrétní implementaci a nemá na ověření vliv. Jen je nutný konzistentní postup
jak při vytváření hesla, tak při jeho ověřování. A to se týká i použitého
hashovacího algoritmu – jiný algoritmus nebo jiné připojení salt způsobí
neúspěch ověření hesla.
Protože uživatelé udržují v tajnosti své heslo, není
nutné salt v databázi šifrovat, můžeme ji ukládat v prostém textu; jejím
účelem není zajistit důvěrnost hesla, ale aby každý hash byl unikátní. Tajná
zůstává pouze část pocházející od uživatele, tedy samotné heslo.
A jak je to v praxi?
Co ale je důležité, salt si musíme uložit do databáze a být
schopni spárovat uživatele a jeho salt, protože bez znalosti salt nebude možné
při přihlášení shodu hesla ověřit. Obvykle se to řeší uložením do stejného
záznamu, jako ostatní data uživatele (username, e-mail…). Dále musí být salt
dostatečně dlouhá, aby se snižovala efektivita útoků předpočítanými tabulkami
(tzv. rainbow tables). OWASP doporučuje minimálně 32 znaků, což
odpovídá 16 bajtům kryptograficky náhodných binárních dat. Ovšem, díky tomu, že
uživatel salt znát nepotřebuje, ta je uložena jen v databázi a předává se
jen aplikaci, nepředstavuje to žádný problém.
Přihlášení uživatele s pomocí salt pak probíhá následovně:
- Na přihlašovací stránce uživatel vyplní
formulář, kam do příslušných kolonek zadá uživatelské jméno (identifikace
uživatele) a heslo (pro jeho autentizaci).
-
Systém podle uživatelského jména najde příslušný
záznam v databázi, obsahující data o uživateli.
-
Systém z nalezeného záznamu vezme salt uživatele
a připojí ji k heslu, které uživatel zadal do formuláře.
-
Ze vzniklého řetězce spočítá hash a ten porovná
s hashem uloženým v databázi.
Postupem času se ukázalo, že i salt nezaručuje úplnou
odolnost vůči útokům na hashe. Moderní grafické karty dokážou u rychlých
hashovacích funkcí počítat miliardy hashů za sekundu. Proto se začaly objevovat
„pomalé“ hashovací funkce, jako Argon2, bcrypt nebo scrypt. Ty jsou navrženy
tak, aby například pomocí iterací záměrně zpomalovaly výpočet hashe, který trvá
řádově ve stovkách milisekund. Navíc mají i vyšší nároky na paměť, takže snaha
o hromadné ověření hesel nejen trvá déle, ale vyžaduje větší výpočetní výkon.
Uživatel si takové „sabotáže“ vůbec nevšimne, ale útočníkovi dramaticky
prodlužuje čas potřebný k prolomení hesel.
Díky tomu je útok hrubou silou výrazně dražší nejen na čas,
ale i na hardware. Výkonné GPU nebo specializované ASIC čipy ztrácejí svou
výhodu a prolomení hesel se stává prakticky neproveditelným.
Shrnutí
V dnešním článku jsme si ukázali postupný vývoj
ukládání hesel ve webových aplikacích, od plaintextu, až po hashování. V
moderních webových aplikacích se hesla doplňují o unikátní salt a do databáze
se ukládá jen výsledný hash pomalé hashovací funkce (např. Argon2 nebo bcrypt).
Tento postup chrání uživatele i v případě úniku databáze a je dnes považován za
standardní a jediný bezpečný způsob ukládání hesel.