Asymetrické šifrování RSA v .NET - správa klíčů

V prvním dílu seriálu Přísně tajné šifry jsem popisoval použití symetrické šifry Rijndael alias AES. Zásadní nevýhodou symatrického šifrování je, že používá sdílený klíč, shodný pro šifrování a dešifrování. Tuto nevýhodu odstraňují asymetrické šifrovací algoritmy. Ve druhém dílu našeho seriálu se podíváme na správu klíčů nejobvyklejšího z nich - RSA.

Rivest - Shamir - Adleman

Stejně jako Rijndael a celá řada dalších algoritmů, i RSA odvozuje svůj název od jmen svých objevitelů. V tomto případě jsou jimi pánové Ronald Rivest, Adi Shamir a Leonard Adleman.

RSA je asymetrický algoritmus, což znamená, že používá ne jeden, ale dva klíče:

  • Veřejný klíč (public key) slouží k zašifrování dat. Tento klíč není třeba nijak tajit či chránit, ba naopak. Každý, kdo vám chce poslat zašifrovanou zprávu, ho musí mít k dispozici a je tedy nutné zajistit, aby ho mohl získat.
  • Soukromý klíč (private key, nazývaný též tajný klíč) slouží k dešifrování dat. Tento klíč musíte naopak žárlivě střežit a ochraňovat.

Zjednodušený popis RSA algoritmu můžete najít například na Wikipedii.

RSA klíče

Mezi klíči pro symetrické algoritmy (jako je AES) a pro algoritmy asymetrické (jako je RSA) je zásadní rozdíl. Generování klíčů pro AES je jednoduché, protože klíčem může být cokoliv, jakákoliv dostatečně dlouhá náhodná hodnota. Proto se také v případě AES tak často používají jednorázové klíče. Generování RSA klíčů je výpočetně dosti náročné, protože se nejedná o prosté sbírání chaosu, ale o výpočet extrémně velkých prvočísel. RSA klíče se obvykle pečlivě uchovávají a používají se opakovaně.

V případě RSA je také nutné pečlivě zvolit požadovanou délku klíče. V případě AES je volba jednoduchá, v drtivé většině případů se používá 256bitový klíč, tedy ten nejdelší, jaký algoritmus podporuje. U RSA s délkou klíče stoupá obtížnost prolomení šifry, ale zároveň také náročnost všech operací s šifrováním spojených - generování klíče, šifrování i dešifrování, a to nikoliv lineárně.

Zvýšíte-li délku klíče na dvojnásobek, zvýší se v průměru:

  • doba generování klíče 16×,
  • doba potřebná pro dešifrování (a další operace s tajným klíčem) 8×,
  • doba potřebná pro šifrování (a další operace s veřejným klíčem) 4×.

Délka klíče tedy závisí na několika faktorech. Klíčová je samozřejmě míra důvěrnosti utajovaných dat a zejména doba, po kterou bude nutné je chránit. Pokud mají být data důvěrná třeba i za několik desítek let, je nutné volit vyšší délku klíče, protože je nutno počítat s vývojem výpočetní techniky v této době. Pokud budete data přechovávat po krátkou dobu, nemusíte tuto otázku řešit.

Pokud máte k dispozici speciální hardware s procesory specializovanými na kryptografické operace, můžete si dovolit použít klíče s vyšší délkou, protože operace budou probíhat rychleji.

Klíče o délce 512 bitů bylo možno již v roce 1997 prolomit s hardwarem v ceně za milión dolarů v době okolo osmi měsíců. V současné době RSA Laboratories doporučují použití klíče o délce alespoň 1024 bitů, pro obzvláště důležité informace pak 2048 bitů. Nejdelší klíč, se kterým je na běžném HW možné normálně pracovat, je 4069 bitů.

Uchovávání a ochrana soukromých klíčů

Pro uchovávání RSA klíčů se používají tzv. kontajnery (key containers). Kontainerem je typicky soubor uložený na harddisku počítače (a chráněný prostředky operačního systému), ale může jím být i speciální hardwarové zařízení. Například čipová karta nebo šifrovací token, připojovaný přes USB rozhraní.

Ochrana soukromého klíče je totiž nejslabším článkem celého systému a jakékoliv implementace šifrování. Z praktického pohledu můžeme považovat všechny dnes standardně používané implementace šifrovacích algoritmů za bezpečné a současnými prostředky neprolomitelné. Největší bezpečnostní riziko tedy představuje, že se dostane do nesprávných rukou samotný klíč.

Pro důležitost zde znovu opakuji základní krédo jakékoliv kryptografie: šifrováním tajemství nezmizí, pouze se zmenší jeho objem. Místo zajištění bezpečného úložiště pro data sama musíme zajistit bezpečné úložiště pro klíč, který je umí dešifrovat.

Ultimátní obranou je fyzické odstranění klíče ze systému, když se nepoužívá. Čipové karty a šifrovací tokeny mohou v tomto směru být velkou pomocí. Neslouží totiž jenom jako paměťové médium, na kterém je klíč uložen, ale obsahují přímo kryptografický procesor, který provádí všechny operace s privátním klíčem. Klíč tedy kartu nikdy neopustí: je na ní vygenerován a veškeré operace s ním (dešifrování, podepisování...) probíhají přímo v jejím procesoru. Bývá dokonce hardwarově navržena tak, že klíč není možné získat a při pokusu o to se karta i s klíčem zničí.

Asymetrické algoritmy nabízejí ještě jednu možnost: v řadě případů není nutné, aby ten, kdo zprávu zašifroval, ji uměl též dešifrovat. Pokud budete například provozovat internetový obchod a uchovávat údaje o platebních kartách, pak web server (který je nejčastějším a nejsnazším cílem útoku) může disponovat pouze veřejným klíčem, protože data pouze šifruje. O jejich zpracování se může starat samostatný server, běžným smrtelníkům nedostupný.

Výhodou je, že kód pro generování klíčů a práci s nimi je v podstatě stejný, bez ohledu na to, jakého kryptografického providera použijete. Následující příklady používají jako úložiště dat disk počítače, ale jejich úprava pro použití např. čipové karty není nikterak náročná.

User store a machine store

Windows používá dvě základní úložiště pro RSA klíče: User Store a Machine Store. User store je určen pro data konkrétního přihlášeného uživatele a je přístupný pouze tehdy, pokud má uživatel natažený svůj profil. Používá se tedy typicky pro interaktivní aplikace. Machine store je určen pro data, která mají být obecně přístupna pro všechny uživatele na daném počítači. Konkrétní klíče jsou uloženy jako samostatné soubory, klíče v machine store je tedy možné (a vhodné) dále zabezpečit pomocí nastavení práv v NTFS filesystému.

Pro použití v rámci webové nebo jinak neinteraktivní aplikace (např. služby ve Windows) je zpravidla nutné použít machine store, protože nemáte k dispozici uživatelský profil a tudíž nemůžete využívat user store. Z toho vycházejí i následující příklady, které všechny využívají právě machine store.

User store je v adresáři %APPDATA%\Microsoft\Crypto\RSA\SID-uživatele. Machine store je v adresáři %PROGRAMDATA%\Microsoft\Crypto\RSA\MachineKeys (na Windows Vista) nebo %ALLUSERSPROFILE%\Application Data\Microsoft\Crypto\RSA\MachineKeys (na starších verzích Windows).

Generování klíčů

O práci s RSA algoritmem, tedy generování klíčů, šifrování a dešifrování se stará třída System.Security.Cryptography.RSACryptoServicesProvider. Pro vytvoření nového klíče potřebujeme znát pouze jeho požadovanou délku:

// using System.Security.Cryptography;

// using System.Xml;

 

int keyLength = int.Parse(this.DropDownListKeyLength.SelectedValue);

 

CspParameters csp = new CspParameters();

csp.KeyContainerName = "DEMO_RsaKeyGen_" + Guid.NewGuid().ToString("N");

csp.Flags = CspProviderFlags.UseMachineKeyStore | // uložit do machine store, nutné pro uživatele bez profilu

            CspProviderFlags.NoPrompt |           // dávkový (neinteraktivní) režim

            CspProviderFlags.UseArchivableKey;    // umožnit export privátního klíče

 

RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(keyLength, csp);

 

string publicKey = rsa.ToXmlString(false);

string bothKeys = rsa.ToXmlString(true);

Výše uvedený příklad je přiložen k článku jako soubor RsaGetKey.aspx. V reálné aplikaci by generování klíče patrně neprováděla webová aplikace, protože se jedná o úkon relativně zřídkavý, v dalších článcích si ukážeme jiné alternativy. Tato aplikace bude nicméně chodit, pokud ji spustíte pod uživatelem s distatečnými právy, jako například v rámci ASP.NET Developer Serveru, který běží v kontextu právě přihlášeného uživatele.

Pro další použití klíče je důležité znát název kontajneru, v němž je klíč uložen. Ten je definován vlastností KeyContainerName a ve shora uvedeném příkladu má formát "DEMO_RsaKeyGen_guid".

Pomocí metody ToXmlString můžete vyexportovat veřejný klíč nebo oba klíče do XML formátu.

Ve shora uvedeném schématu (web server má jenom veřejný klíč, backend oba) byste postupovali tak, že byste na zabezpečeném (backend) serveru vygenerovali klíč a pomocí metody ToXmlString vyexportovali veřejný klíč. Ten byste potom zkopírovali na web server, např. do konfigurace.

Práce s key containery

Privátní klíč sice také můžete exportovat do XML a pracovat s ním přímo, v praxi ho ale většinou necháte uložen v patřičném containeru. K tomu, abyste poté klíč získali, potřebujete znát název containeru, který jste zadali při jeho vytváření. Kód pro načtení klíče z containeru je následující:

CspParameters csp = new CspParameters();

csp.KeyContainerName = container;

csp.Flags = CspProviderFlags.UseMachineKeyStore | // použít machine store, nutné pro uživatele bez profilu

            CspProviderFlags.NoPrompt |           // dávkový (neinteraktivní) režim

            CspProviderFlags.UseArchivableKey;    // umožnit export privátního klíče

 

RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);

 

string publicKey = rsa.ToXmlString(false);

string privateKey = rsa.ToXmlString(true);

Microsoft .NET Framework neobsahuje žádné managed rozhraní, pomocí kterého byste si mohli vypsat seznam všech existujících containerů. Pokud po tom prahnete, musíte pomocí P/Invoke volat přímo CryptoAPI. Následující metoda vám vrátí pole obsahující seznam jmen všech containerů (pozn.: jedná se o minimální implementaci, která neprovádí žádné ošetřování chyb a podobně):

// using System.Security.Cryptography;

// using System.Runtime.InteropServices;

private static string[] GetContainers() {

    const uint CRYPT_VERIFYCONTEXT = 0xF0000000;

    const uint CRYPT_MACHINE_KEYSET = 0x00000020;

    const uint PROV_RSA_FULL = 0x00000001;

    const uint PP_ENUMCONTAINERS = 0x00000002;

    const uint CRYPT_FIRST = 0x00000001;

    const uint CRYPT_NEXT = 0x00000002;

 

    System.Collections.Generic.List<string> r = new System.Collections.Generic.List<string>();

 

    IntPtr h;

 

    // Otevřít context

    CryptAcquireContext(out h, null, null,

        PROV_RSA_FULL,

        CRYPT_VERIFYCONTEXT | CRYPT_MACHINE_KEYSET);

 

    // Načíst jména všech containerů

    uint flags = CRYPT_FIRST;   // chci první container

    uint cch = 512;            // max. délka jména

    StringBuilder sb = new StringBuilder((int)cch);

    while (CryptGetProvParamAnsiString(h, PP_ENUMCONTAINERS, sb, ref cch, flags)) {

        r.Add(sb.ToString());

        flags = CRYPT_NEXT;    // chci další container

    }

 

    // Zavřít kontext

    CryptReleaseContext(h, 0);

 

    // Vrátit jména všech kontajnerů jako pole stringů

    return r.ToArray();

}

 

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]

[return: MarshalAs(UnmanagedType.Bool)]

private static extern bool CryptAcquireContext(out IntPtr hProv, string pszContainer, string pszProvider, uint dwProvType, uint dwFlags);

 

[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]

[return: MarshalAs(UnmanagedType.Bool)]

private static extern bool CryptReleaseContext(IntPtr hProv, uint dwFlagsMustBeZero);

 

[DllImport("advapi32.dll", CharSet = CharSet.Ansi, SetLastError = true, EntryPoint = "CryptGetProvParam", ExactSpelling = true)]

[return: MarshalAs(UnmanagedType.Bool)]

private static extern bool CryptGetProvParamAnsiString(IntPtr hProv, uint dwParam, StringBuilder sb, ref uint cch, uint dwFlags);

V příkladech najdete stránku RsaKeyGet.aspx, která vám zobrazí seznam všech dostupných containerů a umožní vám zobrazit jejich klíče.

Slovo závěrem

V tomto článku jste se naučili, jak vytvářet a spravovat RSA klíče, což je nutnou podmínkou k tomu, abyste mohli využívat asymetrické RSA šifrování. O jeho vlastní implementaci si povíme v následujícím díle tohoto seriálu.

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