注:本文的某些部分基于 .NET Framework 2.0 的预发布版本。与这些部分有关的所有信息都有可能更改。
xml签名和xml加密标准目前被广泛地用作积木(building-block)技术。Microsoft Office InfoPath使用xml签名对部分或整个表单进行签名。Web服务使用xml签名对SOAP消息进行签名,并且使用xml加密技术对它们进行加密。基于ClickOnce的应用程序的xml清单(Visual Studio 2005中的新增功能)也使用xml签名。.NET Framework 1.x包含xml签名标准的对象模型,而.NET Framework 2.0则添加了其他支持,同时还添加了xml加密的对象模型。本文解释了xml签名和xml加密标准,并且说明了如何通过.NET使用它们。有关实际的xml签名规范,请参阅位于http://www.w3.org/TR/xmldsig-core的W3C标准。
在深入探讨xml签名标准之前,让我们回顾一下数字签名的基础知识。因为防止恶意用户在传输期间改变消息很重要,所以数字签名保护数据的完整性,并且可以检测数据在到达接收地的途中受到的任何更改。因为能够标识发送方也很重要,所以消息通常使用发送方的私有(秘密)密钥进行签名,并且用相应的公钥进行验证,从而使接收者在知道发送方的公钥时可以确认发送方的标识。这可以防止恶意用户通过尝试作为已知的发送方发送消息,或者通过截获来自已知发送方的消息并将其替换为他们自己的消息(一种中间人形式的攻击),冒充已知的发送方。
要创建数字签名,首先需要使用加密哈希函数来对需签名的消息进行哈希运算。对于任何长度的输入,加密哈希函数都会返回固定长度的位组,称为哈希值。该哈希值无法容易地重新转换为原来的输入。即使输入中只有一个位发生更改,哈希值也会以不可预知的方式更改,因此无法仅仅通过查找类似的哈希值来找到与原始输入类似的输入。一个常用的哈希函数是SHA-1,它可以产生160位的哈希值。下一个步骤是使用签名算法和您的私钥对该哈希值进行签名,以产生签名值。您用您的私钥创建该签名,以便具有您的公钥的其他人可以对其进行验证(本文稍后将对此进行详细讨论)。RSA是一种流行的用于签名的加密算法。在您将消息和该签名发送给接收者之后,验证过程开始。收到的消息被在签名时使用的相同哈希函数用来进行哈希运算;然后,通过将签名值以及公钥和计算得到的哈希一起传递给签名算法,对签名值进行验证。如果计算得到的哈希与签名哈希相匹配,则签名有效。如果这两个哈希不匹配,则表明数据或签名已经更改,因此不能确保数据的完整性。还可以使用密钥哈希算法签名和验证数据,但是这超出了本文讨论的范围。.NET Framework已经为所有种类的哈希、加密/解密和签名/验证算法包含了一组丰富的类。有关这些类的详细信息,请参阅.NET Framework SDK文档以及由Brian LaMacchia等人编写的《NET Framework Security 》(Addison-Wesley, 2002)。
您可以使用xml签名对任何种类的数据进行签名,这些数据包括xml文档的某个部分、其他xml文档或任何格式的其他数据。但是,实际上,xml签名最常用于对以xml表示的其他数据进行签名。xml签名标准还非常灵活,它允许您在签名之前对数据进行筛选和转换,并且使您可以精确地选择要签名的内容以及签名方式。
一个简单的文档:
Hello
World
经过签名的文档:
Hello
World
"http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
让我们考察一个简单的xml签名方案:对整个文档进行签名并且包含签名(参见图1)。请注意已经被添加到文档中的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元素:
下一个步骤是使用在签名的CanonicalizationMethod元素中指定的算法规范化SignedInfo元素及其子元素。然后,使用在签名的SignatureMethod元素中指定的算法对SignedInfo元素及其子元素进行签名。签名值被存储在SignatureValue元素中。
图3 验证过程
签名验证是刚刚描述的过程的逆过程(参见图3)。首先,必须使用CanonicalizationMethod元素中指定的C14N算法规范化SignedInfo元素及其子元素。然后,必须针对SignedInfo元素及其子元素验证SignatureValue元素中存储的签名值。
最后,对于签名中的每个Reference元素:
如果签名验证成功,并且每个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("http://dotnet.chinaitlab.com/UploadFiles_6597/200608/20060808155829128.jpg");
sig.AddReference(refr);
通过ID对xml数据进行的引用指向包含签名的文档内部或签名本身内部的xml。对于这些引用,签名引擎寻找其ID属性与引用中的URI匹配(不包括#)的元素。以下为一个基于ID的引用的示例:
如果ID为“myData”的元素位于包含签名的文档中,则该引用是完整的,并且签名引擎将在处理该签名时找到它。您通常使用该类型的引用将签名的作用范围限制到示例文档的特定部分。例如,在文档处理应用程序中,审阅者通常只对他审阅的xml文档部分(而不是整个文档)进行签名。该标准还允许您向Object元素中的签名添加任意数据。Object元素的xml看起来类似于以下代码行:
Your xml goes here
要创建基于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元素的引用来对有关签名的元数据进行签名,例如,签名者的唯一标识符或有关该签名的其他一些信息。