利用X.509证书对XML进行加密和签名
- 综述
XML加密和签名技术应用非常广泛。
ASP.NET 使用XML加密对配置信息进行加密;InfoPath使用XML签名对表单进行签名;Web服务使用XML加密和签名对SOAP消息进行加密和签名;等等。
W3C提供了相应的标准:http://www.w3.org/TR/xmldsig-core。
而X.509是一种非常通用的证书格式,符合ITU-T X.509国际标准。此标准已用于许多网络安全应用程序:IP 安全、SSL、电子商务协议(SET)等等。
利用X.509证书提供的公钥/私钥对可以很方便的对XML进行加密和签名。Geneva框架生成的GenericXmlSecurityToken类型安全令牌就是使用X.509证书对XML进行加密和签名的。
下面是原始的XML:
1 <?xml version="1.0" encoding="utf-8" standalone="yes"?>
2 <!--This is a comment-->
3 <content>
4 <public>
5 <name>wuhong</name>
6 <job>engineer</job>
7 </public>
8 <private>
9 <creditcard>20101220</creditcard>
10 <phonenumber>132150</phonenumber>
11 </private>
12 </content>
13
- XML加密
对XML加密是利用.net的System.Security.Cryptography.Xml.EncryptedXml类。
我们选择对<private>节点进行加密:
1 public static void Encrypt(XmlDocument Doc, string targetElementName, string issuer)
2 {
3 //XML有效性验证
4 XmlElement targetElement = Doc.GetElementsByTagName(targetElementName)[0] as XmlElement;
5 if (targetElement == null)
6 {
7 throw new CryptographicException("待加密节点不存在");
8 }
9
10 //获取加密证书
11 X509Certificate2 x = CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, issuer);
12
13 EncryptedXml eXml = new EncryptedXml();
14 //加密
15 EncryptedData encryptElement = eXml.Encrypt(targetElement, x);
16 //替换加密节点
17 EncryptedXml.ReplaceElement(targetElement, encryptElement, false);
18 }
19
下面是关键节点加密后的XML:
<EncryptedData>是通过加密XML生成的根元素,并且它包含有关数据的所有信息。
三个子节点<EncryptionMethod>指定用来加密数据的算法;
<KeyInfo>提供有关使用什么密钥来解密数据的信息;
<CipherData>则包含实际的加密信息。
1 <?xml version="1.0" encoding="utf-8" standalone="yes"?>
2 <!--This is a comment-->
3 <content>
4 <public>
5 <name>wuhong</name>
6 <job>engineer</job>
7 </public>
8 <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns="http://www.w3.org/2001/04/xmlenc#">
9 <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" />
10 <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
11 <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">
12 <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
13 <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
14 <X509Data>
15 <X509Certificate>MIICBzCCAXCgAwIBAgIQ……</X509Certificate>
16 </X509Data>
17 </KeyInfo>
18 <CipherData>
19 <CipherValue>RmzVSAVBW6cl/cmJiEhfjwHLI413aCEI7dkdMIH4poNuc9Ql8IPuDxLdrSo4oXtntlipmRXuj5UK57rTCxu4pln1qFs+Va+2grw+pHklVej1cnZHbM9tdm8IvGcdSQ5NBY5Rg8q3fvNHfqcCrNnTF2oxjNmJQYnaJjU1sRrVkY8=</CipherValue>
20 </CipherData>
21 </EncryptedKey>
22 </KeyInfo>
23 <CipherData>
24 <CipherValue>mw+YdUH/WQ2JwBgjVujQO6HYITOEqssY7eW+M+seHGG+7iyCbw6e3Hm3MZix/UqzIYqmb6hjwOaHfgXe3Xd8YsP4cmKY/GIXWN51AGnCbHUUHMeztCwEE6iA4rqQPDQrotnyAtssFm0wgHrg4NKW9Q==</CipherValue>
25 </CipherData>
26 </EncryptedData>
27 </content>
28
可以看到,加密是非常简便的。如果利用其他密钥进行加密,则需要根据EncryptedXml类手工处理KeyInfo、EncryptedData等细节内容。
解密也非常简便:
1 public static void Decrypt(XmlDocument doc, string issuer)
2 {
3 //获取加密证书
4 X509Certificate2 x = CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, issuer);
5
6 EncryptedXml exml = new EncryptedXml(doc);
7
8 //设置密钥-名称映射,可以随意设置一个名称,我们使用证书私钥解密
9 exml.AddKeyNameMapping("rsaKey", x.PrivateKey);
10
11 //解密
12 //此时会寻找所有<EncryptedData>,查看<KeyInfo>节点,解密<CipherData>节点。用解密<CipherData>的结果来替换<EncryptedData>元素。
13 exml.DecryptDocument();
14 }
15
- XML签名
对XML签名是利用.net的System.Security.Cryptography.Xml.SignedXml类。
利用X.509证书对既定的XML进行签名和验证签名也比较简便。不过签名需要额外两个个步骤:
一是提供一个引用<Reference>来说明签名是包封式的;
二是提供签名证书的信息<KeyInfo>给验证签名使用。
1 public static void Sign(XmlDocument Doc, string issuer)
2 {
3 SignedXml signedXml = new SignedXml(Doc);
4
5 //获取签名证书
6 X509Certificate2 x = CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, issuer);
7 signedXml.SigningKey = x.PrivateKey;
8
9 //引用
10 //指定了在哈希运算之前应当如何对将要签名的数据进行处理。
11 //URI属性标识要签名的数据,而Transforms元素指定如何处理数据。
12 Reference reference = new Reference();
13 reference.Uri = ""; //空字符串,它指定对整个文档进行签名并且包含签名,需要特别注意的是文档中如果已经存在<Signature>节点,在签名前将先被移除。
14 XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform(); //使用包封式签名转换
15 reference.AddTransform(env);
16 signedXml.AddReference(reference);
17
18 //向签名的接收方提供签名证书的信息,在验证签名的同时可以验证签名证书
19 KeyInfoX509Data keyInfoX509 = new KeyInfoX509Data(x, X509IncludeOption.EndCertOnly);
20 signedXml.KeyInfo.AddClause(keyInfoX509);
21
22 //签名
23 signedXml.ComputeSignature();
24
25 //将签名加入XML中
26 XmlElement xmlDigitalSignature = signedXml.GetXml();
27 Doc.DocumentElement.AppendChild(Doc.ImportNode(xmlDigitalSignature, true));
28 }
29
下面是对整个文档进行签名后的XML:
<SignedInfo>元素的子元素包含有关所签名的内容以及签名方式的所有信息。签名算法实际上应用于该元素及其所有子元素以生成签名。
三个子节点<CanonicalizationMethod>指定了用于SignedInfo元素以便将XML规范化的规范化(C14N)算法。
<SignatureMethod>指定了该签名的签名算法。
<Reference>指定了将要签名的数据以及在哈希运算之前应当如何对该数据进行处理。<URI>属性标识要签名的数据,而<Transforms>元素指定在进行哈希运算之前如何处理数据。每个<Reference>元素都可以具有零个或更多个为它指定的转换。按照转换在<Transforms>元素下面出现的顺序,使用<DigestMethod>元素所指定的哈希算法对经过转换的数据进行哈希运算,在<DigestValue>元素中存储产生的哈希值。
<SignatureValue>包含通过签名SignedInfo元素及其所有子元素而计算得到的签名值。
<KeyInfo>存储密钥名称、密钥值、密钥检索方法或证书信息。
1 <?xml version="1.0" encoding="utf-8" standalone="yes"?>
2 <!--This is a comment-->
3 <content>
4 <public>
5 <name>wuhong</name>
6 <job>engineer</job>
7 </public>
8 <private>
9 <creditcard>20101220</creditcard>
10 <phonenumber>132150</phonenumber>
11 </private>
12 <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
13 <SignedInfo>
14 <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
15 <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
16 <Reference URI="">
17 <Transforms>
18 <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
19 </Transforms>
20 <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
21 <DigestValue>4EId0mV0lU5UJ/TMa65XQFZVn9I=</DigestValue>
22 </Reference>
23 </SignedInfo>
24 <SignatureValue>rw84o7X+F1kcennlhx7lTV15T2CuBzZGbusbi1bP+v8fBihe6EcArpvJCXkoEryzvYsr+nNngnrzSV3kYJ9K44KENVPeZfs9wTcs5aw8BUaNMtdiJLla1UeXEsz2MQj2dujTVfvTdG8qCqZVdWnSJYqyp27rnQlsj0CIzElv6EU=</SignatureValue>
25 <KeyInfo>
26 <X509Data>
27 <X509Certificate>MIICBzCCAXCgAwIBAgIQ……</X509Certificate>
28 </X509Data>
29 </KeyInfo>
30 </Signature>
31 </content>
32
验证签名是比较简单的,代码如下:
1 public static Boolean VerifySign(XmlDocument Doc, string issuer)
2 {
3 //XML有效性验证
4 XmlNodeList nodeList = Doc.GetElementsByTagName("Signature");
5 if (nodeList.Count <= 0)
6 {
7 throw new CryptographicException("缺失signature节点");
8 }
9 if (nodeList.Count >= 2)
10 {
11 throw new CryptographicException("signature节点多于一个");
12 }
13
14 SignedXml signedXml = new SignedXml(Doc);
15
16 signedXml.LoadXml((XmlElement)nodeList[0]);
17
18 //获取证书
19 X509Certificate2 x = CertificateUtil.GetCertificate(StoreName.My, StoreLocation.LocalMachine, issuer);
20
21 //验证签名以及证书,verifySignatureOnly设置为flase则不验证证书
22 return signedXml.CheckSignature(x, true);
23 }
24