.NET 8 Native AOT下SignedXml冲突及License验证失败解决咨询
一、解决IL3050错误(SignedXml与Native AOT兼容问题)
System.Security.Cryptography.Xml.SignedXml依赖的XmlDsigXsltTransform需要动态代码生成,而Native AOT不支持动态代码,可通过以下两种方式处理:
1. 移除不必要的XSLT转换(推荐)
如果你的License签名流程未使用XSLT转换,可自定义SignedXml子类,跳过对XmlDsigXsltTransform的支持:
public class AotCompatibleSignedXml : SignedXml { public AotCompatibleSignedXml(XmlDocument doc) : base(doc) { } protected override Transform CreateTransform(string transformUri) { // 跳过XSLT转换,避免触发动态代码 if (transformUri == SignedXml.XmlDsigXsltTransformUrl) { throw new NotSupportedException("XSLT transforms are not supported in AOT"); } return base.CreateTransform(transformUri); } }
验证时使用该子类替代原生SignedXml,同时确保License生成环节未添加XSLT转换。
2. 允许动态代码(不推荐)
若必须保留XSLT转换,可在项目文件中添加配置开启动态代码支持,但会损失AOT的部分优势(如启动速度、封闭性):
<PropertyGroup> <DynamicCodeSupport>true</DynamicCodeSupport> </PropertyGroup>
二、解决自行实现VerifyHash/VerifyData返回false的问题
直接使用SignedInfo.OuterXml获取字节流不符合XML-DSIG规范,因为SignedXml计算签名时采用**独占规范XML(Exclusive Canonical XML)**序列化,而OuterXml输出格式与该序列化结果不一致,导致哈希不匹配。正确做法是对SignedInfo节点进行规范XML序列化:
XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(nodeList[0].OuterXml); XmlNamespaceManager nsManager = GetNamespaceManager(xmlDoc); XmlNode signedInfoNode = xmlDoc.SelectSingleNode("//ds:SignedInfo", nsManager); // 对SignedInfo节点执行独占规范XML序列化 var canonicalizer = new XmlDsigExcC14NTransform(); canonicalizer.LoadInput(signedInfoNode); byte[] signedInfoBytes; using (var ms = new MemoryStream()) { canonicalizer.GetOutput(ms); signedInfoBytes = ms.ToArray(); } // 获取签名值 XmlNode signatureValueNode = xmlDoc.SelectSingleNode("//ds:SignatureValue", nsManager); byte[] signatureValue = Convert.FromBase64String(signatureValueNode.InnerText); // 使用RSA公钥验证 using (var rsa = RSA.Create()) { rsa.ImportFromPem(yourPublicKeyPem); // 替换为你的公钥内容 bool isValid = rsa.VerifyData(signedInfoBytes, signatureValue, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1); return isValid; }
若签名使用普通C14N规范XML,需替换为XmlDsigC14NTransform。
三、替代License验证方案(更适配Native AOT)
若不想处理SignedXml的AOT兼容问题,可改用以下轻量、AOT友好的方案:
1. 自定义二进制License格式
将License核心信息(用户ID、有效期、机器标识等)序列化为二进制(推荐用System.Text.Json序列化),再用RSA对数据签名,验证时反序列化并校验签名:
// 定义License数据结构 public class LicenseData { public string UserId { get; set; } public DateTime ExpireDate { get; set; } public string MachineId { get; set; } } // 生成License var licenseData = new LicenseData { UserId = "user123", ExpireDate = DateTime.Now.AddYears(1), MachineId = "abc123" }; byte[] dataBytes = JsonSerializer.SerializeToUtf8Bytes(licenseData); byte[] signature = rsa.SignData(dataBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); // 将dataBytes和signature组合写入License文件(例如先写数据长度,再写数据,最后写签名) // 验证License // 读取License文件,拆分dataBytes和signature var licenseData = JsonSerializer.Deserialize<LicenseData>(dataBytes); bool isValid = rsa.VerifyData(dataBytes, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); // 额外校验有效期、机器标识等逻辑
2. 使用JWT(JSON Web Token)
JWT是标准化令牌格式,System.IdentityModel.Tokens.Jwt库兼容AOT,可将License信息存入JWT Payload,用RSA签名,验证时直接校验签名与Payload内容:
// 生成JWT License var claims = new List<Claim> { new Claim("userId", "user123"), new Claim("exp", DateTimeOffset.Now.AddYears(1).ToUnixTimeSeconds().ToString()) }; var token = new JwtSecurityToken( issuer: "yourCompany", claims: claims, expires: DateTime.Now.AddYears(1), signingCredentials: new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256) ); string license = new JwtSecurityTokenHandler().WriteToken(token); // 验证JWT License var handler = new JwtSecurityTokenHandler(); var validationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidIssuer = "yourCompany", ValidateLifetime = true, ValidateAudience = false, IssuerSigningKey = new RsaSecurityKey(publicRsa) }; try { handler.ValidateToken(license, validationParameters, out _); return true; } catch { return false; }
内容的提问来源于stack exchange,提问作者Yang Zhang

