Digitální podpisy jsou odpovědí na stoupající nároky na bezpečnost výměny dat v prostředích, jakým je internet. V některých případech potřebujeme ověřit, byl-li dokument podepsán (a tedy snad i odeslán) osobou, od které jsme dokument očekávali, a následně umožnit příjemci ověřit, nebyl-li dokument během přenosu změněn. V tomto článku se podíváme na strukturu digitálního podpisu podle W3C, která je platná pro všechny platformy, v závěru předvedu malou ukázku sestavení digitálního podpisu v .NET Frameworku.

XML Signatures

Jak jsem již uvedl výše, digitální podpis je používán, aby bylo možné zjistit, jestli nebyl přijatý dokument během své cesty k příjemci změněn třetí osobou (man-in-the-middle attack).

Princip digitálních podpisů je založen na šifrování veřejným klíčem. Tato technika se jeví jako velmi výhodná, protože další požadavek, který je na digitální podpisy kladen, je možnost identifikace odesílatele. Pokud odesílatel použije pro šifrování svůj soukromý klíč, nemůže třetí osoba (samozřejmě pokud se nějakým způsobem nezmocní klíče) předstírat, že je „náš“ odesílatel.

Velmi důležitou vlastností XML digitálních podpisů (XML DSIG) je možnost podepisovat jen určité části dokumentu. Uzel podpisu totiž mimo jiné obsahuje potomka <reference>, uvnitř kterého říkáte, ke kterým částem dokumentu se podpis vztahuje.

XML Digital signing teoreticky

Než se pustíme do konkrétních ukázek podpisů, rád bych nejdříve nastínil tvorbu (nebo možná lépe sestavení) a následnou verifikaci XML podpisu teoreticky. Po přečtení tohoto kratičkého výkladu by vám mělo být jasné, jak umožňují digitální podpisy ověřit, jestli nedošlo k manipulaci s daty nebo samotným podpisem. (Zde ještě trošku odbočím – je možné podepsat i jiná data než jen XML soubory, například XHTML, binární data, obrázky v JPG a podobně.)

Jako první krok musíme podepisovaná data hashovat. Získané hashe shromáždíme a uložíme do speciálních elementů společně s informacemi o tom, co a jak bylo hashováno.

Hashovací algoritmy produkují pro stejný vstup vždy stejný výstup. Znamená to tedy, že pokud pošlu příjemci původní zprávu a její hashovou reprezentaci a sdělím mu, který z algoritmů jsem použil, bude schopen vygenerovat svůj hash zprávy. V případě, že se hashe shodují, je tu velká pravděpodobnost, že zpráva nebyla změněna.

Další z kroků by měl směřovat k zabezpečení tohoto hashe. Pokud by totiž útočník změnil i hash, byli bychom velmi rychle v koncích. Tento problém se dá řešit dvěma způsoby.

Za prvé můžeme přidat ke zprávě nějaké tajné „heslo“ (které však zná i příjemce). V tomto případě postupujeme tak, že před hashováním přidáme „heslo“ a hashujeme oboje najednou. Příjemce pak při ověřování postupuje stejně – vezme zprávu, přidá heslo a hashuje. Pokud by chtěl útočník zprávu nepozorovaně změnit, musel by znát heslo, aby mohl generovat hash, který si vygeneruje příjemce. Tento postup je znám pod pojmem Hash Message Authentication Code (HMAC).

Druhou možností je použít některý z asymetrických šifrovacích algoritmů. V souvislosti s XML DSIG se používá RSA a DSA. Asymetrickým algoritmům jsem se na Intervalu již trošku věnoval, proto jen ve zkratce. V asymetrických algoritmech (někdy též šifrování s veřejným klíčem, public-key cryptography) existují dva druhy klíčů – soukromý a veřejný. Asymetrické šifry mají mimo jiné tu vlastnost, že cokoli, co zašifrujeme soukromým klíčem, může být dešifrováno klíčem veřejným. V XML DSIG má soukromý klíč k dispozici pouze odesílatel a veřejný je k dispozici komukoli, kdo chce s odesílatelem komunikovat. Pokud na zprávu aplikuje odesílatelův veřejný klíč a vše proběhne v pořádku, je tu jistota, že zprávu podepisoval náš odesílatel (za předpokladu, že mu nikdo jeho soukromý klíč nezcizil).

Pokud tedy máme jasno, který z postupů nám vyhovuje lépe, můžeme pokračovat v podepisování.

Hashe již máme nashromážděné v elementu Reference. Nyní musíme jen vypočítat takzvaný digest elementu SignedInfo, podepsat tuto hodnotu pomocí zvoleného algoritmu a klíče (respektive message-authentication kódu) a uložit získanou hodnotu do elementu SignatureValue.

Ověření podpisu probíhá prakticky stejně, jen „odzadu“. Nejprve musíme z hodnoty podpisu znovu získat hashovou hodnotu elementu SignedInfo pomocí veřejného klíče a porovnat tuto hodnotu s nově spočítanou hashovou hodnotou elementu SignedInfo. Pokud jsou oba hashe stejné, máme zajištěno, že dokument byl odeslán správnou osobou. Následovně ověříme, jestli nebyl dokument změněn jednoduše tím, že porovnáme hashové hodnoty jednotlivých částí s hashi v elementech Reference. Pokud souhlasí i tyto hashe, je víc než pravděpodobné, že jsme obdrželi originál.

Syntaxe digitálního podpisu

Definice podpisu podle W3C vypadá zhruba následovně (znak „+“ říká, že element se může vyskytnout jednou nebo vícekrát, „?“ představuje žádný nebo jeden výskyt a element následovaný znakem „*“ se může vyskytnou v libovolném počtu):

<Signature ID?>
 <SignedInfo>
  (CanonicalizationMethod)
  (SignatureMethod)
  (<Reference URI? >
   (Transforms)?
   (DigestMethod)
   (DigestValue)
  </Reference>)+
 </SignedInfo>
 (SignatureValue)
 (KeyInfo)?
 (Object ID?)*
</Signature>

Element SignedInfo

Element SignedInfo obsahuje, zjednodušeně řečeno, informace o tom, co a jakým způsobem bylo podepsáno. Obsahuje vnořené elementy jako CanonicalizationMethod, SignatureMethod a Reference.

Element CanonicalizationMethod

Uvnitř tohoto elementu jsou uchovávány informace o tom, jakým způsobem byl element SignedInfo upraven, aby bylo zajištěno, že logicky stejné dokumenty budou též reprezentovány stejnou posloupností znaků. Uvedu malý příklad:

<customer id=“1145″ firstName=“Jan“ lastName= „Novák “ />
<customer id=“1145″ lastName=“Novák“ firstName=“Jan“ />

Jak vidíte, z hlediska informací se jedná o naprosto totožné zákazníky. Jsou však reprezentováni jiným XML zápisem – jinou posloupností znaků – což v důsledku znamená, že bude pro každý z řádků generována jiná hashová hodnota. Abychom pro oba elementy získali stejnou hashovou hodnotu a následně i stejný podpis, musíme zajistit, aby na ně algoritmus hleděl jako na úplně stejné objekty.

K dosažení tohoto cíle obsahuje standard digitálních podpisů algoritmus, který projde dokument a podle svých specifikací ho přetransformuje tak, aby byl dokument, který je logicky stejný, vždy reprezentován stejným textovým zápisem. Současný standard využívá algoritmu C14N nebo algoritmu Exclusive C14N. Více informací o tom, co a jak je transformováno, a o tom, co je ponecháno beze změny, se nejlépe dočtete ve specifikaci Canonical XML. Jen upozorním, že element Signature/SignedInfo/CanonicalizationMethod se stará pouze o strukturu elementu SignedInfo. V případě, že budeme chtít aplikovat tento algoritmus na vstupní dokument, musíme přidat trasfromaci do uzlu Signature/SignedInfo/Reference/Transforms/Transform. K tomu se ale ještě dostaneme.

Element SignatureMethod

Tento element říká, jaký algoritmus byl pro podpis použit. V souvislosti s XML DSIG se používá například RSA nebo DSA:

<SignatureMethod Algorithm=“http://www.w3c.org/2000/09/xmldsig#rsa-sha1″ />

Tento zápis říká, že element SignedInfo bude hashován algoritmem SHA1 a šifrován algoritmem RSA.

Element Reference

Element Reference obsahuje digest (hashovou hodnotu) části dokumentu. Která část byla hashována, určuje atribut URI. Uvnitř komplexního elementu Reference najdate další elementy – konkrétně Transforms, určující transformace zdrojového dokumentu, DigestMethod, který říká, jaký algoritmus byl použit pro tuto část dokumentu, a nakonec DigestValue, jehož „funkci“ není třeba osvětlovat. Podívejte se na malou ukázku, jak může vypadat v praxi uzel Transforms:

<Reference URI=““>
 <Transforms>
  <Transform Algorithm=“http://www.w3.org/2000/09/xmldsig#enveloped-signature“ />
  <Transform Algorithm=“http://www.w3.org/2001/10/xml-exc-c14n#“ />
 </Transforms>
 <DigestMethod Algorithm=“SHA1″ />
 <DigestValue>l4K903bFv6OuCAUQn/oyaM0ZEck=</DigestValue>
</Reference>

Atribut URI zde máme prázdný. Je to z toho důvodu, že podepisujeme celý dokument, který máme k dispozici, a také proto, že podpis se nachází uvnitř dokumentu (enveloped signature), není tedy třeba říkat, kde najdeme zdrojový dokument.

Uzlel Transforms může obsahovat libovolný počet uzlů Transform, kdy každý specifikuje, jak naložit s danou entitou před vypočtením DigestValue. V tomto příkladu říkáme, že podpis bude včleněn do dokumentu (enveloped signature) a že na dokument bude aplikován Exclusive C14N algoritmus. (Tímto plním svůj závazek vrátit se k tomu, jak použít jmenovaný algoritmus na zdrojový dokument, nikoli jen na element SignedInfo.)

Na dokument můžete ale aplikovat i XSLT nebo XPath „výběry“. V případě XPath je však nutné ještě vložit do uzlu Transform také uzel XPath:

<Transforms>
 <Transform>
  <XPath> self::text() </XPath>
 <Transform>
</Transforms>

Další uzel, DigestMethod, nese informace o použitém algoritmu. V souvislosti s XML DSIG je snad nejčastěji používaným hash algoritmem SHA1, můžete ale použít i SHA256, SHA384, MD5 a další.

Element SignatureValue

Obsah tohoto elementu je jasný z názvu a ani v praxi na náhodného pozorovatele nečeká žádná záludnost. V tomto elementu je zkrátka hodnota podpisu – tedy šifrovaný digest elementu SignedInfo.

Element KeyInfo

Element KeyInfo není povinný. Do tohoto elementu je možné vložit například veřejný klíč, který se má použít pro verifikaci platnosti dokumentu, nebo lépe X.509 certifikát. Pokud totiž přiložíme přímo k podpisu veřejný klíč, zaděláváme si tak na další otázky ohledně toho, kdo tam vlastně ten klíč vložil. Můžeme to tedy buď udělat tak, že klíč putuje jinudy než správa (například může být dostupný na webu odesílatele) nebo použijeme certifikát. Certifikáty vydávají certifikační agentury, které umožňují ověřit, jestli poskytnutý veřejný klíč opravdu náleží osobě, která to tvrdí. V tomto dokumentu se certifikáty zabývat nebudu, především proto, že v .NET Frameworku 1.x byla práce s certifikáty značně omezená. Předpokládám však, že se k tomuto tématu v některém z článků ještě vrátím.

Ukázka použití XML Digital signing v .NET

Nyní jsme, myslím, pokryli to důležité z XML DSIG a mohli bychom si ukázat, jak s podpisy pracovat v .NETu. Napíšeme jednoduchou ASP.NET stránku, která vytvoří klíče pro šifrování a podepíše předaný XML dokument.

protected void createKeyFile_Click(object sender, EventArgs e)
{
 AsymmetricAlgorithm key = null;
 
 key = new RSACryptoServiceProvider();
 
 XmlDocument publickey = new XmlDocument();
 publickey.InnerXml = key.ToXmlString(false);
 publickey.Save(Server.MapPath(„~/xml/publickey.xml“));
 
 XmlDocument privatekey = new XmlDocument();
 privatekey.InnerXml = key.ToXmlString(true);
 privatekey.Save(Server.MapPath(„~/xml/privatekey.xml“));
}

Chceme-li něco podepisovat, musíme si nejprve obstarat klíče. Tato jednoduchá procedura vygeneruje nový RSA klíč a uloží jeho privátní verzi, tedy výše zmiňovaný soukromý klíč, do jednoho a veřejný klíč do druhého XML dokumentu. Soubory mohou vypadat zhruba tak, jak je uvedeno v ukázkách níže.

Ukázkový veřejný klíč:

<RSAKeyValue>
 <Modulus>zSFVSoitKcdUBIliyA7bpDlXIcvMGyM83NhEn+zQH/QTTcRU67
   yCRiVeZ3B3z07MdXxLkh2fZG0IJLQNEPL7PrtuvzwuUdXXobEYd7T
   xv5m+/wld+Dmqz0r3oeajiz2o15sra0XW/twuc7G/cUuGHCYD/7g0
   5Qz1HweghnZGPZ8=</Modulus>
 <Exponent>AQAB</Exponent>
</RSAKeyValue>

Ukázkový soukromý klíč:

<RSAKeyValue>
 <Modulus>zSFVSoitKcdUBIliyA7bpDlXIcvMGyM83NhEn+zQH/QTTcRU67
   yCRiVeZ3B3z07MdXxLkh2fZG0IJLQNEPL7PrtuvzwuUdXXobEYd7T
   xv5m+/wld+Dmqz0r3oeajiz2o15sra0XW/twuc7G/cUuGHCYD/7g0
   5Qz1HweghnZGPZ8=</Modulus>
 <Exponent>AQAB</Exponent>
 <P>+hf9tBSUqFJz5RUJ9y9iGL1nq5HTh9/oGvAcl8xhVoVFkPsayfgTUviW
   187t4c2VE/+QYSKpcqD5XLF2cc8vgw==</P>
 <Q>0fmAzX6TKpvUNgK0Eh/8nXPgJxQ8lOfLsmTLcfDDpwMvi0qGUF/LW/pP
   vbbFUlKJYhnM9ilce0Im9ZvYPkjitQ==</Q>
 <DP>6yxCMuOjgsC+IK3vCBTzfuYkpW5kZoHDpgkiKhBTe+OorhOidkekDEK
   cveTlRo9mXz7TyrOoeZUrx+FMyuqT9w==</DP>
 <DQ>ONwawAr9qTUngzS6NIpK6wxc79gwC5a1d2qKGSG8qbZYIp0cwBkgxZi
   EDXi3+Hh3WARql2Jd89bXG/2G0l8EXQ==</DQ>
 <InverseQ>OBVn27OKmFoVCLGDb5W3dYK9sFY3MD4K6+cDeOw6IwmtP9ETh
   4zGAnty/wq+dlIVL2f/pUaGdTaOB8Gan1kK4g==</InverseQ>
 <D>pLZvVjoJUdLMlBf9ScckdunUHT8X1XKmHG9MK15sgDvshDVyOxhTQyAe
   fdDBY6VSZ8Q2qP82Td1+/HnXRBTXyEYsCx3MGh/cYCMi0fQPnt0Zn
   lRBuA/6DMkFMVk/aDt1xaE/wubKXXv0pYpUk18Wdm4baeeHFiJMwM
   m42MA5soE=</D>
</RSAKeyValue>

Máme-li klíč, můžeme začít podepisovat. K sestavení podpisu nám poslouží například tato metoda:

XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath(„~/xml/doc.xml“));
 
XmlDocument keydoc = new XmlDocument();
keydoc.Load(Server.MapPath(„~/xml/privatekey.xml“));
 
AsymmetricAlgorithm key = new RSACryptoServiceProvider();
key.FromXmlString(keydoc.InnerXml);
 
SignedXml sig = new SignedXml(doc);
sig.SigningKey = key;
 
if (includeKeyInfo.Checked)
{
 KeyInfo ki = new KeyInfo() ;
 ki.AddClause(new RSAKeyValue((RSA)key));
 sig.KeyInfo = ki;
}
 
Reference refr = new Reference(„“);
refr.DigestMethod = „SHA1“ ;
refr.AddTransform(new XmlDsigEnvelopedSignatureTransform());
refr.AddTransform(new XmlDsigExcC14NTransform());
 
sig.AddReference(refr);
 
sig.ComputeSignature();
 
doc.DocumentElement.AppendChild(sig.GetXml());
doc.Save(Server.MapPath(„~/xml/doc_signed.xml“));

Pokusím se ve zkratce objasnit, o co v této metodě jde. Nejdříve nahrajeme do objektu typu XmlDocument dokument, který hodláme podepsat. Poté si z XML dokumentu obsahujícího soukromý klíč vytvoříme objekt, který tento klíč ponese a který předáme objektu SignedXml do vlastnosti SigningKey. Objekt SignedXml slouží právě pro sestavování podpisů. Jeho konstruktor přebírá jako parametr odkaz na objekt XmlDocument.

Další krok záleží na volbě uživatele – pokud zaškrtne na stránce CheckBox a sdělí tím programu, že chce zahrnout veřejný klíč do podpisu, přidáme k objektu SignedXml objekt typu KeyInfo, jehož metoda AddClause přebírá jako argument hodnotu klíče.

Dále musíme sesbírat jednotlivé prvky elementu Reference. V této ukázce ponecháme URI prázdné – podepisujeme celý dokument, digest budeme získávat algoritmem SHA1, podpis vložíme přímo k dokumentu a na dokument aplikujeme Exclusive C14N algoritmus.

Na závěr zavoláme metodu ComputeSignature() a soubor uložíme.

Pro úplnost uvedu ješte metodu ověřující platnost podpisu:

XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath(„~/xml/doc_signed.xml“));
 
XmlNodeList nodeList = doc.GetElementsByTagName(„Signature“);
 
SignedXml sig = new SignedXml(doc);
XmlElement signatureElement = (XmlElement)nodeList[0];
sig.LoadXml(signatureElement);
 
if (signatureElement.GetElementsByTagName(„KeyInfo“).Count == 0)
{
 AsymmetricAlgorithm key = null;
 
 XmlDocument keydoc = new XmlDocument();
 keydoc.Load(Server.MapPath(„~/xml/publickey.xml“));
 
 key = new RSACryptoServiceProvider();
 key.FromXmlString(keydoc.InnerXml);
 
 lblStatus.Text = sig.CheckSignature(key) ? „OK“ : „ERR“;
}
else
{
 lblStatus.Text = sig.CheckSignature() ? „OK“ : „ERR“;
}

Zde není co vysvětlovat. Nahrajeme dokument, jehož podpis hodláme ověřit, získáme veřejný klíč (pokud je zahrnut element KeyInfo, necháme práci s klíčem na .NET Frameworku, jinak načítáme z externího XML souboru) a zavoláme metodu CheckSignature(), které předáme jako parametr klíč (není-li u podpisu).

Souhrn

Z tohoto článku byste si měli odnést především povědomí o poslání XML digitálních podpisů. Mechanismus XML DSIG slouží k tomu, abychom byli schopni identifikovat odesílatele zprávy, k čemuž nám dopomohou asymetrické algoritmy, ale také k tomu, abychom odhalili změny, které v dokumentu někdo napáchal během jeho cesty od původce k příjemci. Teoretický úvod do XML DSIG je společný pro všechny platformy, vychází z univerzálního standardů W3C, praktická aplikace už záleží na konkrétním prostředí, v němž XML DSIG použijete.

Odkazy a zdroje

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Žádný příspěvek v diskuzi

Odpovědět