HMAC - Hash Message Authentication Code

Technologie HMAC (Hash Message Authentication Code) je základem elektronického podpisu. Pomocí jednosměrné funkce pro výpočet kontrolního součtu (hashování) můžete ověřovat, zda data nebyla modifikována. S trochou chytrého využití můžete s hashem dělat lecjaká kouzla a zjednodušit vývoj aplikací a zároveň zvýšit jejich bezpečnost.

Co je hash a jak ho spočítat

Simson Garfinkel ve své knize o PGP použil pro hashovací funkce rozkošný výraz "matematická ráčna". Hashovací funkce umožní z jakýchkoliv dat spočítat cosi jako "kontrolní součet", v angličtině se nazývá "message digest" a do češtiny se obvykle překládá jako "výtah zprávy". Výsledku tohoto výpočtu se obvykle říkává "hash" (čti heš).

Hashovací funkce fungují pouze jedním směrem - můžete vypočítat hash ze zprávy (a je pokaždé stejný), ale neexistuje způsob, jakým byste mohli z hashe vypočítat zpět původní zprávu. Pravděpodobnost, že dvě různé zdrojové zprávy budou mít stejný hash, je přitom tak malá, že se obvykle neuvažuje. Tato vlastnost činí hashování mimořádně vhodným pro řadu aplikací, např. v kryptografii.

Hashovací funkce máte k dispozici i v .NET frameworku. Pracují (na vstupu i výstupu) s obecným polem bajtů. Výsledek hashování se obvykle zobrazuje zakódován pomocí Base16 nebo Base64 algoritmů.

Funkce pro výpočet kontrolního součtu z řetězce tedy může vypadat např. takto:

// using System.Security.Cryptography;

 

public static string ComputeHash(string s) {

    // Převést vstupní řetězec na pole bajtů

    byte[] data = System.Text.Encoding.UTF8.GetBytes(s);

 

    // Spočítat MD5 hash

    string r;

    using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider()) {

        byte[] hash = md5.ComputeHash(data);

 

        // Převést na Base64

        r = Convert.ToBase64String(hash);

    }

 

    // Odstranit padding - hash je vždy stejně dlouhý

    r = r.Trim('=');

 

    // Odstranit znaky, které nemohou být v URL

    r = r.Replace('/', '_');

    r = r.Replace("+", "-");

    return r;

}

Jak vidíte, vlastní výpočet hashe je na dva řádky, většinu kódu tvoří ošetřování vstupu a výstupu:

  1. Řetězec je nutné převést na pole bajtů. Já jsem zvolil reprezentaci v UTF-8. Myslete na to, že pokud používáte řetězce s diakritikou, musí být použito vždy stejné kódování, protože hash se počítá z bajtů, ne ze znaků.
  2. Výsledek je nutné pro většinu aplikací zakódovat tak, aby byl čitelný. Proto jsem zvolil metodu Base64, protože je vestavěná v .NET frameworku. Pozor: Base64 je citlivé na velká a malá písmena!
  3. Base64 využívá tzv. padding v případě, že počet bitů není beze zbytku dělitelný. Jako výplňový znak se používá "=". My ho můžeme bezpečně vypustit, protože hash je vždy stejně dlouhý, můžeme si ho v případě potřeby zpět domyslet.
  4. Base64 může obsahovat i znaky "+" a "/", které mají v URL speciální význam. Používá se proto tzv. "URL-safe Base64", kde se tyto dva znaky nahradí znaky "_" a "-".

Zkuste si spočítat hash řetězce "demo". Výsledek by měl být "_gHOKn-6yPr67XyYKgTiKQ".

Jakou hashovací metodu zvolit?

Obecně je nedostatek kvalitních hashovacích metod jedním z akutních problémů současné kryptografie. Hlavní problém spočívá v tom, že pro oba nejpoužívanější hashovací algoritmy (MD5 a SHA1) byly již nalezeny kolizní algoritmy. Tedy způsob, jak získat dvě různé zprávy, jejichž hash je tentýž. Pro účely popisované v tomto článku to sice nepředstavuje problém, ale obecně to ve střednědobém horizontu může problémy přinést.

V některých případech nemáte příliš na výběr, protože zejména ve starších standardech a protokolech je hashovací algoritmus určen napevno a nebo si můžete vybrat z MD5 a SHA1. Pokud takto omezeni nejste, obecně se doporučuje používat SHA512 (např. v případě ukládání hesel, kterýžto případ jsem na tomto webu již nedávno pojednával). Na druhou stranu, SHA512 hash je delší, než MD5 i SHA1, což hraje významnou roli, pokud ho budete například používat způsobem, zahrnujícím předávání v URL (query stringu). V takovém případě může být správným řešením použití teoreticky méně bezpečného, leč kratšího hashe.

Jak funguje HMAC

HMAC je postup, jímž je možno zjistit, zda nebyla informace cestou změněna. Základní postup je takový:

  1. Mějme informaci (zprávu), kterou chceme ověřit.
  2. Přidejme k ní jakákoliv další data, která protistrana nezná, ale my ano. Těm se obvykle říká "salt", neboli "sůl".
  3. Z kombinace zprávy a soli spočítejme hash.
  4. Výsledek předchozí operace pošleme na klienta, spolu s původní informací.

Po obdržení výsledku zpět postup v podstatě zopakujeme:

  1. Vezměme zprávu, kterou jsme obdrželi.
  2. Přidejme k ní sůl.
  3. Z kombinace zprávy a solu spočítejme hash.
  4. Pokud takto spočítaný hash souhlasí s tím, který jsme obdrželi spolu se zprávou, je všechno v pořádku, zpráva nebyla modifikována.

Praktické příklady

Tento postup se v případě webových aplikací používá především pro data, u nichž je třeba zajistit, aby při roundtripu na klienta nemohla být změněna. V případě ASP.NET se jedná například o ViewState: pokud v konfiguraci nastavíte hodnotu enableViewStateMac na true (resp. ji nezměníte - standardně je tato funkce zapnuta), bude ASP.NET ověřovat nepoškozenost ViewState právě tímto způsobem. Pokud budou ViewState data poškozená, vyhodí ASP.NET svou populární výjimku.

Generování CAPTCHA

HMAC je možno využít například při vývoji CAPTCHA prvků. Při psaní komentáře na tento web budete vyzvání k nepopulárnímu opisování textu z obrázku. Při jeho generování se využívá hashování se solí.

Generátor obrázků dostává jako vstupní údaj náhodně vygenerované číslo. Totéž číslo je zasláno ve skrytém poli formuláře. Kdesi v konfiguraci serveru je uložena náhodně vygenerovaná hodnota, která slouží jako sůl. Generátor ji přidá k číslu, které dostal, a spočítá z toho hash. Ten pak projde určitými úpravami: vyhodí se z něj všechny speciální znaky a znaky, u nichž by mohlo snadno dojít k vizuální záměně. Pak se z něj použije jenom prvních několik znaků, které se vykreslí do obrázku.

Poté, co jsou data poslána zpět na server, tento postup zopakuje a ověří si, zda jsou zadané znaky opsány správně.

Ověření e-mailové adresy

Provozujete-li službu, která zasílá e-mailové zprávy, je venkoncem dobrý nápad ověřit si, zda uživatelem zadaná adresa mu skutečně patří a že je funkční. Může to být užitečné jak z hlediska technologického (abyste zbytečně nerozesílali maily ne nesmyslné adresy), tak z hlediska právního. Pokud např. uživateli hodláte zasílat reklamu v podobě nějakého newsletteru, měli byste být schopni prokázat, že se k jeho odběru skutečně přihlásil.

Jednou z možností je uložit všechny informace o (například) nově registrovaném uživateli do databáze a přidat k nim nějaký příznak "neověřen" a náhodně vygenerovaný potvrzovací kód. Ten pak pošlete uživateli e-mailem a pokud ho někde na vašem webu zadá a ověří tak, že má přístup k dané e-mailové schránce, příznak odstraníte a umožníte mu přístup do systému. Tato možnost ale znamená, že musíte čas od času databázi čistit od nepotvrzených registrací a že vám mohou prostředky zabírat nepotvrzené automatizované registrace.

Lepší řešení může využívat HMAC. V prvním kroku registrace se zeptáte pouze na e-mailovou adresu. Zašlete na ni zprávu s odkazem, který obsahuje zadanou adresu a její hash se solí. Na této stránce pak shora popsaným způsobem ověříte, že potvrzovací kód souhlasí, a uživateli můžete dovolit pokračovat v registraci. V okamžiku mezi "nezávazným" odesláním zprávy a jejím případným potvrzením nemusíte nikde nic skladovat, ověření je automatické

Omezení časové platnosti ticketu

Shora uvedený základní postup lze dle potřeby modifikovat a rozšiřovat. Chcete-li, můžete např. učinit platnost potvrzovacího kódu časově omezenou. Zahrňte do "podepsaných" údajů i čas konce platnosti (expirace). Ten si pak můžete ověřit - s jistotou, že nemohl být změněn.

Metoda hashování se solí je jednoduchým řešení řady problémů, se kterými se při vývoji webových aplikací běžně potýkáme. Vzhledem k tomu, že je to řešení velmi elegantní, nenáročné a bezpečné, mohu ho vřele doporučit.

  • Altairis
  • Nemesis
  • Microsoft MVP
  • IIS
  • ASP.NET