Hašování hesel na klientu

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

V nedávné době došlo k několika problémům s ukládáním hesel u služeb, které velmi dbají na bezpečnost svých uživatelů a hesla se snaží ukládat správně. GitHub si omylem ukládal čitelná hesla do logu při jejich resetu, Twitter dokonce u všech uživatelů. Uživatelé jsou zároveň nepoučitelní a jedno heslo používají na více různých službách. Např. GitHub se opakovaně stal obětí tohoto nešvaru, jednou dokonce ve velkém rozsahu. Útočníci prostě heslo uniklé z jiné služby zkusili použít i na GitHubu a u některých uživatelů to prošlo. Aby se to nestalo vám, je vhodné dát uživatelům možnost použít jednorázové heslo.

Jak se nestat tím, od koho hesla uniknou a útočníci je pak použijí i jinde? V první řadě samozřejmě používáním HTTPS a správným ukládáním hesel na serveru, v PHP funkcí password_hash. Jak jsme ale viděli u GitHubu a Twitteru, samo o sobě to nestačí. Stačí malá nepozornost, přidání nějakého „neškodného“ logování a hesla se najednou válí na serveru i v čitelné podobě. Navíc i pokud máme aplikaci neprůstřelnou, tak se k heslům může dostat jakýkoliv zaměstnanec s přístupem k webovému serveru, který vidí hesla ještě před jejich bezpečným uložením nebo ověřením.

Hašování na klientu

Řešením je hesla hašovat už na klientu, takže se v čitelné podobě na server vůbec nedostanou. Na klientu spočteme hash_client(password), na serveru uložíme hash_server(hash_client(password)). hash_client a hash_server jsou nějaké hašovací funkce, klidně ta stejná. Pokud má klient vypnutý JavaScript, tak na server pošleme samotné heslo a obě hašování provedeme na serveru. Je důležité si uvědomit, že toto opatření nezlepší bezpečnost našeho samotného serveru. Pokud by se útočník dostal k hodnotě hash_client(password) (ať už odposlechem nebo třeba z logu na serveru), tak ji prostě příště použije znovu. S touto hodnotou je proto potřeba zacházet stejně obezřetně jako s heslem samotným. Důležitou výhodou ale je, že tato hodnota mu bude k ničemu na jiných službách.

Další výhodu spatřuji v tom, že uživatelé si mohou sami ověřit, jak služba s jejich heslem nakládá. Michal Špaček shromažďuje informace o tom, jak služby ukládají hesla, jediným vstupem pro tuto službu je ale vyjádření služeb samotných (krom nejkřiklavějších případů, kdy nám služba např. zapomenuté heslo prostě pošle tak, jak jsme ho zadali). GitHub i Twitter mají obě nejlepší hodnocení, přestože obě podle vlastního vyjádření hesla ukládala i v čitelné podobě (byť omylem).

Věřím, že stejně jako služby začaly hesla postupně ukládat bezpečně, tak je taky začnou přenášet bezpečně, protože jim do mého hesla konec konců nic není. Ověření síly hesla (k čemuž jedině služby heslo potřebují, navíc jen při registraci) se může dělat také na klientu.

Jakub Vrána, Řešení problému, 7.5.2018, diskuse: 5 (nové: 0)

Diskuse

vonTrips:

Příspěvek se mi líbí, jen mi není jasná jedna věc. Nejsem zrovna odborník, takže by mě zajímalo, jak můžu zkontrolovat (při registraci) sílu hesla na klientu, kde je vypnutý JavaScript?

ikona Jakub Vrána OpenID:

Pokud je na klientu vypnutý JavaScript, tak se heslo přenese v plaintextu a můžu ho zkontrolovat na serveru.

Antonín Bouchal:

Skvěle popsáno.
Hlavně je důležité info, že samotnou službu to o moc lépe neuchrání, nicméně je důležité, že heslo samotné bude stále nečitelné a tudíž jinde nevyužitelné.

U sebe volím na straně klienta keccak hash, který má dostupné JS knihovny i na server část. Jediné co jsem ale nerozchodil je 512 bitová podpora pro PHP.

Tomáš:

Tohle už nějakou dobu používají třeba
na https://account.socialbakers.com a je to vlastně myšlenka dnes již bohužel mrtvého HTTP digest.

V tomhle řešení může být několik slabých míst a měly by to nativně podporovat prohlížeče. Heslo se dostává do JS heapy a teoreticky je šance, že si ho záškodník vytáhne. Musíme podporovat dvě verze vstupu na heslo, plaintext a heshované, kdo ví, jestli není možný nějaký typ útoků, který by zneužil obě API zároveň. Ne všechny hashe je bezpečné dávat jako vstup do jiných hashů, hashovací funkce má poté na vstupu určitou sadu znaků o určité délce, tohle může být tenký led.

Při samotné implementaci poté může vzniknout řada problémových situací. Používat na tuhle funkci knihovny z externích míst znamená, že dávám někomu jinému důvěru, aby manipuloval s hesly uživatelů. Zranitelnost může být v samotné knihovně, hůře se to ověřuje, knihoven může být spousty. JS a jeho přetékání čísel je past na kryptografii atd.

ikona Jakub Vrána OpenID:

HTTP digest umřel z dobrého důvodu – server musel mít uložené informace, které se přímo daly použít pro přihlášení. Jinými slovy nemohl mít uložený jen haš toho, co mu klient pošle.

Zbytek je typický FUD – máme z něčeho strach, tak to radši nebudeme dělat, i když tady je jasný problém, který by bylo potřeba vyřešit (server má přístup k heslům, které pak může použít i jinde).

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.