Internet Info, s.r.o. Lupa Měšec Podnikatel Root Zdroják DigiZone Slunečnice Vitalia TopDrive KupDnes Navrcholu NovýTarif Dobrý web Weblogy Woko Jagg Computer.cz SK: MojeLinky

Hlavní navigace

Bezpečné přihlašování uživatelů

Aktualizováno 2006-04-13 00:00:00 hod.
Pomocí protokolu HTTPS lze zajistit šifrovaný přenos všech informací a ideálně se tak hodí mimo jiné pro přihlašovací formuláře. Pokud tento protokol nemůžete použít (u malých projektů proto, že vám nevyjde vstříc hosting, u velkých z výkonnostních důvodů), přenáší se všechna data nešifrovaně a zdatný uživatel je může po cestě odposlouchávat. Jak jistě víte, nedávno byl obětí takového útoku Seznam.cz. Bezpečné přihlašování se ale dá zajistit i na nezabezpečeném protokolu.

Tweetni to Twitter Jaggni to! Jagg Del.icio.us Delicious

Technika výzva-odpověď funguje tak, že server pošle klientovi výzvu, klient k této výzvě připojí své heslo a serveru pošle otisk tohoto spojení. Server na své straně provede totéž a pokud výsledky odpovídají, tak uživatele přihlásí, jinak ho odmítne. Bezpečnost tohoto řešení je založena na tom, že server každou výzvu posílá jen jednou a pokud se útočníkovi podaří odpověď klienta zachytit, k ničemu mu to neposlouží, protože stejnou výzvu už server nikdy nepošle.

Realizace pomocí PHP, MySQL a JavaScriptu

K technické realizaci tohoto řešení budeme potřebovat na straně serveru i klienta funkci na výpočet otisku hesla spojeného s výzvou. V PHP i dalších serverových jazycích jsou hashovací funkce k dispozici již v základu, takže máme situaci poměrně jednoduchou, na straně klienta budeme muset sáhnout po externí knihovně – např. JavaScript pro MD5 i SHA-1 nabízí v BSD licenci Paul Johnston.

Pro spojení výzvy a hesla by se dalo použít prosté zřetězení, o něco bezpečnější by ale mělo být použití kódu HMAC. V JavaScriptové knihovně je tento algoritmus už implementován, v PHP si funkci budeme muset napsat sami, naštěstí je poměrně jednoduchá:

<?php
function hmac_md5($key, $data) {
    $blocksize = 64;
    if (strlen($key) > $blocksize) {
        $key = pack("H*", md5($key));
    }
    $key = str_pad($key, $blocksize, chr(0x00));
    $k_ipad = $key ^ str_repeat(chr(0x36), $blocksize);
    $k_opad = $key ^ str_repeat(chr(0x5c), $blocksize);
    return md5($k_opad . pack("H*", md5($k_ipad . $data)));
}
?> 

Dále musíme zajistit ukládání použitých výzev. Pokud nám nevadí, že výzvy budou ze spojité řady (takže kdokoliv bude moci poznat, kolikrát se náš přihlašovací formulář použil), stačí nám k tomu jednoduchá tabulka:

CREATE TABLE challenges (
    id int NOT NULL AUTO_INCREMENT,
    created datetime NOT NULL,
    PRIMARY KEY (id)
); 

Do této tabulky budeme při každém zobrazení přihlašovacího formuláře vkládat nový řádek. Při jeho odeslání se do této tabulky podíváme a pokud v ní výzvu nalezneme, ověříme heslo uživatele. Pokud souhlasí, tak výzvu smažeme, což je možné provádět i u zastaralých řádků (např. starších než 1 den), aby velikost tabulky zůstávala v rozumných mezích.

Zbývá vytvořit HTML formulář a celé to spojit dohromady:

<script type="text/javascript" src="md5.js"></script>
<script type="text/javascript">
function md5form(f)
{
    f['password_hmac'].value = hex_hmac_md5(hex_md5(f['password'].value), f['challenge'].value);
    f['password'].disabled = true;
    f.submit();
    f['password'].disabled = false;
    return false;
}
</script>
<form action="" method="post" onsubmit="return md5form(this);">
<fieldset>
<?php
mysql_query("INSERT INTO challenges (created) VALUES (NOW())");
$challenge = mysql_insert_id();
?>
<input type="hidden" name="challenge" value="<?php echo $challenge; ?>" />
<input type="hidden" name="password_hmac" value="" />
Login: <input name="login" />
Heslo: <input type="password" name="password" />
<input type="submit" value="Přihlásit se" />
</fieldset>
</form> 

Při zapnutém JavaScriptu se do skrytého formulářového pole password_hmac vloží otisk kombinace výzvy a MD5 hesla. MD5 hesla se používá proto, že na serveru je vhodné mít z bezpečnostních důvodů uložen pouze otisk hesla, takže při použití samotného hesla by server neměl jak spočítat výsledný otisk. Poté se zakáže pole se zadaným heslem (což způsobí, že se toto pole s formulářovými daty nebude posílat) a formulář se odešle. Po jeho odeslání se pole s heslem opět povolí, což se dělá jen kvůli tomu, aby se uživatel po neúspěšném přihlášení mohl vrátit v historii a heslo opravit. Pokud má uživatel JavaScript vypnutý, přenese se heslo jako prostý text, pomocí značky <noscript> je možné ho na toto riziko upozornit.

Na straně serveru můžeme heslo ověřit tímto kódem:

<?php
$logged = false;
$login = (get_magic_quotes_gpc() ? $_POST["login"] : addslashes($_POST["login"]));
$row = mysql_fetch_assoc(mysql_query("SELECT password_md5 FROM users WHERE login = '$login'"));
if ($_POST["password_hmac"]) {
    $valid = (hmac_md5($row["password_md5"], $_POST["challenge"]) == $_POST["password_hmac"]);
} else {
    $valid = ($row["password_md5"] == md5($_POST["password"]));
}
if ($valid) {
    mysql_query("DELETE FROM challenges WHERE id = " . intval($_POST["challenge"]));
    if (mysql_affected_rows()) {
        $logged = true;
    }
}
?> 

Pokud klient poslal pole password_hmac, ověříme heslo na jeho základě, jinak se spokojíme s textovým tvarem hesla. Za přihlášeného uživatele označíme tehdy, pokud souhlasí hesla a v tabulce challenges se nám podaří smazat zaslanou výzvu.

Poznámka k heslům s diakritikou

Funkce charCodeAt, kterou používá JavaScriptová knihovna, pracuje s Unicodovými kódy znaků. Knihovna z těchto kódů ve výchozím nastavení bere jen spodních 8 bitů (takže řada znaků koliduje), snadno se dá ale přenastavit tak, aby pracovala se 16 bity. Pokud již ale máte uložené otisky hesel uživatelů v jiném kódování, musí se kódování poměrně pracně převést – knihovna MD5 upravená pro Latin-2.

Závěr

S vynaložením nijak zvláštního úsilí můžeme zabezpečit své přihlašovací formuláře proti odposlechu. Použití HTTPS má samozřejmě i nadále svůj smysl, protože jednak šifruje všechna přenášená data a jednak dovolí ověřit i identitu protistrany. Technikou výzva-odpověď se ale dá bezpečnost přihlašovacích formulářů zlepšit i tam, kde použití HTTPS není z jakéhokoliv důvodu možné.

Kromě bezpečného přenášení hesla je vhodné zaměřit se i na jeho bezpečné ukládání na straně serveru a na vhodný způsob pamatování informace o přihlášenosti uživatele. Odchytávání hesel a jejich zveřejnění je ale jistě mediálně nejvděčnější…

Doplnění

Jak správně poznamenali čtenáři v diskusi, je tato technika velice citlivá na bezpečnost dat uložených v databázi. Proto nabízím její vylepšení:

  1. U každého uživatele bude uložen login, challenge a  md5(hmac_md5(password, challenge)).
  2. Při přihlašování se AJAXem zjistí, jaký challenge uživatel naposledy použil, a pošle se hmac_md5(password, old_challenge)md5(hmac_md5(password, new_challenge)).
  3. Na serveru se navíc ověří, jestli md5(old_hmac) souhlasí s tím, co je uloženo v databázi, a pokud ano, přepíše se to novými hodnotami.

Autorem této myšlenky je Paul Jonhston. Přikládám Proof of Concept.

Posílání výzev ze souvislé řady má kromě již zmíněné možnosti zjištění počtu zobrazení přihlašovacího formuláře ještě jednu nevýhodu – skript se může stát obětí útoku DoS. Pokud tomu chceme zabránit a nechceme si navždy pamatovat všechny náhodně vygenerované výzvy, můžeme výzvu ukládat do session proměnné.

Jakub Vrána

Jakub Vrána

Autor se živí programováním v PHP, podílí se na jeho oficiální dokumentaci, vyučuje ho na MFF UK a vede odborná školení. Poznámky si zapisuje na weblog PHP triky.

Školení: IP v 6 na Linuxu

Tento krátký kurz je určený speciálně pro zkušené správce sítí IPv4, kteří se chtějí seznámit s nastupujícím internetovým protokolem IPv6.

Platforma: Linux

  • Adresace
  • Link-local adresy
  • Dynamické přidělování adres
  • a další

Podrobnější informace a přihláška

Ohodnoťte jako ve škole:
Průměrná známka 2,95

Přehled názorů

Zabezpečení databáze s hesly
peta 12. 4. 2006 00:26
Nový
└ 
Re: Zabezpečení databáze s hesly
Lukáš Zapletal 12. 4. 2006 09:19
Nový
 
└ 
Re: Zabezpečení databáze s hesly
anonymní uživatel 12. 4. 2006 15:14
Nový
 
 
├ 
Re: Zabezpečení databáze s hesly
Igor 13. 4. 2006 11:22
Nový
 
 
│
└ 
Re: Zabezpečení databáze s hesly
Jirka Kosek 13. 4. 2006 11:47
Nový
 
 
│
 
├ 
Re: Zabezpečení databáze s hesly
Igor 13. 4. 2006 13:51
Nový
 
 
│
 
└ 
Re: Zabezpečení databáze s hesly
anonymní uživatel 13. 4. 2006 21:54
Nový
 
 
└ 
Re: Zabezpečení databáze s hesly
HKMaly 13. 4. 2006 20:19
Nový
 
 
 
└ 
Re: Zabezpečení databáze s hesly
peta 13. 4. 2006 21:31
Nový
 
 
 
 
└ 
Re: Zabezpečení databáze s hesly
Andrew 17. 4. 2006 19:47
Nový
Nojo, ale...
Michal Ludvig 12. 4. 2006 01:17
Nový
├ 
Re: Nojo, ale...
antonin chadima 12. 4. 2006 07:29
Nový
├ 
Re: Nojo, ale...
platYpus 12. 4. 2006 13:10
Nový
│
└ 
Re: Nojo, ale...
Ivo Peterka 12. 4. 2006 16:51
Nový
└ 
Re: Nojo, ale...
Bilbo 12. 4. 2006 13:19
Nový
Bezpecne ukladani vs. challenge-response
Michal Kára 12. 4. 2006 07:37
Nový
├ 
Re: Bezpecne ukladani vs. challenge-response
HKMaly 12. 4. 2006 07:49
Nový
│
├ 
Re: Bezpecne ukladani vs. challenge-response
Michal Kára 12. 4. 2006 07:55
Nový
│
│
├ 
Re: Bezpecne ukladani vs. challenge-response
Wejn 12. 4. 2006 09:36
Nový
│
│
└ 
Re: Bezpecne ukladani vs. challenge-response
Bilbo 12. 4. 2006 13:22
Nový
│
│
 
└ 
Re: Bezpecne ukladani vs. challenge-response
Michal Kára 12. 4. 2006 15:22
Nový
│
├ 
RSA v JavaScriptu
darBis 12. 4. 2006 09:51
Nový
│
└ 
Re: Bezpecne ukladani vs. challenge-response
Jakub Vrána 12. 4. 2006 10:45
Nový
│
 
└ 
Re: Bezpecne ukladani vs. challenge-response
junix 13. 4. 2006 17:18
Nový
│
 
 
└ 
Re: Bezpecne ukladani vs. challenge-response
Jakub Vrána 13. 4. 2006 17:32
Nový
│
 
 
 
└ 
Re: Bezpecne ukladani vs. challenge-response
junix 13. 4. 2006 18:23
Nový
│
 
 
 
 
└ 
Re: Bezpecne ukladani vs. challenge-response
Jakub Vrána 13. 4. 2006 20:38
Nový
│
 
 
 
 
 
└ 
Re: Bezpecne ukladani vs. challenge-response
sly 10. 8. 2006 15:27
Nový
└ 
Re: Bezpecne ukladani vs. challenge-response
sly 10. 8. 2006 15:24
Nový
postranni kanal
michal_sjx 12. 4. 2006 10:23
Nový
├ 
Re: postranni kanal
martin 12. 4. 2006 23:16
Nový
│
└ 
Re: postranni kanal
Michal S 12. 4. 2006 23:28
Nový
│
 
└ 
Re: postranni kanal
junix 13. 4. 2006 11:09
Nový
└ 
Re: postranni kanal
michal_sjx 12. 4. 2006 23:18
Nový
 
├ 
Re: postranni kanal
junix 13. 4. 2006 11:15
Nový
 
└ 
Re: postranni kanal
Igor 13. 4. 2006 11:32
Nový
Digest Access Authentication
Petr Mika 12. 4. 2006 16:27
Nový
Jednoduse?
Abraxis 12. 4. 2006 16:36
Nový
└ 
Re: Jednoduse?
Jakub Vrána 12. 4. 2006 16:46
Nový
napadeni seznamu
Pakanek 12. 4. 2006 19:37
Nový
Podezrele veci
Petr 12. 4. 2006 20:16
Nový
├ 
Re: Podezrele veci
Jakub Vrána 12. 4. 2006 21:38
Nový
│
├ 
Re: Podezrele veci
Petr Mika 13. 4. 2006 09:20
Nový
│
│
└ 
Re: Podezrele veci
Michal Krause 13. 4. 2006 09:34
Nový
│
│
 
└ 
Re: Podezrele veci
Petr Mika 13. 4. 2006 10:11
Nový
│
│
 
 
└ 
Re: Podezrele veci
Jakub Vrána 13. 4. 2006 10:24
Nový
│
│
 
 
 
└ 
Re: Podezrele veci
anonymní uživatel 15. 8. 2006 11:48
Nový
│
│
 
 
 
 
└ 
Re: Podezrele veci
anonymní uživatel 15. 8. 2006 18:24
Nový
│
│
 
 
 
 
 
└ 
Re: Podezrele veci
anonymní uživatel 16. 8. 2006 11:44
Nový
│
│
 
 
 
 
 
 
└ 
Re: Podezrele veci
Jakub Vrána 16. 8. 2006 14:52
Nový
│
└ 
Re: Podezrele veci
Petr 13. 4. 2006 19:14
Nový
│
 
├ 
Re: Podezrele veci
Jakub Vrána 13. 4. 2006 21:35
Nový
│
 
│
└ 
Re: Podezrele veci
Petr 13. 4. 2006 23:17
Nový
│
 
└ 
Re: Podezrele veci
Petr 13. 4. 2006 23:12
Nový
└ 
Re: Podezrele veci
martin 12. 4. 2006 21:39
Nový
 
└ 
Re: Podezrele veci
HKMaly 13. 4. 2006 21:08
Nový
 
 
├ 
Re: Podezrele veci
Leoš Bitto 13. 4. 2006 23:26
Nový
 
 
└ 
Re: Podezrele veci
kinghowa 17. 4. 2006 20:41
Nový
       

Tento text je již více než dva měsíce starý. Chcete-li na něj reagovat v diskusi, pravděpodobně vám již nikdo neodpoví. Pro řešení aktuálních problémů doporučujeme využít naše diskusní fórum.

Zasílat nově přidané příspěvky e-mailem