Artykuły

A A A
Drukuj Ekportuj do PDF
Opublikowane: 2007.06.28 23:35 | User 93757 | Aktualizacja: 2010.01.21 2:04

Podpisy XML w .NET

tagi: XML
Celem artykułu jest zapoznanie Czytelnika z podpisami XML oraz możliwością ich tworzenia i weryfikacji w aplikacjach tworzonych w ramach platformy .NET.
1. Co to są podpisy XML?
Podpisy XML (ang. XML Signature) są to specjalne podpisy cyfrowe służące głównie do elektronicznego podpisywania dokumentów XML (technicznie można podpisać wszystko do czego można uzyskać dostęp przez URL). Charakterystyczną ich cecha jest to, że podpisany dokument nadal jest poprawnym dokumentem XML, co ułatwić może jego przetwarzanie. Podpisany dokument zawiera dodatkowy element Signature, który zawiera w sobie właściwy podpis, jak również inne informacje które są potrzebne w celu jego weryfikacji.

2. Podstawy podpisów cyfrowych
Cyfrowy podpis pod dokumentem jest to ciąg znaków umożliwiający weryfikację treści dokumentu. Zmiana choćby jednego znaku w treści dokumentu lub w podpisie powoduje, że podpis staje się fałszywy.

Aby zrealizować podpis cyfrowy potrzebna jest para kluczy: prywatny i publiczny. Klucz prywatny służy do złożenia podpisu pod dokumentem, klucz publiczny służy do weryfikacji podpisu. Klucz prywatny powinien być szczególnie chroniony przez właściciela, gdyż jego kompromitacja powoduje, że można podpisać cyfrowo dowolny i udowodnić, że podpis złożył właściciel klucza. Klucz publiczny, jak sama nazwa wskazuje, powinien być publicznie dostępny dla wszystkich, aby możliwa była weryfikacja podpisu. W przypadku podpisów XML klucz prywatny może zostać dołączony do dokumentu XML (ale może zostać przekazany również inną drogą, np. przez stronę www). Charakterystyczną cechą pary kluczy jest to, że z klucza publicznego nie można efektywnie wyliczyć klucza prywatnego i vice versa.

Podpisy cyfrowe wykorzystują dwie istotne funkcje: ustaloną funkcję skrótu (inaczej nazywaną funkcją haszującą) oraz funkcję szyfrującą. Możliwości wyboru obu rodzajów funkcji określone są standardami dotyczącymi danego rodzaju podpisów cyfrowych. W przypadku podpisów XML według obowiązujących standardów do szyfrowania można wykorzystać algorytmy RSA oraz DSA, natomiast jako funkcję skrótu należy użyć SHA-1.

Algorytmy RSA (nazwa powstała od pierwszych liter nazwisk twórców: Rivest - Shamir - Adleman) oraz DSA (Digital Signature Algorytm) różnią się sposobem ustalania pary kluczy oraz operacjami potrzebnymi do wyliczenia podpisu cyfrowego. Z punktu widzenia programisty, wystarczy jedynie zadbać, aby para kluczy RSA była wykorzystywana do podpisów opartych na RSA i analogicznie dla DSA (kluczami RSA nie można podpisać wiadomości wykorzystując algorytm DSA i vice versa), nie musi on znać samego algorytmu szyfrowania.

Podpis nie jest składany pod całą wiadomością, lecz pod jej skrótem, z tego powodu konieczne jest zastosowanie funkcji skrótu. Funkcja skrótu dla dowolnej wiadomości wyznacza stałej długości skrót, który ma tę istotną z punktu widzenia kryptografii własność, że zmiana jednego ze znaków w dokumencie pociąga za sobą bardzo duże zmiany w skrócie wiadomości oraz, że nie można w sposób efektywny znaleźć dwóch różnych wiadomości, którym będzie odpowiadał ten sam skrót (jeśli udałoby się znaleźć takie wiadomości to podpis złożony pod jedną z nich byłby również ważny pod drugą wiadomością). W praktyce pojawiły się ataki na stosowany w podpisach XML algorytm SHA-1, choć na razie nie są wystarczająco efektywne, aby zagrozić bezpieczeństwu podpisów cyfrowych opartych na tym algorytmie.

Gdy już mamy odpowiednią parę kluczy, podpisywanie w najogólniejszej postaci przebiega następująco:
1. Policz skrót podpisywanej wiadomości (algorytm SHA-1).
2. Policz podpis pod skrótem wiadomości przy pomocy klucza prywatnego (algorytm RSA lub DSA).
3. Dołącz podpis do dokumentu (lub zapisz podpis w osobnym pliku).

3. Struktura podpisów XML
Aby zrozumieć w jaki sposób tworzyć i weryfikować podpisy XML trzeba najpierw poznać ich schemat. Najłatwiej przyjrzeć się przykładowemu podpisowi:

    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
            <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
            <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1" />
            <Reference URI="">
                <Transforms>
                    <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
                </Transforms>
                <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                <DigestValue>RjQyi5Gw10AFZ8FeeWcAHFkplAU=</DigestValue>
            </Reference>
        </SignedInfo>
        <SignatureValue>bTo3CR5HyAs5rsl31jIllqmok/CxWWgx/8jGdHWQDUv5SB16S70OdA==</SignatureValue>
    </Signature>

Element Signature w skazuje miejsce, gdzie znajduje się podpis i inne informacje potrzebne do jego sprawdzenia. Przestrzeń nazw tego elementu (xmlns) została zdefiniowana odnośnikiem do odpowiedniego schematu W3.org. Poniżej tego elementu w drzewie XMLowym znajdują się dwa kolejne elementy: SignedInfo oraz SignatureValue. Pierwszy z nich definiuje dodatkowe informacje, które są przekazywane razem z podpisem, natomiast SignatureValue to nic innego jak właściwa wartość podpisu.
W przypadku dołączenia klucza publicznego do podpisu poza wspomnianymi dwoma elementami pojawia się trzeci KeyInfo, zawierający wszystkie potrzebne informacje o kluczu publicznym. KeyInfo ma różną strukturę w zależności od wyboru metody podpisywania.
Może się tu również pojawić element Object zawierający treść podpisywanej wiadomości.

CanocalizationMethod oraz SignatureMethod odnoszą się do użytych metod kanonizacji jak i zastosowanego algorytmu do wykonania podpisu. Informacje te są istotne, aby podczas weryfikacji można było dokonać kanonizacji w ten sam sposób oraz zastosować odpowiedni algorytm podpisywania. Obie te wartości podawane są jako odnośniki do specyfikacji W3.org je definiujących. W tym przypadku wykorzystana została standardowa kanonizacja dokumentu a jako algorytm podpisywania wykorzystany został DSA/SHA1.
Kanonizacja dokumentu XML może zostać przeprowadzona na dwa sposoby: typowy (jak powyżej), lub z pominięciem komentarzy (wtedy jako wartość atrybutu Algorithm podaje się wartość http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments ). Jeśli zaś chodzi o metody podpisywania to poza zaprezentowaną w powyższym przykładzie metodą DSA/SHA1 istnieje możliwość wykorzystania metody RSA/SHA1 poprzez zmianę wartości argumentu Algorithm na http://www.w3.org/2000/09/xmldsig#rsa-sha1.

Reference to nic innego jak odnośnik do podpisywanego dokumentu z pewnymi dodatkowymi informacjami. W tym przypadku atrybut URI jest pusty, co oznacza, że podpisywany dokument znajduje się w tym samym pliku co podpis. Dodatkowo zdefiniowane zostały przekształcenia (Transforms), które muszą zostać dokonane przed weryfikacją podpisu (w tym przypadku jest jedno przekształcenie definiujące, że wykorzystany został podpis otaczający - enveloped signature), zastosowana funkcja skrótu (DigestMethod) oraz wartość funkcji skrótu dla danego dokumentu (DigestValue).

4. Generowanie kluczy kryptograficznych
4.1 Inicjalizacja generatora
Mając teraz wystarczające podstawy teoretyczne czas przejść do praktyki. Podpisywanie dokumentu rozpocząć należy od wygenerowania odpowiedniej pary kluczy. W tym momencie należy zdecydować czy do wygenerowania kluczy posłuży algorytm RSA czy DSA. Do generowania kluczy wykorzystuje się CryptoServiceProvidera (CSP) dla odpowiedniego algorytmu.

Uwaga: Większość wykorzystywanych klas znajduje się w przestrzeniach nazw System.Xml, System.Security.Cryptography oraz System.Security.Cryptography.Xml.

Dla algorytmu RSA odpowiednie instrukcje wyglądają następująco:
[Kod C#]

RSACryptoServiceProvider cryptoKey = new RSACryptoServiceProvider(); // nowy RSA-CSP

Natomiast dla DSA:
[Kod C#]

DSACryptoServiceProvider cryptoKey = new DSACryptoServiceProvider(); // nowy DSA-CSP

Instrukcje te wygenerują automatycznie parę kluczy. Teraz wygenerowaną parę kluczy warto by wyeksportować:
[Kod C#]

string stringKeyPair = cryptoKey.ToXmlString(true); // zwraca klucze do XMLowego stringa

Metoda ToXmlString() zwraca wygenerowany klucz w postaci dokumentu XMLa, co jest bardzo przydatną reprezentacją w przypadku podpisów XML. Metoda ta zawiera parametr typu bool, który określa czy ma zostać zwrócony tylko klucz publiczny (wartość false) czy oba klucze (wartość true). W tym przypadku potrzebujemy zapisać parę kluczy dlatego parametr ma wartość true.

4.2 Zapisanie kluczy kryptograficznych
Następnie parę kluczy można zapisać w pliku na dysku:
[Kod C#]

StreamWriter sw = new StreamWriter("sciezka_do_pliku_z_kluczami.xml"); // przygotowuje plik do zapisu
sw.Write(stringKeyPair); // zapisuje wyeksportowane klucze
sw.Close(); // zamyka plik


4.3 Wczytanie kluczy kryptograficznych
Gdy para kluczy zostanie już zapisana, warto znać możliwość jej wczytania do CSP, aby zamiast generować nowe pary kluczy, wykorzystać konkretną parę. Ponownie rozpocząć należy od inicjalizacji odpowiedniego CSP:

Dla RSA:
[Kod C#]

RSACryptoServiceProvider cryptoKey = new RSACryptoServiceProvider(); // nowy RSA-CSP

Dla DSA:
[Kod C#]

DSACryptoServiceProvider cryptoKey = new DSACryptoServiceProvider(); // nowy DSA-CSP

Następnie należy wczytać zawartość pliku z kluczem(ami) do stringa i wczytać je do odpowiedniego CSP;
[Kod C#]

StreamReader sr = new StreamReader( "sciezka_do_pliku_z_kluczami.xml" ); // przygotowuje plik do odczytu
string stringKeyPair = sr.ReadToEnd(); // wczytuje zawartość całego pliku do stringa
sr.Close(); // zamyka plik

cryptoKey.FromXmlString(stringKeyPair); // wczytuje klucze z XMLowego stringa

5. Podpisywanie
5.1 Wygenerowanie/wczytanie pary kluczy
Proces podpisywana rozpoczyna się od wygenerowania lub (częściej) wczytania pary kluczy, tak jak to opisano powyżej.

5.2 Przygotowanie dokumentu do podpisania

Teraz należy przygotować (wczytać) dokument XML, który zostanie podpisany:

[Kod C#]

XmlDocument xmlDoc = new XmlDocument(); // tworzy nowy obiekt XmlDocument
xmlDoc.Load("plik_do_podpisania.xml"); // wczytuje plik XML do obiektu XmlDocument

5.3 Przygotowanie obiektu do przechowania podpisanego dokumentu
Kolejnym krokiem jest przygotowanie obiektu, który będzie przechowywał podpisany dokument XML:
[Kod C#]

SignedXml signedXml = new SignedXml(xmlDoc); // przygotowuje obiekt SignedXml na podstawie obiektu XmlDocument
signedXml.SigningKey = cryptoKey; // ustawia klucz do podpisywania

5.4 Tworzenie odwołania do podpisywanego dokumentu
Tworzenie odwołania do dokumentu, który jest podpisywany:
[Kod C#]

Reference reference = new Reference(); // nowa referencja
reference.URI = ""; // podpis znajdować się będzie w tym samym pliku co podpisany dokument

// transformacja podpisu do podpisu otaczającego (enveloped signature)
XmlDsigEnvelopedSignatureTransform envelope = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(envelope); // dodanie transformacji do referencji

signedXml.AddReference(reference); // dodanie referencji do podpisanego dokumentu

5.5 Złożenie podpisu
Gdy wszystko już zostanie przygotowane można podpisać dokument:
[Kod C#]

signedXml.ComputeSignature(); // wylicza podpis

5.6 Dopisanie podpisu do podpisywanego dokumentu
Aby zakończyć cały proces należy jeszcze dopisać podpis do podpisywanego dokumentu:
[Kod C#]

XmlElement xmlDigitalSignature = signedXml.GetXml(); // zwraca element <Signature> jako obiekt XmlElement

// dopisanie elementu <Signature> w odpowiednim miejscu
xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));

xmlDoc.Save(); // zapisanie podpisanego cyfrowo dokumentu

6. Weryfikacja podpisu
6.1 Wczytanie klucza

Rozpoczyna się to od inicjalizacji i wczytania do odpowiedniego CryptoServiceProvidera klucza wykorzystywanego do weryfikacji.

Wersja dla RSA:
[Kod C#]

RSACryptoServiceProvider cryptoKey = new RSACryptoServiceProvider(); // nowy RSA-CSP
cryptoKey.FromXmlString(stringKey); // wczytuje klucz(e) z XMLowego stringa

Wersja dla DSA:
[Kod C#]

DSACryptoServiceProvider cryptoKey = new DSACryptoServiceProvider(); // nowy DSA-CSP
cryptoKey.FromXmlString(stringKey); // wczytuje klucz(e) z XMLowego stringa

W tym przypadku nie potrzeba posiadać pary kluczy, a jedynie sam klucz publiczny.

6.2 Przygotowanie dokumentu do weryfikacji
Następnie przygotować należy dokument do weryfikacji:
[Kod C#]

XmlDocument xmlDoc = new XmlDocument(); // tworzy nowy obiekt XmlDocument
xmlDoc.Load("plik.xml"); // wczytuje plik XML do obiektu XmlDocument

SignedXml signedXml = new SignedXml(xmlDoc); // przygotowuje obiekt SignedXml na podstawie obiektu XmlDocument

6.3 Odnalezienie podpisu w podpisanym dokumencie
Kluczową operacją jest odnalezienie i wczytanie poddrzewa XML, zawierającego podpis (a więc elementu <Signature>):
[Kod C#]

XmlNodeList nodeList  = xmlDoc.GetElementByTagName("Signature");

if (nodeList.Count <= 0)
{
   // NIE ZNALEZIONO ELEMENTU <Signature>.
}
else if (nodeList.Count >= 2)
{
   // WERYFIKACJA NIE POWIODŁA SIĘ GDYŻ JEST WIĘCEJ NIŻ 1 ELEMENT <Signature>
}
else
{
   signedXml.LoadXml((XmlElement)nodeList[0]); // wczytanie podpisu do obiektu SignedXml
}

6.4 Sprawdzenie poprawności podpisu
Ostatnią operacją, którą należy wykonać jest już właściwa walidacja podpisu:
[Kod C#]
bool validSignature = signedXml.CheckSignature(cryptoKey);

W przypadku gdy wartość validSignature zawiera true mamy prawidłowy podpis, w przypadku false podpis jest sfałszowany.

7. Co jeszcze można zmienić w podpisie
Opisana powyżej metoda dotyczy najprostszego możliwego podejścia do podpisu - bez dodawania KeyInfo oraz dołączając podpis do dokumentu. Warto również wiedzieć w jaki sposób zmodyfikować powyższy kod, aby w bardziej uniwersalny sposób móc posługiwać się podpisami.

7.1 Dodawanie KeyInfo
KeyInfo zawiera informacje o kluczu publicznym, który zamiast przekazywania osobnymi kanałami zostaje przekazany razem z podpisanym dokumentem. Aby dołączyć KeyInfo do podpisu, należy po utworzeniu obiektu klasy SignedXml wykonać następujące operacje:

[Kod C#]

KeyInfo keyInfo = new KeyInfo(); // tworzy nowy obiekt KeyInfo

// dla algorytmu DSA:
DSAKeyValue keyValue = new DSAKeyValue(cryptoKey); // wartość klucza publicznego w algorytmie DSA
// dla algorytmu RSA:
RSAKeyValue keyValue = new RSAKeyValue(cryptoKey); // wartość klucza publicznego w algorytmie RSA
// dla przypomnienia cryptoKey to obiekt odpowiedniego dla algorytmu CSP

keyInfo.AddClause(keyValue); // dodaje wartość klucza publicznego do KeyInfo
signedXml.KeyInfo = keyInfo; // dodaje KeyInfo do obiektu zawierającego podpisany dokument

7.2 Dodawanie treści dokumentu do podpisu
Istnieje możliwość dodania treści podpisywanego dokumentu do podpisu, zamiast doklejania podpisu do istniejącego dokumentu. W tym przypadku treść podpisywanego dokumentu znajdować się będzie wewnątrz elementu Object, który jest potomkiem elementu Signature, zawierającego podpis. Aby to osiągnąć należy zmodyfikować tworzenie odwołania do podpisywanego dokumentu (punkt 5.4) w następujący sposób:

[Kod C#]

DataObject dataObj = new DataObject(); // nowy obiekt danych
dataObj.Data = xmlDoc.ChildNodes; // wstawia dzieci elementu głównego (root) drzewa XML jako dane
dataObj.Id = xmlDoc.DocumentElement.Name; // wstawia nazwę elementu głównego (root) drzewa XML jako ID

Reference reference = new Reference(); // nowa referencja
reference.Uri = "#" + dataObj.Id; // ustawienie URI na odpowiednią wartość - ID danych obiektu danych

signedXml.AddReference(reference); // dodanie referencji do podpisanego dokumentu
Warto zauważyć, że w przypadku takiego podpisywania dokumentów nie ma potrzeby ustawiania transformacji podpisu.

8. Możliwości zastosowania
Opisane zostały możliwości tworzenia i weryfikacji podpisów XML, jednak wiele osób może zadawać sobie pytanie po co to komu. Zastosowanie podpisów XML może być bardzo szerokie, pomijając oczywistą funkcję jako uwierzytelnianie dokumentów zapisanych w XMLu.

Podpisy te można stosować również w przypadku gdy dana aplikacja umożliwia eksportowanie i importowania danych do i z XMLa, gdy ważne jest zapewnienie, aby żadna zewnętrzna aplikacja w żaden sposób danych nie modyfikowała. Wystarczy wtedy taki eksport podpisać cyfrowo a przy imporcie sprawdzać poprawność podpisu (klucz prywatny w tym momencie jest zaszyty w skompilowanej aplikacji, więc nie można go wydobyć).

Podobne artykuły

Komentarze 0 Masz uwagi do tej strony? Napisz

Dodaj komentarz

avatar

Zaloguj się lub Zarejestruj się aby wykonać tę czynność.

Autor User 93757
avatar
 

Załóż konto
CodeGuru to miejsce dla każdego programisty. Przez lata portal rozwijany był siłami społeczności i to właśnie społeczność programistów jest tutaj najważniejsza. CG od wielu lat gromadzi wokół siebie coraz większą grupę pasjonatów. Warto być jej częścią!

Dowiedz się więcej o CodeGuru

Geek Club - Windows Phone

 

MetroOne

Idź na górę strony