在C#开发中,处理XML文档的数字签名时,SignedXml类是最常用的工具,它封装了XML数字签名规范的大部分实现,能够方便地完成签名生成和验证工作。下面我们一步步讲解如何使用这个类对XML文档进行签名。

签名前的准备工作
使用SignedXml类签名XML文档,首先需要准备非对称加密的密钥对,通常我们使用RSA算法生成密钥,也可以使用已有的证书。同时需要明确要签名的XML文档内容,以及签名要覆盖的节点范围。
生成RSA密钥对
我们可以通过RSACryptoServiceProvider类生成RSA密钥,示例代码如下:
using System.Security.Cryptography;
// 生成RSA密钥对,密钥长度2048位
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048))
{
// 导出公钥,用于验证签名
string publicKey = rsa.ToXmlString(false);
// 导出私钥,用于生成签名
string privateKey = rsa.ToXmlString(true);
Console.WriteLine("公钥已生成");
Console.WriteLine("私钥已生成");
}
使用SignedXml签名XML文档
签名的核心步骤包括加载XML文档、创建SignedXml实例、配置签名引用、计算签名并将签名节点插入到XML文档中。
完整签名示例
以下是完整的XML签名实现代码:
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Xml;
using System.Xml.Cryptography.X509Certificates;
public class XmlSigner
{
/// <summary>
/// 使用RSA私钥签名XML文档
/// </summary>
/// <param name="xmlContent">原始XML字符串</param>
/// <param name="privateKeyXml">RSA私钥的XML字符串</param>
/// <returns>签名后的XML字符串</returns>
public static string SignXml(string xmlContent, string privateKeyXml)
{
// 加载原始XML文档
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.PreserveWhitespace = true;
xmlDoc.LoadXml(xmlContent);
// 创建SignedXml实例,关联要签名的XML文档
SignedXml signedXml = new SignedXml(xmlDoc);
// 设置签名的密钥,使用RSA私钥
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(privateKeyXml);
signedXml.SigningKey = rsa;
}
// 创建签名引用,指定签名覆盖整个XML文档
Reference reference = new Reference();
reference.Uri = ""; // 空字符串表示签名整个文档
// 添加XML规范化转换,确保签名的一致性
XmlDsigEnvelopedSignatureTransform envTransform = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(envTransform);
XmlDsigC14NTransform c14nTransform = new XmlDsigC14NTransform();
reference.AddTransform(c14nTransform);
// 将引用添加到SignedXml实例
signedXml.AddReference(reference);
// 计算签名
signedXml.ComputeSignature();
// 获取签名节点并插入到XML文档中
XmlElement signatureElement = signedXml.GetXml();
// 将签名节点添加到文档的根节点下
xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(signatureElement, true));
return xmlDoc.OuterXml;
}
}
调用签名方法
我们可以构造一个简单的XML文档,调用上面的签名方法进行测试:
class Program
{
static void Main(string[] args)
{
// 原始XML内容
string originalXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
<user>
<id>1001</id>
<name>张三</name>
<age>25</age>
</user>";
// 生成RSA密钥对
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048))
{
string privateKey = rsa.ToXmlString(true);
// 调用签名方法
string signedXml = XmlSigner.SignXml(originalXml, privateKey);
Console.WriteLine("签名后的XML内容:");
Console.WriteLine(signedXml);
}
}
}
验证XML签名
签名完成后,我们还需要验证签名的有效性,确保XML文档没有被篡改,并且签名是由对应的私钥生成的。
签名验证实现
验证签名的代码示例如下:
using System;
using System.Security.Cryptography;
using System.Xml;
public class XmlVerifier
{
/// <summary>
/// 验证XML文档的签名
/// </summary>
/// <param name="signedXmlContent">签名后的XML字符串</param>
/// <param name="publicKeyXml">RSA公钥的XML字符串</param>
/// <returns>签名是否有效</returns>
public static bool VerifyXml(string signedXmlContent, string publicKeyXml)
{
// 加载签名后的XML文档
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.PreserveWhitespace = true;
xmlDoc.LoadXml(signedXmlContent);
// 创建SignedXml实例,关联要验证的XML文档
SignedXml signedXml = new SignedXml(xmlDoc);
// 获取XML中的签名节点
XmlNodeList signatureNodeList = xmlDoc.GetElementsByTagName("Signature", "http://www.w3.org/2000/09/xmldsig#");
if (signatureNodeList.Count == 0)
{
throw new Exception("XML文档中未找到签名节点");
}
// 加载签名节点
signedXml.LoadXml((XmlElement)signatureNodeList[0]);
// 设置验证用的公钥
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(publicKeyXml);
signedXml.CheckSignature(rsa);
}
// 验证签名
return signedXml.CheckSignature();
}
}
调用验证方法
使用之前生成的公钥验证签名:
class Program
{
static void Main(string[] args)
{
// 假设signedXml是之前签名得到的XML字符串,publicKey是之前生成的公钥
string signedXml = "签名后的XML内容";
string publicKey = "生成的RSA公钥XML字符串";
bool isValid = XmlVerifier.VerifyXml(signedXml, publicKey);
if (isValid)
{
Console.WriteLine("XML签名验证通过,文档未被篡改");
}
else
{
Console.WriteLine("XML签名验证失败,文档可能被篡改");
}
}
}
注意事项
- 签名前需要设置
XmlDocument的PreserveWhitespace属性为true,避免空白字符变化导致签名验证失败。 - 如果使用证书进行签名,可以将证书的私钥赋值给
SignedXml.SigningKey,验证时使用证书的公钥即可。 - 签名引用的
Uri属性可以根据需求调整,比如指定某个节点的ID来只签名特定节点。 - 生产环境中私钥需要妥善保存,避免泄露导致签名被伪造。