Přejít k navigační liště

Zdroják » Různé » Autentizace bez hesel

Autentizace bez hesel

Články Různé

Je to už nějaká chvíle, co se Internetem šířila zpráva o chybě Heartbleed. Mimo jiné ukázala, že autentizace pomocí hesel není dokonalá. Pojďme si ukázat, jak implementovat autentizaci, která hesla nevyžaduje.

Stručný úvod

Chyba Heartbleed ukázala, že autentizace pomocí hesel má své slabiny. Jak nejspíše víte, díky nedostatku v knihovně OpenSSL mohl útočník získat vaše heslo s uživatelským jménem ze serveru vámi používané služby. Všichni zodpovědní provozovatelé webů, kterých se toto týkalo, začali posílat varovné emaily, které nás vyzývaly ke změně hesla.

Z vaší strany uživatele si stačí tedy vytvořit nové heslo, nejlépe u každé služby, kterou na Internetu používáte. Snadné? Pokud jste zaregistrováni na spoustě webů, zabere vám to nezanedbatelně dlouhou dobu. Dostatečně dlouhou na to, abyste si mohli postěžovat, že jste ji museli obětovat kvůli něčemu, co jste nezpůsobili.

Přitom by stačilo hesla z autentizačního procesu odstranit. Potom byste vy po další podobné chybě nemuseli dělat vůbec nic. Vše by obstaral provozovatel. Vás by pak jenom o útoku/chybě informoval, ale také ujistil, že jste zase v bezpečí. Ale jak na to?

Email a SMS

Světe div se, ale takový systém už dlouhou dobu používáme, a sice při resetování našeho hesla. Tento proces většinou sestává z těchto kroků:

  1. Kliknete na odkaz “Zapomenuté heslo”
  2. Služba vám na email pošle zprávu s dočasným odkazem pro jeho resetování
  3. Link vás přesměruje na stránku, kde si můžete nastavit heslo nové

Občas se vás ještě ptají na jméno vašeho domácího mazlíčka nebo příjmení babičky za svobodna, ale to je pouze doplněk. Stejný princip (pouze s SMS) je vlastně použit také u dvoukrokového přihlášení, kdy se vám po prvním kroku pošle na mobil dočasný kód, který musíte zadat do aplikace.

Na co tedy potřebujeme hesla? Tento anglický článek hezky popisuje, jak funkci “Zapomenuté heslo” využít tak, abyste si museli pamatovat jenom jedno – k vaší mailové schránce. Způsob to ale není dokonalý, vyžaduje krok navíc – zvolení nového hesla. Jak by tedy měla vypadat autentizace bez hesel?

  1. Při přihlašování nám od uživatele stačí pouze emailová adresa nebo telefonní číslo
  2. Naše aplikace vygeneruje dočasný token a uloží si ho do databáze
  3. Pošleme email nebo SMS s tímto kódem uživateli (mezitím mu můžeme nabídnout omezený přístup – například veřejné informace, které stejně vidí ostatní)
  4. Při kliknutí na odkaz (nebo zadání kódu) naše aplikace obdrží daný token a zkontroluje, zda je platný
  5. Pokud ano, vytvoří nový s dlouhou platností, uloží ho do databáze a na klientovi
  6. Uživatel je nyní přihlášen (tento proces není potřeba opakovat, dokud nevyprší token nebo se uživatel nechce přihlásit na novém zařízení)

Výhody

Jaké z toho plynou výhody? Představte si přihlašovací formulář pouze s jedním políčkem. Není to krásné? Nechme ale nyní stranou uživatelské rozhraní a zaměřme se na bezpečnost. Pokud by každý web implementoval tento systém, uživatelovi si stačí pamatovat pouze jediné heslo, a to ke svému emailu. Toto heslo může být velmi silné (jedno takové si uživatel zapamatuje), aby bylo odolné vůči brute-force útoku. Je taky možné, že provozovatel mailové schránky může nabídnout takovou podporu při bezpečnostním problému, kterou si my třeba nemůžeme dovolit.

A teď konečně – co když se objeví chyba podobná Heartbleedu? Potom si stačí změnit pouze jedno heslo (což takový problém není). O všechno ostatní by se zodpovědní provozovatelé služeb měli postarat sami. Na jejich straně stačí pouze zneplatnit všechny tokeny (a samozřejmě opravit danou chybu). Vy se budete muset pouze zase přihlásit. Tohle stačí a vaše účty jsou opět na nějakou dobu v bezpečí.

Dále je to ochrana pro líné a nezodpovědné uživatele, kteří volí hesla typu “password”, “123456”, “qwerty” nebo “000000”. Při klasickém přihlašování, tím, že jim taková hesla zakážete, je jen naštvete. Navíc pravidla typu minimální počet znaků a co musí heslo obsahovat vadí i ostatním uživatelům a navádí je do podobné situace.

V neposlední řadě můžete s trochou snahy implementovat to, aby se bylo možné odkudkoliv odhlásit z určitého (nebo všech) zařízení. Protože je jedno, zda na straně klienta budete stav o jeho přihlášení uchovávat pomocí cookies nebo třeba local storage, vždy byste měli kontrolovat, zda je daný token platný i na serveru. Pokud není, uživatel není přihlášen.

Implementace

Nyní si ukážeme způsob, jak by se dal tento systém přihlašování implementovat v té nejjednodušší podobě. Ukázky budou psané v pseudo-kódu, abyste mohli použít váš nejoblíbenější jazyk, framework či databázi. Budu se zabývat pouze emailem, ale myslím, že s trochou snahy zvládnete SMS variantu vytvořit sami.

Model

V našem modelu uživatele budeme potřebovat pouze následující atributy: email a tokens. Políčko email je jasné.

Pojďme si vysvětlit tokens. Pokud chceme, aby se mohl uživatel přihlásit na více zařízeních, potřebujeme aby tento atribut mohl uchovávat více hodnot. V relačních databázích tohoto docílíme novou tabulkou Tokens, kterou s uživatelem spojíme pomocí one-to-many relace. Zato ve většině NoSQL databázích můžeme ukládat pole hodnot. To se nám bude hodit.

Záznam tokenu by měl obsahovat minimálně hodnotu tokenu a čas expirace. Token musí být náhodný řetězec. Snad každý jazyk nabízí možnost, jak generovat kryptograficky bezpečnou sekvenci znaků, nebo by k němu měla alespoň existovat knihovna. A co délka tokenu? To záleží na vaší bezpečnostní politice, ale mělo by stačit 16 znaků.

Dále budeme mít model s názvem třeba LoginRequest. Když se uživatel bude chtít přihlásit, vezmeme jeho ID a vygenerujeme dočasný token. Obě hodnoty vložíme do tohoto modelu opět spolu s časem expirace. Při ověřování uživatele přes odkaz budeme hledat v LoginRequest. Pokud ověření proběhne úspěšně, smažeme daný záznam v LoginRequest a přihlásíme uživatele vytvořením tokenu s dlouhou expirační dobou v User modelu.

Aplikace

Náš controller/presenter/cokoliv Authenticator bude obsahovat tyto metody:

  • Login(email) – vloží záznam do LoginRequest a pošle autentizační email
  • Authenticate(userid, token) – projde LoginRequest a zkontroluje, jestli jsou údaje platné. Pokud ano, vytvoří token u příslušného uživatele v modelu User
  • EnsureAuth(userid, authtoken) – umožňuje klientské aplikaci zjistit, zda je token, který má uložený, stále platný (tato metoda je užitečná spíše v SPA aplikacích)
  • Logout(userid, authtoken) – Vymaže uživatelův token patřící zařízení

Registraci zde nebudu řešit. Stejně se nijak zvlášť neliší od té klasické, pouze chybí pole “heslo” a “heslo znovu”. Pojďme se podívat na metodu Login:

user = User::FindByEmail(Request::email)

if user exists
    token = Token::Generate()
    //uživatelské ID, náhodný token, datum a čas za půl hodiny
    LoginRequest::Add(user::id, token, DateTime::NowPlusMinutes(30))
    //pošleme uživatelovi přihlašovací email s danými údaji
    Email::Template("login")::To(user::email)::Send(user::id, token)
    //všechno v pořádku
    Response::Send(200, "Byl vám odeslán autentizační email")
    
else
    //uživatel s danou adresou neexistuje
    Response::Send(404, "Uživatel s tímto emailem neexistuje")

Nejdříve ověříme, zda uživatel existuje. Pokud ano, vložíme záznam do modelu LoginRequest. Daný token platí půl hodiny. Pro přihlašovací token by bylo víc času na škodu. Třicet minut je více než dost. Pošleme email, ve kterém bude odkaz ve tvaru třeba takovémhle: mujweb.cz/authenticate?userid=<user::id>&token=<token>. Zajímavější je samotná autentizace (Authenticate):

userid = Request::userid
token = Request::token

record = LoginRequest::FindByUserId(userid)

//je token správný
if token is record::token
    //a je ještě platný?
    if record::expiration is still valid
        //autentizační token s dlouhou platností
        authtoken = Token::Generate()
        user = User::FindById(userid)
        user::tokens::Add(authtoken, DateTime::NowPlusDays(14))
        //uložíme si uživatele do nějakého našeho session objektu
        Session::Set("user", user)
        //pošleme token zpět, aby si ho mohl klient uložit
        //také vrátíme objekt uživatele
        Response::Send(200, authtoken, user)
        
    else
        //smažeme požadavek o přihlášení z modelu
        LoginRequest::Remove(record)
        Response::Send(401, "Platnost tokenu vypršela")
        
else
    Response:Send(401, "Neplatný token")

Pokud tokenu již vypršela platnost, smažeme ho. Jinak ho ale necháváme uložený. Proč? Uživatel se tak může na jedno přihlášení autentizovat na všech svých domácích zařízeních. Stačí pouze potvrdit odkaz na všech přístrojích a je hotovo. Ani nemusí chodit na přihlašovací obrazovku vaší aplikace. Problém může být obrana proti brute-force útoku. Půl hodiny je však podle mého názoru dostatečně málo na prolomení.

Jinak je, myslím, vše jasné. Doba platnosti tokenu je opět na vás – 14 dní je taková zlatá střední cesta. V reálné aplikaci by bylo asi vhodné nabídnout uživateli klasický “zapamatovat” check box. Pokud nechce, abychom si ho zapamatovali, žádný token ani ukládat nemusíme. Postačí nám objekt v naší Session.

Na řadu přichází metoda EnsureAuth, která je vhodná především pro SPA aplikace, které mají svůj token uložený například v local storage, kam se ze serveru nedostaneme. Pokud však máte klasickou serverovou aplikaci a token uložený například v cookies, hned na serveru víme, zda je uživatel přihlášen či ne.

user = Session::Get("user")

//pokud náhodou ještě nemáme uživatele uloženého v Session
if user exists
    Response::Send(200, user)
    
else
    userid = Request::userid
    authtoken = Request::authtoken
    
    user = User::FindById(userid)
    record = user::tokens::FindByToken(authtoken)
    
    if record exists
        if record::expiration is still valid
            //můžeme obnovit čas expirace
            record::expiration = DateTime::NowPlusDays(14)
            record::Save()
            
            Session::Set("user", user)
            Response::Send(200, user)
            
        else
            //vypršelý token už nebudeme potřebovat
            user::tokens::Remove(record)
            Response::Send(401, "Byl jste již odhlášen")
        
    else
        Response::Send(401, "Nejste přihlášen")

Myslím si, že kód mluví sám za sebe. Nejdříve zkoušíme, zda nemáme uživatele uloženého v našem Session objektu. To se může stát, pokud je třeba otevřen nový panel s naší aplikací a nově načtený klientský kód si bude chtít ověřit, zda je uživatel přihlášen. Pokud záznam v Session neexistuje, ověřujeme platnost posílaného tokenu. Pokud tento proces proběhne úspěšně, uložíme si objekt uživatele pro pozdější použití a pošleme ho také klientovi. Uživatel je přihlášen. Případně můžeme prodloužit platnost tokenu na dalších 14 dní. Pokud jakýkoliv krok v našem ověřování selhal, pošleme klientovi chybovou zprávu.

Jak se v této metodě bránit proti brute-force útoku? Útočník, pokud by náhodou věděl ID uživatele, může zkoušet uhodnout autentizační token. Pomineme-li fakt, že trefit náhodnou kombinaci 16 znaků je přinejmenším velmi obtížné, můžeme si zaznamenávat počet neúspěšných pokusů. Naše správně napsaná klientská část aplikace by se totiž měla zeptat neúspěšně nanejvýš jednou. A to jen, když má u sebe uložený token, který již není platný. Když server odpoví záporně, token se ze zařízení klienta musí smazat.

Jako poslední nám zbývá implementovat odhlášení pomocí Logout:

userid = Request::userid
authtoken = Request::authtoken

user = User::FindById(userid)
user::tokens::RemoveWhereToken(authtoken)
Session::Delete("user")

Response::Send(200, "Byl jste úspěšně odhlášen")

Stačí pouze odstranit daný token z databáze a vymazat objekt uživatele ze Session.

A to je vše. Ukázali jsme si naivní implementaci (neošetřujeme mnoho možných chyb, určitě by šlo přidat další bezpečnostní prvky, atd.) bezheslové autentizace. Tento základ se dá samozřejmě dále rozšiřovat. Prostoru k vylepšování je mnoho. Tak to zkuste. Důležité je také říct, že by vaší databázi prospělo občasné vyčištění od již neplatných tokenů. Záleží na počtu uživatelů, ale jednou za čas byste mohli skriptem projít všechny záznamy v User i LoginRequest a zkontrolovat jejich platnost. Ty s vypršelou expirační dobou totiž v databázi nepotřebujeme.

Námitky

K tomuto systému může mít spousta lidí určité výhrady. To je pochopitelné, nic není dokonalé a nic se nedokáže zavděčit všem. Zkusil jsem vymyslet nebo sesbírat několik námitek, které by mohl uživatel mít:

Nezajímá mě bezpečnost, ale pohodlnost!

Tak to je vaše rozhodnutí. Ale pokud zůstáváte přihlášený na vašem zařízení delší dobu (neustále), zas takové komplikace vám tento systém nepřinese.

Jsem zvyklý na dosavadní systém. Nechci nic měnit!

Na každou změnu se dá zvyknout. Pokud v tomto řešení nevidíte zvýšení bezpečnosti, ale pouze jen znesnadnění procesu přihlašování, moc mě to mrzí.

Nezahltí mi přihlašovací emaily mou schránku?

Zprávy můžete ihned po přihlášení mazat nebo si schránku nastavit tak, aby takové emaily mazala třeba den po doručení. Také je třeba připomenout, že pokud na svém zařízení zůstáváte přihlášeni, zas tolik zpráv vám chodit nebude.

Co když získá někdo přístup k mému emailu? Dostane tak možnost přihlásit se na každý můj účet.

To je opravdu nemilé, ale stejné nebezpečí hrozí i při současném systému, kdy útočník může zresetovat všechna vaše hesla, když má k vašemu emailu přístup. Tato situace může být vyřešena například pomocí sekundárního emailu, na který si zresetujete heslo vašeho primárního. Nebo použití mobilu. Případně kontaktovat provozovatele vaší schránky a dohodnout řešení s ním.

Kamarád si může vzít můj mobil a přihlásit se, když mu na něj dorazí autentizační kód.

Když dnes získá kamarád přístup k vašemu mobilu, může resetovat všechna vaše hesla pomocí emailu, který máte s mobilem synchronizovaný. Takový člověk ale není kamarád.

Co když se vám někdo nabourá do databáze? Pak bude mít přístup ke všem mým tokenům a pomocí jednoho se bude moci přihlásit.

To je pravda a při situaci, kdy se někdo dostane do databáze, je tento systém mnohem zranitelnější než řešení s hesly. Když se toto stane, je průšvih. Provozovateli však stačí smazat všechny platné tokeny, hned jak se o útoku dozví. Kdežto heslo vám jen tak nezmění (přestože se to může stát). Pak už se útočník nepříhlásí. Ale možnost, že se dostane na váš účet tu je, nicméně se ho nemůže plně zmocnit. Závažnější změny účtu by měly být potvrzovány opět emailem, který máte vy. A hned jak provozovatel provede daná opatření, znovu přihlásit se můžete zase jenom vy.

Závěr

Toto řešení není vhodné pro aplikace, kde je bezpečnost opravdu na nejvyšším místě. Mám na mysli banky, platební sytémy a podobně. Zde je potřeba zajistit neprůstřelné přihlašování. Taková řešení ovšem nebývají tak pohodlná, a proto je zbytečné je mít všude. A právě tam, kde doteď stačila pouhá hesla, by se mohla objevit bezheslová autentizace. Pokud máte otázky, námitky proti tomuto systému či návrhy na vylepšení implementace, podělte se v diskuzi.

Hotová řešení

NoPassword pro

Další čtení

Komentáře

Subscribe
Upozornit na
guest
24 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
pepca

Získání databáze tokenů by neměl být problém, pokud tokeny budou neúplné. Tedy naísto $server_token == $client_token by se kontrolovalo sha($client_cookie + $server_salt) == $server_hash, přičemž $client_cookie by nebylo na serveru uloženo. tedy pokud by útočník získal všechny $server_salt a $server_hash, stále by se nemohl snadno přihlásit jako libovolný klient, dokud by neuhodl odpovídající cookie.

pepak

Heartbleed neměl s hesly prakticky nic společného.

Tak nějak jsem čekal aspoň formální vypořádání se s námitkou, že maily putují po netu zcela nezabezpečeně a může si je přečíst každý, kdo sedí na cestě. SMS jsou zase drahé.

Co taková drobnost, že o mailový účet nebo telefonní číslo přijdu? Třeba proto, že příslušný poskytovatel skončil (mail) nebo mi někdo mobil ukradl (SMS). Nebylo by dobré mít nějaký nástroj, jak můj uživatelský účet převést na jiný mail/telefon? Připadá mi trochu hloupé přijít hned o všechno…

Petr

Ahoj,

to je dobrý nápad, ale volil bych ho asi jako poslední možnost, osobně se radši přihlásím např. přes google, facebook, twitter, linkedin, mojeid či obecně jakýkoliv openid… většina lidí účet na některé takové síti má.

Místo toho, abych zadával e-mail, nebo telefonní číslo, pak čekal, než mi přijde kód a pak ho kopíroval, nebo opisoval… tak radši kliknu na ikonu googlu a jsem přihlášený hned, protože na googlu jsem přihlášený většinou trvale.
V případě googlu platí, že je to jedno a to samé heslo (k e-mailu), které si musím pamatovat, se všemi výhodami a nevýhodami.

Zde popisovaná možnost je ale dobrá pro ty, co se neradi přihlašují přes jinou síť, nebo tam účet nemají… tedy jako takový fallback místo současného systému jména(e-mailu) a hesla.

Každopádně palec nahoru za nápad.

krab

Některé e-maily mají k sobě asociované OpenID (ze zkušenosti alespoň Gmail, Seznam).

Uživatelsky přívětivá volba může být přihlášení pomocí OpenID nebo jiného protokolu v případě že takový způsob z adresy rozpoznám a systém posílání tokenů e-mailem nechat jako zálohu. Jen pro případ, že pro daný e-mail nedokážu využít lepší protokol.

Tomáš Myšík

Hmm, asi by tokeny mělo jít hashovat úplně stejně jako hesla, ne? Nebo mi něco uniká?

Stanislav Nechutný

Co když se vám někdo nabourá do databáze? Pak bude mít přístup ke všem mým tokenům a pomocí jednoho se bude moci přihlásit.

Tak ty tokeny budu mít u sebe přeci uložené jen jako hashe s nějakou krátkou platností a když ho uživatel zadá, tak ho zahashuju a porovnám jen hashe jako se to dělá s hesly. Ty tokeny mají stejnou důležitost, jako hesla a tak by s nimi taky mělo být nakládáno. Pokud by leaknuly ty hashe, tak právě použití nějakého složitějšího hashovacího algoritmu a krátká doba životnosti zajistí, že útočník nestihne najít kolizní řetězce.

Jakub

Pekny clanek, dekuji

Bubla

Hesla si za mě pamatuje prohlížeč.
Takže dnes:
1) kliknu na položku v oblíbených
2) kliknu na tlačítko přihlásit (jméno a heslo předvyplnil prohlížeč)
…a jsem tam
Navrhované řešení:
1) kliknu na položku v oblíbených
2) kliknu na tlačítko přihlásit
3) přejdu do emailového klienta
4) kliknu na tlačítko pro synchronizaci pošty
5) počkám až se stáhne autorizační email
6) kliknu na něj
7) v detailu toho mailu kliknu na odkaz
…a jsem tam
Takže doufám, že se tohoto nápadu provozovatelé webu nechytnou.

danaketh

No a pak stačí jeden človíček s trochou času a znalostí a výsledek se brzy dostaví…

http://shubh.am/how-i-bypassed-2-factor-authentication-on-google-yahoo-linkedin-and-many-others/

LH

Mně by na tom popsaném způsobu nejvíce vadilo, že je šíleně složitý (viz příspěvek od Bubly) a především bych zdůraznil jeho pátý bod: „počkám až se stáhne autorizační email“. Poměrně běžně se mi stává, že mi tyto emaily chodí s několika minutovým zpožděním a občas navíc přijdou do spamu. Představa, že čekám třeba pět minut než se můžu někam přihlásit mě opravdu neláká. Jo, na nejpoužívanějších webech jsem přihlášený pořád, ale stejně se denně aspoň do jedné jiné méně používané služby přihlašuji.

Pavel D.

To je právě ten důvod, proč já bych to třeba nemohl použít. Protože u některých mých specifických výtvorů kvůli bezpečnosti vážně nabádám uživatele k odhlášení a případně je odhlásím po určité době nečinnosti. Rád bych se vyhnul heslům, ale pokud nepotřebuji, aby byl uživatel neustále přihlášen, spíš naopak, pak budu asi muset stále čekat nebo sám hledat nový způsob přihlašování a tenhle článek mě zase nadchl jenom svým nadpisem (což není chyba autora článku, ale oboru, ve kterém dělám). Třeba příště

zlatkofedor1

Pre prihlasovanie pomocou tokenov je tu este jedna moznost a to JSON web token

Priklad a library pre node je tu

V skratke: server ma svoj secret code, pomocou ktoreho sa generuje token pre uzivatela, ktory sa nasledne posiela pouzivatelovi. Rozdiel je v tom, ze token nieje iba retazec ale su v nom zakodovane udaje. Napr. ID usera a expiracia. Pri requeste od klienta sa token zvaliduje a je mozne ziskat ulozene informacie. Vyhoda: nieje potrebne ukladat nic na strane servera(aj deaktivacia tokenov je mozna bez ukladania) Nevyhoda: velkost rastie od poctu dat.

kominár

Dalsia diera: Moj poskyovatel emalu (napr. Google), bude vediet o kazdom mojom prihlaseni. Kedy, kam…
Doteraz to nevedel.
Co si neviete zapamatat par hesiel? Alebo si ich niekde napisat?
Siroty po ni zustali – si pamatam 30 rokov :-)

filip.jirsak

Pokud se chci všude přihlašovat jedním heslem, použiju OpenID nebo podobnou technologii. Celé přihlášení se v takovém případě odehraje maximálně během pár sekund, a ještě můžu nechat předat další údaje (jméno, e-mail, další kontakty), aniž bych je musel znova zadávat,

Při přihlášení přes e-mail musím čekat, než e-mail vůbec dorazí (což můžou být sekundy, ale také minuty i víc, případně ho musím hledat ve spamu). Musím se přepínat do jiného programu nebo do webmailu, po přihlášení budu mít v prohlížeči dvě záložky s webem, na který jsem se přihlašoval (první, ze které bylo přihlášení iniciováno, druhou, ve které bylo přes token dokončeno).

Je to zajímavý nápad, ale jak už jsem psal, existují řešení, která mají stejné výhody, ale nemají tolik nevýhod.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.

Pocta C64

Za prvopočátek své programátorské kariéry vděčím počítači Commodore 64. Tehdy jsem genialitu návrhu nemohl docenit. Dnes dokážu lehce nahlédnout pod pokličku. Chtěl bych se o to s vámi podělit a vzdát mu hold.