[摘要]XML簽名和XML加密標准目前被廣泛地用作構建快(building
注
XML簽名和XML加密標准目前被廣泛地用作積木(building
數字簽名
在深入探討XML簽名標准之前
要創建數字簽名
XML簽名基礎知識
您可以使用XML簽名對任何種類的數據進行簽名
一個簡單的文檔
Hello
World
經過簽名的文檔
Hello
World
Algorithm=
"#enveloped-signature"/>
圖1 簽名前後的簡單XML文檔
讓我們考察一個簡單的XML簽名方案:對整個文檔進行簽名並且包含簽名(參見圖1)。Tw.WinGwiT.Com請注意已經被添加到文檔中的Signature元素,該元素包含XML簽名。讓我們看一下每個元素所包含的內容:
SignedInfo——該元素的子元素包含有關所簽名的內容以及簽名方式的所有信息。簽名算法實際上應用於該元素及其所有子元素以生成簽名。
CanonicalizationMethod——該元素指定了用於SignedInfo元素以便將XML規范化的規范化(C14N)算法。我們將在下文中討論C14N。
SignatureMethod——該元素指定了該簽名的簽名算法。在該示例中,簽名算法是帶有RSA(用於對產生的哈希值進行簽名)的SHA-1(用於哈希運算)。
Reference——這些元素指定了將要簽名的數據以及在哈希運算之前應當如何對該數據進行處理。URI屬性(它表示統一資源標識符)標識要簽名的數據,而Transforms元素(稍後描述)指定在進行哈希運算之前如何處理數據。在該示例中,我們將使用特殊的URI——空字符串,它指定包含簽名的文檔是要包含在簽名中的數據。XML簽名標准對Reference數據使用間接簽名機制。該標准不是對Reference中的所有數據進行哈希運算然後加密哈希值,而是使用由Reference的DigestMethod元素所指定的算法對每個Reference的數據進行哈希運算,然後將哈希值存儲到Reference的DigestValue元素中。接下來,對SignedInfo元素和它的所有子元素(包括Reference元素)進行哈希運算;哈希值被加密以生成簽名。因此,您實際上是對Reference元素中所引用數據的哈希的哈希進行簽名,但是該方案仍然可以保護數據的完整性。圖2和圖3在匹配的XML旁邊顯示了簽名和驗證過程。
Transforms——每個Reference元素都可以具有零個或更多個為它指定的轉換。這些轉換按照它們在XML中列出的順序應用於該Reference的數據。轉換使您可以在對Reference的數據進行哈希運算之前對該數據進行篩選或修改。在該示例中,我們將使用包封式簽名轉換,該轉換選擇了包含文檔中除Signature元素以外的所有XML。我們必須從將被簽名的數據中移除Signature元素,否則,當我們存儲簽名值時,可能會修改我們嘗試簽名的數據。我們將在下文中詳細討論轉換。
SignatureValue——該元素包含通過簽名SignedInfo元素及其所有子元素而計算得到的簽名值。
圖2 簽名過程
現在,讓我們討論用於創建簽名的處理模型(參見圖2)。首先,對於簽名中的每個Reference元素:
按照轉換在Transforms元素下面出現的順序,將Transform元素中指定的每個轉換算法應用於Reference的數據。
使用Reference的DigestMethod元素所指定的哈希算法對經過轉換的數據進行哈希運算。
在Reference的DigestValue元素中存儲產生的哈希值。
下一個步驟是使用在簽名的CanonicalizationMethod元素中指定的算法規范化SignedInfo元素及其子元素。然後,使用在簽名的SignatureMethod元素中指定的算法對SignedInfo元素及其子元素進行簽名。簽名值被存儲在SignatureValue元素中。
圖3 驗證過程
簽名驗證是剛剛描述的過程的逆過程(參見圖3)。首先,必須使用CanonicalizationMethod元素中指定的C14N算法規范化SignedInfo元素及其子元素。然後,必須針對SignedInfo元素及其子元素驗證SignatureValue元素中存儲的簽名值。
最後,對於簽名中的每個Reference元素:
按照轉換在Transforms元素下面出現的順序,將Reference的Transform元素中指定的每個轉換算法應用於Reference的數據。
使用Reference的DigestMethod元素所指定的哈希算法對Reference的經過轉換的數據進行哈希運算。
將計算得到的哈希值與DigestValue元素中存儲的值進行比較。
如果簽名驗證成功,並且每個Reference的哈希值與簽名中存儲的哈希值相等,則XML簽名有效。否則,或者由Reference元素之一引用的數據已經更改,或者Signature元素已經更改。
嵌入到由其簽名的文檔中的簽名稱為信封簽名。用於創建這種簽名的代碼如圖4所示,用於驗證該簽名的代碼如圖5所示。
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
// Also, add a reference to System.Security.dll
// Assume the data to sign is in the data.xml file, load it, and
// set up the signature object.
XmlDocument doc = new XmlDocument();
doc.Load("data.xml");
SignedXml sig = new SignedXml(doc);
// Make a random RSA key, and set it on the signature for signing.
RSA key = new RSACryptoServiceProvider();
sig.SigningKey = key;
// Create a Reference to the containing document, add the enveloped
// transform, and then add the Reference to the signature
Reference refr = new Reference("");
refr.AddTransform(new XmlDsigEnvelopedSignatureTransform());
sig.AddReference(refr);
// Compute the signature, add it to the XML document, and save
sig.ComputeSignature();
doc.DocumentElement.AppendChild(sig.GetXml());
doc.Save("data-signed.xml");
圖4 創建一個信封簽名
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
// Also, add a reference to System.Security.dll
// Load the signed data
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.Load("data-signed.xml");
// Find the Signature element in the document
XmlNamespaceManager nsm = new XmlNamespaceManager(new NameTable());
nsm.AddNamespace("dsig", SignedXml.XmlDsigNamespaceUrl);
XmlElement sigElt = (XmlElement)doc.SelectSingleNode(
"//dsig:Signature", nsm);
// Load the signature for verification
SignedXml sig = new SignedXml(doc);
sig.LoadXml(sigElt);
// Verify the signature, assume the public key part of the
// signing key is in the key variable
if (sig.CheckSignature(key))
Console.WriteLine("Signature verified");
else
Console.WriteLine("Signature not valid");
圖5 驗證一個信封簽名
您已經了解了如何創建和驗證包封式簽名,它們很常用並且在對整個XML文檔進行簽名時很方便,而且XML簽名標准還使您可以通過在Reference元素中指定不同的URI對其他數據進行簽名。因此,讓我們接下來考察一下不同類型的引用。
引用
除了包封式引用(其URI屬性為空字符串的Reference元素)以外,在XML簽名標准中還定義了其他兩個寬泛類型的引用:對分離數據的引用以及通過ID對XML數據進行的引用。分離數據位於包含簽名的XML文檔的外部,這些引用可以指向另外一個XML文檔或任何其他類型的資源。當您在一個簽名中對多個資源(例如,一個XML文檔以及由該文檔引用的其他一些文件)進行簽名時,通常會使用該類型的引用。下面的XML代碼片段顯示了分離引用的一個示例:
為了使用Framework中的類來創建分離引用,只需在創建引用對象時將該引用的URI設置為資源的URI,然後 將該引用添加到簽名中,如下所示:
// Create a Reference to detached data, assume a SignedXml object in sig
Reference refr = new Reference("_6597/200608/20060808155829128.jpg");
sig.AddReference(refr);
通過ID對XML數據進行的引用指向包含簽名的文檔內部或簽名本身內部的XML。對於這些引用,簽名引擎尋找其ID屬性與引用中的URI匹配(不包括#)的元素。以下為一個基於ID的引用的示例:
如果ID為“myData”的元素位於包含簽名的文檔中,則該引用是完整的,並且簽名引擎將在處理該簽名時找到它。您通常使用該類型的引用將簽名的作用范圍限制到示例文檔的特定部分。例如,在文檔處理應用程序中,審閱者通常只對他審閱的XML文檔部分(而不是整個文檔)進行簽名。該標准還允許您向Object元素中的簽名添加任意數據。Object元素的XML看起來類似於以下代碼行:
要創建基於ID的引用(該引用指向包含簽名的文檔中已經存在的元素),請添加如下所示的代碼:
// Create a Reference to XML data in the containing document,
// assume a SignedXml object in sig
Reference refr = new Reference("#myData");
sig.AddReference(refr);
如果基於ID的引用指向了簽名中的一個Object元素,則除了添加該引用以外,還必須向簽名中添加一個DataObject,如剛才顯示的代碼中所示。數據對象可以包含您傳入的任何XML。
// Adds a DataObject with an Id of "#myData" to the signature, assume a
// SignedXml object in sig, and xml data of type XmlNodeList in data
DataObject dobj = new DataObject();
dobj.Id = "myData"; // Note: no #
dobj.Data = data; // XML Data of the Object
sig.AddObject(dobj);
您通常使用指向Object元素的引用來對有關簽名的元數據進行簽名,例如,簽名者的唯一標識符或有關該簽名的其他一些信息。
轉換
轉換通過允許您在生成引用的數據的哈希值之前修改該數據,使您可以對已簽名的內容進行更多的控制。例如,信封式簽名轉換在對XML文檔進行簽名之前會移除Signature節點。引用可以指定任何數量的轉換,這些轉換按照在Transforms元素中指定的順序而做用。.NET Framework中的類除了支持我們前面提到的信封式簽名轉換以外,還支持下列轉換:
任何規范化算法都可以用作轉換。
通過Base64轉換可以對Base64編碼數據進行解碼。
通過XSLT轉換,可以在對XML數據進行簽名之前,向其應用XSLT樣式表。要應用的XSLT樣式表被指定為Transform元素下的XML。
通過XPath轉換可以用XPath表達式篩選XML數據。
XPath表達式被指定為Transform元素下的XPath元素的文本內容。需要注意的是,XPath轉換充當篩選器,而不是充當在作為輸入傳遞的XML中選擇節點的手段。該轉換針對作為輸入傳遞給該轉換的每個節點計算XPath表達式,結果被轉換為布爾值。輸入節點將被考慮以便傳遞計算,並且如果計算的結果為true,則輸入節點將被包含在轉換的輸出中。考慮轉換的以下XML輸入:
Some data
Even more data
假設我們只希望選擇“b”節點進行簽名。帶有XPath表達式“ancestor-or-self::b”的XPath轉換將返回以下節點集(它正是我們所需要的):
Some data
Even more data
該XPath表達式的Transform元素看起來類似於下面的代碼片段:
Algorithm="-xpath-19991116"
>
要在簽名時以編程方式創建轉換,請創建某個轉換對象的實例,適當設置它的屬性,並且將其添加到它所應用於的引用。以下示例將上一個示例中使用的XPath轉換添加到某個引用中,以便創建轉換:
// Add an XPath transform to a reference.
// Assume a Reference object in refr
XmlDocument doc = new XmlDocument();
doc.LoadXml("ancestor-or-self::b");
XmlDsigXPathTransform xptrans = new XmlDsigXPathTransform();
xptrans.LoadInnerXml(doc.ChildNodes);
refr.AddTransform(xptrans);
規范化
規范化的目的是為兩個邏輯上相同但可能不是由相同的文本表示的XML片段產生相同的XML數據。例如,請觀察下面兩個代碼片段。它們在邏輯上是相同的;它們的不同之處僅僅在於文本表示。但是如果您要對它們按原樣進行哈希運算,則得到的哈希值將是不同的:
Some text
Some text
要避免該問題,該標准中指定的默認規范化算法將執行很多任務,包括消除開始和結束標記中的空白以及將空元素轉換為開始/結束標記對。但是,它不會更改元素內容中的任何空白。所執行操作的完整列表可以在Canonical XML(位於-c14n)中得到。
簽名引擎在必要時自動規范化數據,以便符合W3C標准。特別地,每當簽名引擎需要將XML數據轉換為二進制數據以便進行哈希運算時,都會規范化該數據。例如,當它准備對SignedInfo元素及其子元素進行簽名時,就會發生這種情況。當它准備引用或轉換的輸出以便進行簽名時,也可能發生這種情況。例如,如果您使用基於ID的引用(指向包含簽名的文檔中的其他XML數據),並且該引用沒有與其相關聯的轉換,則簽名引擎在對該引用的XML數據進行哈希運算之前將規范化該數據。
密鑰管理
XML簽名標准提供了KeyInfo元素,幫助進行密鑰管理。該元素可以存儲密鑰名稱、密鑰值、密鑰檢索方法或證書信息,以幫助接收方驗證簽名。該標准沒有指定應當如何信任以及是否應當信任KeyInfo元素中的任何信息。如果發送方和接收方共享一個受信任密鑰列表,或者如果您發現了其他某種用於將密鑰名稱映射到密鑰的方法,則KeyInfo元素可能很有用。.NET Framework 1.x具有一些對密鑰名稱、值和檢索方法的支持,.NET Framework 2.0還包含對X.509證書的支持。讓我們假設發送方和接收方共享一個密鑰列表,接收方對於他期望從其接收消息的每個發送方都具有一個公鑰。簽名應用程序可以添加以下代碼,以便向簽名中添加KeyInfo元素:
// Adds an KeyInfo element with RSA public key information to the
// signature.
// Assumes a SignedXml object in sig, and an RSA object in key.
KeyInfo ki = new KeyInfo();
ki.AddClause(new RSAKeyValue(key));
sig.KeyInfo = ki;
該代碼應當在調用ComputeSignature之前添加。它將在簽名中產生一個KeyInfo元素,該元素看起來類似於以下代碼:
這表示用來對XML文檔進行簽名的RSA公鑰。接收方應用程序應當將該密鑰與受信任密鑰列表進行比較,如果該公鑰不在列表中,則不應當信任文檔。否則,攻擊者就可以在傳輸過程中替換已經簽名的文檔,並且用另外一個密鑰對其進行簽名。如果簽名包含與此類似的RSAKeyValue,則驗證代碼可以調用SignedXml類的CheckSignature方法(它不采用任何參數),並且.NET Framework將根據RSAKeyValue元素計算出該密鑰。下面是一個示例:
// Verify a signature that includes RSAKeyInfo or DSAKeyInfo.
// Assume a SignedXml object in sig.
bool verified = sig.CheckSignature();
簽名配置文件
伴隨XML簽名的靈活性而來的是一定數量的風險。因為轉換是如此靈活,所以可能很難精確計算出簽名涵蓋了哪些數據,這可能導致意外的或不安全的結果。這些簽名配置文件可以通過指定應用程序所支持的簽名形式,在該方面提供幫助。盡管沒有相應於簽名配置文件的標准,但簽名配置文件起碼應當指定應用程序期望簽名具有的引用和轉換,以便您可以確保所期望簽名的數據確實進行了簽名。簽名配置文件還可以包含其他數據,例如,期望簽名數據具有的簽名算法或密鑰大小。應用程序應當檢查並強制它所創建和驗證的那些簽名符合該應用程序所支持的簽名配置文件。要更好地理解配置文件為什麼如此重要,請考慮圖1。假設您要編寫接受XML簽名數據的應用程序,但是您的應用程序只期望使用信封式簽名轉換而非任何其他轉換的簽名。現在,有人向您發送了帶有額外XPath轉換的簽名文檔,如圖6所示。
Hello
World
Algorithm="#enveloped-
signature"/>
Algorithm="-xpath-19991116"
>
圖6 使用額外的轉換對文檔進行簽名
在圖6的示例中,簽名只涵蓋示例文檔中的“a”元素。如果您剛剛加載了該文檔並且調用了SignedXml類的CheckSignature方法,則即使“b”元素未被該簽名涵蓋,該簽名仍然可能驗證,這是因為簽名引擎將應用在該簽名中指定的轉換。如果應用程序依賴於該簽名涵蓋了“b”元素這一前提,則數據的完整性已經遭到損害。應用程序應當檢驗只有一個引用具有作為URI的空字符串並且該引用具有一個轉換——信封式簽名,從而驗證它所期望的簽名配置文件。它會在驗證簽名時拒絕任何其他簽名配置文件。一些用於檢驗該簽名配置文件的示例代碼顯示在圖7中。
// This method checks the signature profile for the signature
// in the supplied document. It ensures there is only one
// Signature element and only one enveloped reference with only
// one enveloped signature transform
public bool CheckSignatureProfile(XmlDocument doc)
{
// Make sure there is only one Signature element
XmlNamespaceManager nsm = new XmlNamespaceManager(new NameTable());
nsm.AddNamespace("dsig", SignedXml.XmlDsigNamespaceUrl);
XmlNodeList sigList = doc.SelectNodes("//dsig:Signature", nsm);
if (sigList.Count > 1)
return false; //Wrong number of Signature elements
//Make sure the Signature element has only one Reference
XmlElement sigElt = (XmlElement)sigList[0];
XmlNodeList refList = sigElt.SelectNodes(
"dsig:SignedInfo/dsig:Reference", nsm);
if (refList.Count > 1)
return false; //Wrong number of Reference elements
// Make sure the Reference URI is ""
XmlElement refElt = (XmlElement)refList[0];
XmlAttributeCollection refAttrs = refElt.Attributes;
XmlNode uriAttr = refAttrs.GetNamedItem("URI");
if ((uriAttr == null) || (uriAttr.Value != ""))
return false; // Wrong type of reference
// Make sure the only tranform is the enveloped signature transform
XmlNodeList transList = refElt.SelectNodes(
"dsig:Transforms/dsig:Transform", nsm);
if (transList.Count != 1)
return false; //Wrong number of Transform elements
XmlElement transElt = (XmlElement)transList[0];
string transAlg = transElt.GetAttribute("Algorithm");
if (transAlg != SignedXml.XmlDsigEnvelopedSignatureTransformUrl)
return false; //Wrong type of transform
return true;
}
圖7 檢驗一個簽名配置文件
迄今為止,我們已經考察了XML簽名標准的一些不同方面以及.NET Framework中對它的支持。讓我們將這些功能中的某些功能一起放到一個更為完整的示例中。假設您要編寫一個應用程序以便交換XML形式的消息,並且您希望對該消息的全部內容進行簽名。您還希望將一些有關簽名者的XML數據作為對象添加到Signature元素中,以便只對該數據的signerID元素進行簽名。您的應用程序可以訪問一個眾所周知的密鑰列表,因此您還將在簽名中存儲公鑰信息,並且檢驗以確保該密鑰在驗證期間映射到一個眾所周知的密鑰。用於簽名和驗證消息的代碼包含在本文的完整代碼下載中。用該代碼對消息進行簽名將產生如圖8所示的XML簽名。
Algorithm=
"#enveloped-signature"/>
Algorithm=
"-xpath-19991116"
>
ancestor-or-self::my:SignerID
圖8 一個稍微復雜一些的簽名文檔
.NET Framework 2.0中的新特性
迄今為止討論的所有XML簽名特性在.NET Framework 1.x和2.0中都可用。XML加密和X.509證書集成對於版本2.0而言是新增特性。通過X.509證書集成,可以更容易地將X.509證書用於XML簽名。通過新增的X509CertificateEx類和相關的類,可以更容易地操縱和使用證書,並且XML簽名對象模型在適當的時候使用這些類。在本文的結尾,我們將對X.509集成進行更詳細的討論。XML加密是另外一個W3C標准。正如XML簽名指定了有關創建XML形式的數字簽名的格式和處理模型一樣,XML加密對如何加密XML形式的數據進行了標准化。XML數字簽名是通過SignedXml類驅動的,而XML加密是使用新的EncryptedXml類執行的。盡管XML加密可以用來加密任意數據,但它最常用於加密其他XML。當以這種方式使用時,您將在文檔的加密方式上擁有很多的靈活性。例如,可以用不同的密鑰加密XML文檔的不同節點,同時將某些節點保留為明文。而且,由於用EncryptedXml類加密某些內容會產生XML,因此您甚至可以加密已經加密的結果,該過程稱為“超級加密”。讓我們考察一下一些已經加密的XML(參見圖9)。一件可以立刻注意到的有趣的事情是,XML加密標准對某些元素(包括KeyInfo元素)使用XML簽名命名空間。
一個簡單的文檔:
Hello
World
經過加密的文檔:
>
圖9 加密前後的XML文檔
EncryptedData元素
EncryptedData是通過加密XML生成的根元素,並且它包含有關數據的所有信息。EncryptedData包含三個主要的子元素:EncryptionMethod指定用來加密數據的算法;KeyInfo元素提供有關使用哪個密鑰來解密數據的信息;CipherData或CipherReference元素則包含實際的加密信息。EncryptionMethod元素指定用來加密和解密密碼數據的算法,這些算法由URI指定,就像XML簽名標准一樣。EncryptionMethod同時用於加密數據和加密密鑰,但並非每個算法都可以同時用於這兩個場合。圖10顯示了每個算法可以用在哪個場合。請注意,高級加密標准(Advanced Encryption Standard,AES)還可用於192位和128位密碼;您只需在URI中更改密鑰大小。
EncryptedXml類的URI屬性 加密數據 加密密鑰 AES XmlEncAES256Url √ XmlEncAES256KeyWrapUrl √ DES XmlEncDESUrl √ TripleDES XmlEncTripleDESUrl √ XmlEncTripleDESKeyWrapUrl √ RSA XmlEncRSA1_5Url √圖10 使用加密算法的場合
在加密或解密任何數據之前,加密引擎需要知道應當使用哪個密鑰來加密和解密。可以用兩種方式標識密鑰,最容易的方式是為該密鑰分配一個名稱,並且在KeyInfo元素內部放置一個KeyName元素。解密文檔的應用程序可以獲得KeyName標記,並提供與給定的名稱相匹配的密鑰。該應用程序不僅提供密鑰的名稱,還可以將密鑰作為EncryptedKey直接嵌入到KeyInfo元素中。EncryptedKey與EncryptedData包含相同的元素:加密方法、用來解密該密鑰的密鑰以及構成加密密鑰的密碼數據。加密密鑰通常與命名密鑰結合使用,以作為隨機會話密鑰。首先,生成一個隨機會話密鑰並使用它來加密XML;然後,用需要解密文檔的眾所周知的命名密鑰加密會話密鑰本身;最後,將該命名密鑰插入到加密會話密鑰的KeyInfo元素中,並且將該加密會話密鑰附加到加密數據中。圖9中的示例說明了這一點。XML數據的加密方法是256位的AES,而AES算法的密鑰也已經被加密。EncryptedKey元素包含有關如何加密AES算法的密鑰的信息,在該示例中,它是使用名為recipients_public_key的RSA密鑰加密的。使用XML加密的應用程序必須將該名稱映射到實際的密鑰。
可以將實際的加密數據嵌入到EncryptedData元素中,或者將其放到單獨的位置,然後從EncryptedData中引用它。如果要將密碼數據直接放到EncryptedData中,則會將其作為Base64編碼的二進制文件放到CipherData元素中。圖9中的示例使用了一個CipherData元素。另一個方案是將加密數據放到EncryptedData元素外部。可以將密碼文本放在從該文檔中的另一個元素到遠程Web站點的任何位置。在這兩種情況下,都將使用CipherReference元素而不是CipherData元素(參見圖11)。
CipherReference的位置 URI格式 密碼文本格式 相同文檔中 #order Base64字符串 遠程Web站點 二進制圖11 CipherReferences
對遠程Web站點的CipherReferences提出了一個有趣的安全方案。在解密XML的過程中,解密引擎必須轉到任意Web站點並下載密碼文本。由於要解密的文檔可能不會受到與完成解密的代碼同等程度的信任,因此應當在沙箱中完成該操作。這是通過向EncryptedXml對象提供它將在解析任何CipherReferences時使用的證據來完成的。通過沙箱可以更安全地執行代碼,因為解密應用程序可能不具有與提供加密數據的站點相同的權限。例如,如果應用程序試圖解密不受信任的站點,並且該不受信任的站點不能夠訪問位於安全的、受信任站點上的某些受信任的數據,則它可以通過包含密碼引用,讓解密應用程序為它訪問該文件。由於.NET安全策略是以證據為中心的,因此所提供的證據通常應當至少包含Site、Zone和Url對象,如下所示:
// Create evidence based on the referring document
Evidence evidence = new Evidence();
evidence.AddHost(new Zone(SecurityZone.Internet));
evidence.AddHost(new Site("untrustedsite"));
evidence.AddHost(new Url("untrustedsite/encrypted.xml"));
EncryptedXml exml = new EncryptedXml(untrustedDoc, evidence);
XML加密示例
現在,讓我們考察一下如何使用.NET Framework 2.0中的類。該示例說明了一個銷售CD的Web站點,每次購買活動都被歸檔到一個XML文檔中,其中包含有關訂購了哪些商品、發貨信息和信用卡號的詳細信息。圖12顯示了訂單的一些示例XML。
圖12 一個CD訂單的XML
在該實例中,公司內部的任何人查看訂單中的項目是一件可以接受的事情,但是您可能希望對某些更為敏感的數據(例如,發貨地址和信用卡信息)進行保密。實際上,您甚至可能希望對它們分別進行加密,以便只有計帳部門能夠訪問信用卡信息,並且只有發貨部門能夠訪問發貨地址。要做到這一點,需要對付款元素下的XML部分進行加密,以便只有計帳部門能夠訪問它,只有發貨部門可以獲得的單獨密鑰將用來加密發貨元素。最後,整個訂單將用公司中任何人都可以獲得的密鑰加密。
第一步是創建一個EncryptedXml對象。這是通過傳入具有要加密或解密的數據的文檔完成的,如下所示:
// Assumes the order is in the order.xml file.
XmlDocument doc = new XmlDocument();
doc.Load("order.xml");
EncryptedXml exml = new EncryptedXml(doc);
在加密XML之前,必須將要使用的密鑰映射到它們的相應名稱,這些名稱將出現在KeyName元素中。可以使用EncryptedXml的AddKeyMapping方法完成該任務:
// Set up the key mapping. Assumes a method called GetBillingKey
// that returns the RSA key for the billing department.
RSA billingKey = GetBillingKey();
exml.AddKeyNameMapping("billing", billingKey);
在設置了密鑰-名稱映射以後,加密XML就很容易了。第一步是調用Encrypt方法,它完成實際的加密,並且返回一個EncryptedData對象以表示文檔的加密部分。之後,您需要調用一個工具方法,將原始XML文檔的未加密部分換為新的加密數據:
// Find the element to encrypt.
XmlElement paymentElement =
doc.SelectSingleNode("//order/payment") as XmlElement;
// Encrypt the payment element, passing in the key name.
EncryptedData encryptedPayment =
exml.Encrypt(paymentElement, "billing");
// Swap the encrypted element for the unencrypted element.
EncryptedXml.ReplaceElement(paymentElement, encryptedPayment, true);
這將產生圖13中所示的加密XML。
>
Sce6lLD+u2f8HzPFyuGxTF32z4mb2ugql3JuJIPAqIP98iYs+Muhqg==
圖13 加密了支付信息的CD訂單
請觀察一下加密數據,您可以看到我們已經描述的各個部分。首先,EncryptionMethod元素顯示了AES-256的URI,這意味著該文檔用帶有256位密鑰的AES算法(由RijndaelManaged類實現)加密。Encrypt方法為您生成一個隨機的會話密鑰;該密鑰在KeyInfo元素中加密。通過觀察EncryptedKey元素,您可以看到它是用RSA算法並借助於一個名為“billing”的密鑰加密的。CipherData元素存放了該密鑰的加密值。在KeyInfo的後面是包含加密內容(原來的付款元素)的CipherData。解密剛才說明的文檔很容易,這要歸功於EncryptedXml的DecryptDocument方法。首先,加載含有加密內容的文檔:
// Assumes the encrypted order is in encrypted.xml
XmlDocument doc = new XmlDocument("encrypted.xml");
EncryptedXml exml = new EncryptedXml(doc, documentEvidence);
接下來,設置密鑰-名稱映射,如下所示:
// Set up the key mapping. Assumes a method called GetBillingKey
// that returns the RSA key for the billing department.
RSA billingKey = GetBillingKey();
exml.AddKeyNameMapping("billing", billingKey);
最後,調用DecryptDocument。該方法將負責解密密碼文本,並且將加密的XML替換為它的解密內容。
// Decrypt the encrypted XML in the document
exml.DecryptDocument();
在幕後,加密引擎將在調用DecryptDocument時尋找任何EncryptedData元素,在這種情況下,它只找到付款元素下面的那個元素。在找到該元素以後,它將查看KeyInfo子句並且看到它持有加密的256位AES密鑰。加密密鑰的KeyInfo將顯示它是用名為billing的RSA密鑰加密的。然後,它檢查密鑰映射表以尋找名為billing的密鑰。我們為一個名為billing的密鑰添加了一個密鑰映射,因此引擎隨後將解密加密密鑰。既然引擎具有密鑰,那麼它將解密CipherData。然後,用解密的CipherData的結果來替換EncryptedData元素。當DecryptDocument被調用時,引擎將對它在該文檔中找到的每個EncryptedData元素執行這一過程。
X.509支持
安全方面的難題之一是信任。在網絡中安全地分發和存儲受信任的公鑰並不容易,但是,Windows提供了大量基礎結構來解決該問題。加密API(Cryptographic API,CAPI)提供了對分發、操縱和存儲X.509證書的支持。CAPI提供的支持也稱為公鑰基礎結構(Public Key Infrastructure,PKI)。
X.509證書提供有關密鑰的其他信息。證書指定了密鑰的頒發者、密鑰的接收者、證書何時有效以及實際的密鑰信息。證書是由證書頒發機構(Certificate Authorities,CA)頒發的,CA是您給予信任以擔保證書持有者身份的實體(例如,某個外部公司或您所在公司的IT部門)。如果您具有一個有效證書,它包含由您信任的CA頒發的屬於您的計帳部門的公鑰,則您可以確保該公鑰確實是計帳部門的。
通過Windows PKI支持,您可以安全地在計算機上的證書存儲器中存儲證書,在網絡計算機上的證書存儲器中添加和移除證書,在網絡計算機上添加和移除受信任的CA,獲得和驗證有關單個證書的信息等等。例如,這使您可以將證書(包含您所在公司計帳部門的公鑰)分發到公司網絡中的所有計算機。在.NET Framework 1.x中,必須調用非托管API才能利用該支持的大部分功能。在.NET Framework 2.0中,可以在托管代碼中通過X509CertificateEx類和相關的類使用這些API中的大多數API。XML簽名類直接支持X509CertificateEx類。要用證書創建XML簽名,只需從該證書的PrivateKey屬性中獲得私鑰,然後將其用作SignedXml對象的簽名密鑰。
// Use the private key from the certificate. Assumes a SignedXml
// object in sig and an X509CertificateEx object in cert.
sig.SigningKey = cert.PrivateKey;
當然,該證書必須具有相關聯的私鑰,否則PrivateKey屬性返回null。您還可以通過使用KeyInfoX509Data類將X.509證書信息添加到已簽名的KeyInfo元素中,如以下代碼所示:
// Add X.509 certificate info to the KeyInfo element. Assumes a
// SignedXml object in sig and an X509CertificateEx in cert.
KeyInfoX509Data keyInfoX509 =
new KeyInfoX509Data(cert, X509IncludeOption.EndCertOnly);
sig.KeyInfo.AddClause(keyInfoX509);
要驗證用證書的私鑰簽名的XML簽名,可以調用SignedXml類的CheckSignature方法的新重載,該新重載采用一個X509CertificateEx對象和一個布爾值。如果該布爾值被設置為true,則該方法將針對證書中的公鑰驗證簽名,並且通過檢查密鑰使用率以及生成到受信任的根頒發者的鏈條來驗證該證書。
// Check the signature against the cert and verify the cert. Assumes a
// SignedXml object in sig and an X509CertificateEx object in cert.
bool verified = sig.CheckSignature(cert, true);
還可以使用不帶任何參數的CheckSignature方法來驗證簽名,但是它不會同時驗證X.509證書(如果使用了一個這樣的證書對XML進行簽名)。作為一個單獨的步驟,您必須根據KeyInfo重新創建X509CertificateEx對象並對其進行驗證。有關出於測試目地創建X.509證書的更多信息,請參閱.NET Framework SDK文檔中的證書創建工具(Makecert.exe)主題。
.NET Framework 2.0向簽名引擎中添加了幾個新的轉換和規范化算法。這些算法包括Exclusive C14N規范化算法、XML解密轉換和LTA轉換。圖14包含.NET Framework 2.0簽名引擎中提供的轉換和規范化算法的完整表格。
Class Description XmlDecryptionTransform Decrypts encrypted XML XmlDsigBase64Transform Decodes base64 encoded data XmlDsigC14NTransform Performs C14N canonicalization (see -c14n for more information) XmlDsigEnvelopedSignatureTransform Removes an enveloped signature from a document XmlDsigExcC14NTransform Performs exclusive C14N canonicalization (see -xml-exc-c14n-20020718 for more information) XmlDsigXPathTransform Applies an XPath filter to the input XML XmlDsigXsltTransform Applies an XSLT transform to the input XML XmlLicenseTransform Implements the LTA transform XmlDsigC14NWithCommentsTransform Performs C14N canonicalization, but leaves comments in the canonicalized XML XmlDsigExcC14NWithCommentsTransform Performs exclusive C14N canonicalization, but leaves comments in the canonicalized XML圖14 轉換和規范化算法庫
小結
我們在這裡討論了XML簽名標准的基礎知識以及它在.NET Framework 1.x中是如何實現的。我們還討論了.NET Framework 2.0中的一些新功能,包括對XML加密標准的支持以及對XML簽名的X.509證書集成。通過這些積木技術,可以使用標准與其他應用程序互操作,並且將標准支持內置到您自己的應用程序中。
作者簡介
Mike Downen 是 Microsoft 的 CLR 安全小組的項目經理,並從事代碼訪問安全性、加密術和新的 ClickOnce 安全模型方面的工作。您可以通過 m 與 Mike 聯系。
Shawn Farkas 是 Microsoft 的 CLR 安全小組的見習軟件設計工程師。他從事 ClickOnce、加密技術和 IL 驗證方面的工作。請在 查看他的網絡日記。
From:http://tw.wingwit.com/Article/program/net/201311/12537.html