Zpět na seznam článků RSS

Bezpečné uložení autentizace uživatele

Dlouhodo­bé uložení, „zapama­tování“ chcete-li, autenti­zace uživatele webu je řešitelné velkým množstvím způsobů, ale také často omezeno velkým množstvím technic­kých podmínek. Je velmi snadné zvolit špatné předpokla­dy a výrazně tím oslabit bezpečnost aplikace.

Session v PHP

Autenti­zaci většinou realizuje session na straně serveru. V ideál­ním světě bychom mohli dlouhodo­bou autenti­zaci vyřešit nastave­ním dlouhé doby session. Reálně vám ale většina sdílených hostingů dopřeje platnost v řádu hodin, nikoliv dnů nebo dokonce týd­nů.

Ne nepodstat­ná je zde i otázka bezpečnos­ti. Alespoň v případě PHP, není session zrovna místo, které by bylo ideální pro nějaké dlouhodo­bé uchovává­ní citlivých údajů — PHP používá obyčejné nešifro­vané textové soubory jako úložiště. Jejich případná kompromi­tace umožňuje snadné napadení všech aktivních relací.

Cookie uživatele

Druhým způsobem je tedy uložení zadaných údajů pro autenti­zaci přímo na straně uživate­le.

Předpoklá­dejme, že je naše autenti­zace řešena uživatel­ským jménem a heslem. To máme v databá­zi uloženo jediným možným správným způsobem — jako výstup funkce bcrypt.1

Jak to udělat špatně

  1. Uložit heslo do cookie — Patří k dobrým zvykům neukládat heslo nikam, kde by mohlo být přečteno.
  2. Uložit hash hesla do cookie — Špatný nápad, popírá jeden z důvodů, proč se hesla na serveru hashují. Aby jejich případná kompromi­tace nebyla zneužitel­ná.
  3. Uložit náhodný token do cookie — Stejné oslabení bezpečnos­ti, jako u hashe. Umísťuje­te na server něco, co může útočník ukrást a zneužít.
  4. Uložit zašifro­vaný token do cookie — Close, but no cigar. Server musí znát šifrovací klíč a v tomto případě i token. Útočník má na serveru opět nachystá­ny oba díly skládač­ky.

Jak to udělat správně

Uložit zašifro­vané heslo do cookie — Klíč na serveru je náhodný a unikátní pro každého uživate­le. Heslo je vloženo uživate­lem během přihláše­ní, jinak ho server nezná a nemusí zná­t.

Kompromi­tace náhodného klíče nepředsta­vuje riziko, sám o sobě k přihlá­šení nestačí a pro získání hesel by musel útočník úspěšně napadnout i jedno­tlivé uživatele a jejich cookie.

Součástí šifrované informace může být i časový údaj. Na rozdíl od doby expirace samotné cookie s ním nelze manipulo­vat a může tak sloužit pro bezpečné omezení platnos­ti.

Jednoduchým vytvoře­ním nového klíče na serveru dojde k okamži­tému zneplat­nění všech případných cookies. Tato operace může být přístupná i uživa­telům jako „odhláše­ní ze všech prohlíže­čů“.

Schéma šifrování přihlašovacích údajů

Schéma šifrování hesla do cookie a jeho následné­ho ověření s hashem na serveru.

Ukázka implemen­tace šifrování v PHP

Pro šifrování je třeba zvolit symetric­kou šifru2, ideální je AES (Rijnda­el). Klíč větší než 128 bitů je dle mého soudu pro tohle použití overkill, ale jestli děláte webové rozhraní k raketo­vému si­lu…

Pro AES je nejběžněj­ší blokový šifrovací režim CBC3 s náhod­ným iniciali­začním vektorem. Ten je velmi důležitý, aby stejná zpráva neprodu­kovala stále stejnou šifru. Vektor není a nemá být tajný, přidává se přímo na začátek zašifro­vané zprávy.

$key = pack('H*', $random); // 32-znakový hexadecimální řetězec → 128-bitový binární klíč
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
$encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, time().'/'.$password, MCRYPT_MODE_CBC, $iv);

echo base64_encode($iv.$encrypted);

Dešifro­vání probíhá analogic­ky. AES je bloková šifra, nemělo by vás tedy překvapit, že zpráva, která není násobkem 128 bitů, bude doplněna nulovými bajty. Ty je třeba odstranit.

$message = base64_decode($message);
$size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = substr($message, 0, $size);
$encrypted = substr($message, $size);
$decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $encrypted, MCRYPT_MODE_CBC, $iv);

echo rtrim($decrypted, "\0");

Samotná cookie tedy obsahuje zašifro­vaný čas a heslo. Uživatel­ské jméno (e-mail) už za tajný údaj považovat nelze a může být připojeno v nezaši­frované formě — respekti­ve musí, pokud byste nechtěli cookie zkoušet dešifro­vat všemi klíči z databá­ze.

Reference

  1. PHP: passwor­d_hash — php.net
  2. PHP: Mcrypt ciphers — php.net
  3. PHP: Mcrypt Predefi­ned Constan­ts — php.net

Pokud není uvedeno jinak, podléhá obsah licenci CC BY-NC-ND a ukázkové zdrojové kódy CC BY.
Martin Hozík —