Ochrana hesla hashom a custom autentifikácia

V príspevku s veľavravným názvom :-) Custom authentication sme si ukázali, ako jednoducho sa dá nahradiť Provider funkcionalita pri forms autentifikácii niečím efektívnejším a prispôsobiteľnejším. MembershipProvider však ponúka oveľa viac funkcionality. Ponúka napr. možnosť zvoliť si úroveň ochrany uloženého hesla. K dispozícii máme ochranu symetrickou šifrou alebo ukladanie hesla v salt hashovanej podobe. Takúto ochranu by sme radi asi doplnili do našej aplikácie.

V krátkosti si povedzme o zmysle a výhodách jednotlivých prístupov.

Symetrická šifra

Ukladanie hesla zakódovaného symetrickou šifrou je miernym vylepšením bezpečnosti oproti ukladaniu v čistej podobe. Ak sa útočník dostane ku databáze používateľov s heslami, musí získať ešte klúč, prostredníctvom ktorého sa heslá zakryptovali. Ak uložíme kľúč niekam na suborový systém a navyše iný počítať, ako je umiestnená databáza, nastavíme striktne oprávanenia na súborovom systéme nad týmto súborom, alebo ho inak bezpečne a oddelene uložíme, značne skomplikujeme rekonštrukciu hesiel. Útočník musí získať aj onen kľúč. Určitou výhodou prístupu môže byť fakt, že heslo sa dá spätne dekryptovať a ak existuje bezpečný kanál pre jeho doručenie, vieme mu umožniť prihlásenie bez komplikácií.

Hashový algoritmus

Hash algoritmus je algoritmom tranformujúcim reťazec ľubovoľnej dĺžky na iný reťazec konštatnej dĺžky tak, aby nebolo možné spätne rekonštruovať pôvodný reťazec a aby existovala vysoká entropia výsledných reťazcov.

Výhodou, ktorú prináša použitie hashu je nemožnosť rekonštruovať pôvodné heslo. V prípade, ak ukladáme heslá v takejto podobe do databázy, odcudzením databázy vzniká útočníkovi problém s rekonštruovaním hesiel. Ak by sme však zostali len pri samotnom hashovom algoritme, má k dispozícii pomôcku – tzv. dictionary(slovníkový) útok, kedy si predgeneruje hashe pre celú alebo špecifickú množinu reťazcov, a jednoduchým porovnaním dokáže veľmi jednoducho napárovať hash a odhaliť heslo.

Nevýhodou použitia hashu je nemožnosť pri heslo rekonštruovať a opätovne ho oznámiť používateľovi. Jediný spôsob, ako mu umožniť znovuprihlásenie je vygenerovať heslo nové, ktoré obdrží podľa možností bezpečným kanálom prípadne bude novovygenerované heslo slúžiť len ako prostriedok pre jeho prvé prihlásenie s následným vynútením si zmeny.

Salt

Existuje však aj spôsob, ako efektívne predísť slovníkovým útokom. Pre každé ukladané heslo vygenerujeme náhodný reťazec - tzv. Salt, ktorý pripojíme ku heslu a až potom vytvoríme hash z celého takto vzniknutého reťazca. Salt uložíme do databázy spolu s výsledným hashom. Útočník by musel pregenerovať tabuľku hashov pre každý cieľový salt, aby mohol použiť slovníkový prístup na jeho spätné získanie. Tým sa jeho snaha výrazne, priam až ketegoricky, časovo skomplikuje.

Naša práca s heslom je však naďalej veľmi jednoduchá. Načítame salt uložený pre daného používateľa, spojíme s heslom, vytvoríme hash a ten porovnáme s hashom uloženým v databáze. Ak sa oba reťazce zhodujú, používateľ zadal správane heslo.

Na tento účel nám postačí pár statických metód. Na vytvorenie Hashu použijeme pre jednoduchosť funkciu FormsAuthentication.HashPasswordForStoringInConfigFile a SHA1 hash algoritmus.(K dispozícii máme ešte MD5 hashový algoritmus. Ten sa však vďaka zisteným vážnym "trhlinám" prestal používať a aj keď pri SHA1 bola tiež trhlina zistená, stále, nie je katerogorická.)

Na vygenerovanie saltu použijeme RNGCryptoServiceProvider - generátor náhodných čísel, ktorý je šítý práve na takýto účel.

using System;

using System.Web.Security;

using System.Security.Authentication;

using System.Security.Cryptography;

 

namespace ASPNET.Security

{

    public class AuthenHelper

    {

        public static string GetHashedPassword(string password, string salt)

        {

            String foo;

            return SaltPassword(password, salt, out foo);

        }

 

        public static string GetHashedPassword(string sPassword, out string sSalt)

        {

            return SaltPassword(password, null, out salt);

        }

 

        private static string SaltPassword(string password, string salt, out string genSalt)

        {

            genSalt = null;

            if (salt == null)

            {

                RNGCryptoServiceProvider cryptoProvider = new RNGCryptoServiceProvider();

                byte[] arrSalt = new byte[16];

                cryptoProvider.GetBytes(arrSalt);

                salt = Convert.ToBase64String(arrSalt);

                genSalt = salt;

            }

            return FormsAuthentication.HashPasswordForStoringInConfigFile(salt + password, "SHA1");

        }

    }

}

Modifikovaný kód prihlasovacej stránky z článku by mohol vyzerať takto.

public void LoginCtrl_Authenticate(object sender, AuthenticateEventArgs e)

{

    IDACUser user = (IDACUser)DACFactory.Get(DACObject.User);

    user.Login = Lgn.Text.ToLower();

 

    //Ziskanie zakladnych udajov o userovi potrebnych pri autentifikacii

    if (!user.GetAuthenticationInfo())

    {

        dvMsg.InnerText = Resources.User.ErrorLoginUser;

        dvMsg.Visible = true;

        return;

    }

 

    string password = Pwd.Text.Trim();

    //vytvorenie hashu na porovnanie z prave zadaneho hesla a ulozeneho saltu

    string hashedPassword = AuthenHelper.GetHashedPassword(password, user.Salt);

 

    //porovnanie oboch hashov, hashu hesla ulozeneho v db a toho prave vyrobeneho zo zadaneho hesla

    if (hashedPassword != user.Password)

    {

        dvMsg.InnerText = Resources.User.LoginFailure;

        dvMsg.Visible = true;

        return;

    }

 

    TestIdentity identity = new TestIdentity();

    identity.Login = user.Login;

    identity.ID = user.ID;

    identity.DisplayName = user.FirstName +" "+ user.SurName;

    identity.Company.ID = user.Company.ID;

    identity.Company.Name = user.Company.Name;

 

    CustomPrincipal principal = new CustomPrincipal(identity, new string[] {"Director", "Pensioner"});

    HttpContext.Current.User = principal;

    string state = principal.SaveStateAsBase64String();

 

    DateTime now= DateTime.Now;

    FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,

        identity.Login,

        now,

        now.AddMinutes(60),

        LoginCtrl.RememberMeSet,

        state);

 

    string encodedTicket = FormsAuthentication.Encrypt(ticket);

    HttpCookie ticketCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encodedTicket);

    Response.Cookies.Add(ticketCookie);

 

    string returnUrl = Request.QueryString["ReturnUrl"];

    Response.Redirect(!string.IsNullOrEmpty(returnUrl) ? returnUrl : "~/Private.aspx",

        true);

}

Kód, kde vytvárame používateľa by mohol vyzerať symbolicky takto:

string salt = null;

user.password = AuthenHelper.GetHashedPassword(TBPwd.Text.Trim(), out sSalt);

user.salt = sSalt;

//...

user.Create();

//...

Komentáre

# Custom authentication - Tom???? - DevBlog said:

Pingback from  Custom authentication - Tom???? - DevBlog

Saturday, June 30, 2007 10:50 AM
# mato said:

Ahoj

ja bz som sa ta chcel opytat, ci by si nemohol uverejnit nejaku jednoduchu ukazkovu aplikaciu kde sa vyuziva prihlasovanie a uzivatelia sa tahaju z databazy napr MS SQL. Prave zacinam robit v ASP NET, doteraz som robil PHP a akosi sa mi to tu zda vsetko omoc komplikovanejsie a moc sa mi nedari, aj ked mozno to je len prvy dojem. Uz som prestudoval asi 3 knihy ale take daco konkretne a jednoduche na pochopenie som nenasiel. Dakujem moc

Wednesday, July 18, 2007 3:02 PM
# T said:

Ahoj, ver tomu, ze je to len o prvom dojme. Su dve moznosti, bud vyuzijes existujucu infrastrukturu, ktoru .net ponuka...pozri tutorial videa na www.asp.net a mas to naklikane za 10minut alebo si vytvoris vlastne infrastrukturu.

Ja tu resp. prechadzajucom diely ponukam uz trosku sofistikovenejsie pristup, ako obist membership infrastrukturu. Samotne pouzitie je VELMI jednoduche.

Ale ak pojdes postupne ako pisem, tak to urcite naklikas, alebo ak budes mat s niecim problem, kludne napis, odpises / doplnim.

To co sa riesilo Includom na zaciatku kazdej stranky v PHP sa v .NETe riesi zavesenim sa na spravnu udalost aplikacie...a to zabecpeci autentikacny modul z clanku, ktory len staci zaregistrovat v Tvojom web.configu a hotovo. Jedine co musis urobit je jeden lookup do DB, ci je spravne heslo.

Thursday, July 19, 2007 8:28 AM
# kardo said:

Ahoj,

 no chcem sa teda este opytat ten SQl MemberShipProvider pristupuje k nejakej databaze uzivatelov resp tabulkam uzivatelia a role atd co su uz priamo vytvorene pre potreby tohto providera v .net frameworku? alebo ako ked tam nikde nemozem najst kam sa tie data zapisuju a odkial sa tahaju. No a druha vec je taka ze ja mam navrhnutu vlastnu databazu uzivatelov a chcel by som tu autentizaciu a autorizaciu robit z nej tak neviem ci mam nato napisat vlastneho providera alebo sa to da aj priamo cez toho SQLMemberShipProvidera alebo ako?

Dik moc

Friday, July 20, 2007 10:01 AM
# T said:

Vlastneho providera nepis v tom zmysle, ze by si po svojom implementoval interfaces, je to samovrazda(ja som robil implementaciu pre PostGreSql) aj ked da sa to, len mi nie je jasne naco.

Ak to klikas cez studio, vygeneruje Ti attached db AppData foldra. Scripty ak potrebujes, najdes niekede na disku, mozes si vytvorit potrebne tabulky do vlastnej db a len prekonfigurujes connection string. Uz som to niekde popisoval, skus hladat vo forach na www.aspnet.sk.

Ved si stiahni ten sampel z predchadzajuceho dielu, co som pridal.

Tam to mas rozchodene. Staci Ti nahradit tu cast, ktore vyhodnocuje meno a heslo tak, ze si to potiahnes s DB a jeden Select snad zvladnes zavolat. Atributy Identity usera si mozes popridavat, ake potrebujes.

Friday, July 20, 2007 5:25 PM
# T said:

www.aspnet.sk/.../ShowTopic.aspx

www.aspnet.sk/.../ShowTopic.aspx

Friday, July 20, 2007 5:41 PM
# kardo said:

Myslis z dielu  Custom authentication?

Friday, July 20, 2007 10:01 PM
# T said:

Ano, pozri na konci clanku.

Sunday, July 22, 2007 12:11 PM
# kardo said:

ahoj,

to som zasa ja:-) Prosim ta nevies mi poradit ako sa da vo VB.Net urobit taketo daco :

public class DBLIdentity : DBLayer

{

#region constructors

public DBLIdentity() : base() { }

......

}

Dik moc

Monday, July 23, 2007 2:28 PM
# kardo said:

na upresnenie myslim ten konstruktor s tym base

Monday, July 23, 2007 7:58 PM
Prihlásiť | Registrovať | Pomoc