Uchovávejte správně hesla!
Na Intervalu jsme se už prošli základy kryptografie v .NET. Tentokrát se zaměříme na palčivou otázku z praxe – hesla. Přestože neexistuje stoprocentně bezpečný způsob, jak hesla uchovávat, měli bychom se snažit to dělat co možná nejbezpečněji. Všechny ukázky budou napsány v ASP.NET, teoretické postupy jsou však více či méně společné pro všechna prostředí.
Hodně webů vyžaduje, aby uživatel před vstupem zadal svoje osobní heslo. Ať už to je firemní web, kde si můžete třeba jen objednat oběd na další den, nebo emailová schránka, přístup neoprávněné osoby je naprosto nežádoucí. Bylo by proto vhodné chránit tato hesla co možná nejlepším způsobem před zraky ostatních. Jsou dva typy „problémů“.
Jedním z nich je sám uživatel, který si zvolí heslo naprosto nevyhovující – například anakonda
nebo traktor
nebo dokonce jméno nějakého příbuzného. Pokud na takovéto heslo použije útočník takzvaný slovníkový útok, má během několika minut vyhráno. V rámci zapamatovatelnosti hesla se proto doporučuje nějaká kombinace alespoň dvou slov, pokud možno s použitím nějakých „nestandardních“ znaků (pomlčka, podtržítko a podobně) nebo čísel – například traktor_zetor
či 10alik01
. Ovšem pokud docílíte i tohoto, zdánlivě nemožného cíle (do uživatelů můžete hučet jak do dubu), nemáte vyhráno.
Za prvé, i takovéto heslo se dá odhadnout, i když už to není tak jednoduché. Druhým problémem jsou však sami vývojáři. Pokud i vhodně zvolené heslo je uloženo v nějakém datovém úložišti (ať v databázi nebo XML souboru) ve své textové, čitelné podobě, má útočník hodně možností, jak si heslo vytáhnout. Pro SQL databáze například SQL injection (do nedávna nejběžnější způsob útoku) nebo pro soubory XML stačí nezabezpečený adresář a není problém ani pro amatéra nechat si soubor zobrazit v prohlížeči. A hned má útočník všechna jména a hesla jako na dlani.
Hesla hešujte!
Pro vás, jako pro vývojáře, by se proto mělo stát samozřejmostí hesla uchovávat v nečitelné podobě. Jako vhodný způsob se nabízí hesla hešovat. Hešová podoba slova se dá totiž jen těžko zpětně dešifrovat do původní podoby. I kdyby znal útočník použitý algoritmus, dalo by mu hodně práce rekonstruovat původní text. Takový útočník si pak raději zvolí jiný cíl útoku. A to nám stačí.
Pokud způsob ukládání hesel a následného přihlašování uživatele znázorním schematicky, mělo by to vypadat zhruba následovně:
Znovu však upozorňuji, že znázorněný postup není odolný proti slovníkovým útokům!
Podíváme-li se na problém z pohledu kódu, mohlo by to v ASP.NET vypadat třeba takto:
<%@ Page Language=“C#“ %>
<%@ import Namespace=“System.Security.Cryptography“ %>
<%@ import Namespace=“System.Xml“ %>
<%@ import Namespace=“System.Data“ %>
<script runat=“server“>
string GetHash(string input)
{
byte[] bInputData = ASCIIEncoding.ASCII.GetBytes(input);
MD5 objMD5 = new MD5CryptoServiceProvider();
byte[] bHashResult = objMD5.ComputeHash(bInputData);
return ASCIIEncoding.ASCII.GetString(bHashResult);
}
void NewUser(object sender, EventArgs e)
{
bool userExists = false;
DataSet ds = new DataSet();
ds.ReadXml(Server.MapPath(„users.xml“));
DataTable dt = ds.Tables[0];
// test na kolizi dat
DataRow[] foundRows = dt.Select(„nick = ‚“+tbNick.Text+“‚“);
if(foundRows.Length > 0) userExists = true;
if(!userExists)
{
DataRow dr = dt.NewRow();
dr[„nick“] = tbNick.Text;
dr[„heslo“] = GetHash(tbPass.Text);
lblOut.Text = GetHash(tbPass.Text);
dt.Rows.Add(dr);
ds.AcceptChanges();
SaveDS(ds);
}
}
private void SaveDS(DataSet ds)
{
if (ds == null) { return; }
System.IO.FileStream fs;
System.Xml.XmlTextWriter writer = null;
string filename = Server.MapPath(„users.xml“);
fs = new System.IO.FileStream(filename, System.IO.FileMode.Create);
writer = new System.Xml.XmlTextWriter(fs, System.Text.Encoding.Unicode);
writer.Formatting = Formatting.Indented;
writer.Indentation = 5;
ds.WriteXml(writer);
if (writer != null)
writer.Close();
}
void TestUser(object sender, EventArgs e)
{
XmlTextReader xtr = null;
bool auth = false;
try
{
xtr = new XmlTextReader(Server.MapPath(„users.xml“));
while (xtr.Read())
{
if (xtr.Name == „user“)
{
if (xtr.GetAttribute(„heslo“) == GetHash(tbPass.Text))
auth = true;
}
}
}
finally
{
if (xtr != null)
xtr.Close();
}
lblOut.Text = auth ? „ok“ : „špatný nick nebo heslo!“;
}
</script>
<html>
<head>
</head>
<body>
<form runat=“server“>
Nick:
<asp:TextBox id=“tbNick“ runat=“server“></asp:TextBox>
<br />
Heslo:
<asp:TextBox id=“tbPass“ runat=“server“></asp:TextBox>
<br />
<asp:Button id=“btnOk“ onclick=“TestUser“ runat=“server“ text=“Odeslat!“></asp:Button>
<asp:Button id=“btnNew“ onclick=“NewUser“ runat=“server“ text=“Nový“></asp:Button>
<br />
<asp:Label id=“lblOut“ runat=“server“></asp:Label>
</form>
</body>
</html>
Databáze uživatelů:
<userlist>
<user nick=“rj“ heslo=“???S??
??%p??“ />
<user nick=“gates“ heslo=“?]?????JM???a??“ />
</userlist>
V příkladu jsem jako úložiště dat použil soubor XML. Teoretický postup při ukládání do jiných datových skladů je však analogický. Nyní bychom si mohli uvedený příklad projít krůček po krůčku.
string GetHash(string input)
{
byte[] bInputData = ASCIIEncoding.ASCII.GetBytes(input);
MD5 objMD5 = new MD5CryptoServiceProvider();
byte[] bHashResult = objMD5.ComputeHash(bInputData);
return ASCIIEncoding.ASCII.GetString(bHashResult);
}
Toto je jednoduchá metoda, která vstupní text hešuje algoritmem MD5 a vrací řetězec, který již můžeme celkem směle uložit do databáze. Uvedenou metodu voláme jak při ukládání, tak i při testování správnosti hesla.
void NewUser(object sender, EventArgs e)
{
bool userExists = false;
DataSet ds = new DataSet();
ds.ReadXml(Server.MapPath(„users.xml“));
DataTable dt = ds.Tables[0];
// test na kolizi dat
DataRow[] foundRows = dt.Select(„nick = ‚“+tbNick.Text+“‚“);
if(foundRows.Length > 0) userExists = true;
if(!userExists)
{
DataRow dr = dt.NewRow();
dr[„nick“] = tbNick.Text;
dr[„heslo“] = GetHash(tbPass.Text);
// ukladame hashovanou podobu
dt.Rows.Add(dr);
ds.AcceptChanges();
SaveDS(ds);
}
}
Toto je metoda, která vytváří nového uživatele. Nás nyní zajímá především řádek dr["heslo"] = GetHash(tbPass.Text);
. Tady voláme již zmíněnou metodu GetHash()
a její výstup uložíme. Když se podíváte na soubor s uživateli, vidíte, jak zhruba vypadá výstup této metody. Testování uživatele při přihlašování může vypadat následovně:
void TestUser(object sender, EventArgs e)
{
XmlTextReader xtr = null;
bool auth = false;
try
{
xtr = new XmlTextReader(Server.MapPath(„users.xml“));
while (xtr.Read())
{
if (xtr.Name == „user“)
{
if (xtr.GetAttribute(„heslo“) == GetHash(tbPass.Text))
auth = true;
}
}
}
finally
{
if (xtr != null)
xtr.Close();
}
lblOut.Text = auth ? „ok“ : „špatný nick nebo heslo!“;
}
Tato metoda prochází úložiště dat a testuje uloženou hešovou podobu hesla s hešem hesla zadaného do textboxu. Pokud se heše shodují, je vše vpořádku a uživatel může být přihlášen. Zde zároveň vidíte, že není třeba používat některý ze symetrických či nesymetrických šifrovacích algoritmů, protože není třeba uloženou hodnotu dešifrovat. Možnost dešifrovat reprezentaci hesla je tady přímo nežádoucí! Dali bychom totiž útočníkovi další možnost, jak se k heslu dostat.
Starší komentáře ke článku
Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.
Mohlo by vás také zajímat
-
AI a internetové podvody
29. října 2024 -
Landing page: Jak vytvořit landing page s vysokým CTR
7. května 2024 -
Nové trendy v doménách pro osobní projekty – DIY, LIVING a LIFESTYLE
9. prosince 2024 -
Jak se chránit před podvody na internetu – část 1
8. října 2024
Nejnovější
-
Jak rozšířit úložiště Macu za pětinovou cenu?
16. prosince 2024 -
Nové trendy v doménách pro osobní projekty – DIY, LIVING a LIFESTYLE
9. prosince 2024 -
Jak chránit webové stránky před Web/AI Scrapingem
27. listopadu 2024 -
Jaký monitor je nejlepší k novému Macu Mini?
25. listopadu 2024