仅用原始数据哈希验证PKCS7(CMS)签名的技术实现问询
我尝试用System.Security.Cryptography或BouncyCastle库验证分离式PKCS7(CMS)签名,折腾了好几个小时都没成功。核心问题是:我只能拿到原始数据的哈希值,以及对应的分离式CMS签名,需要验证这个签名的有效性。
当传入原始数据时,下面的代码能正常验证,但我拿不到原始数据,只有哈希值。我推测失败原因是CheckSignature方法会在验证前再次对传入的哈希值做哈希运算,得想办法跳过这一步。有没有不用原始数据就能验证签名的方法?
.NET代码示例(传入原始数据有效,传入哈希值无效):
byte[] originalData = Encoding.UTF8.GetBytes("Test"); byte[] hashedData = new SHA512Managed().ComputeHash(originalData); // hashedData是我随签名收到的数据 var content = new System.Security.Cryptography.Pkcs.ContentInfo(originalData); // 正常工作,但我没有原始数据 var content = new System.Security.Cryptography.Pkcs.ContentInfo(hashedData); // 无法验证通过 var cms = new System.Security.Cryptography.Pkcs.SignedCms(content, true); cms.Decode(signature); cms.CheckSignature(true);
我试过在ContentInfo构造函数里指定数据已哈希,但没用。如果.NET实现不了,也可以给BouncyCastle的解决方案,我当前的BouncyCastle代码也只支持原始数据,用哈希值不行:
var certificate = DotNetUtilities.FromX509Certificate(workerCert); var processable = new CmsProcessableByteArray(hashedData); var cms = new CmsSignedData(processable, signature); var signers = cms.GetSignerInfos(); var signersCollection = signers.GetSigners(); foreach (var signer in signersCollection.Cast<SignerInformation>()) { if (signer.Verify(certificate.GetPublicKey())) { Console.WriteLine("Working"); } }
.NET 解决方案
要直接用哈希值验证,关键是告诉CMS系统传入的是预计算的哈希值,而不是原始数据。.NET的SignedCms没有直接暴露这个选项,但可以通过手动构造ContentInfo并指定正确的OID来实现:
- 确定签名使用的哈希算法OID(比如SHA-512的OID是
2.16.840.1.101.3.4.2.3) - 构造包含哈希值的
DigestInfoASN.1结构,将其作为ContentInfo的内容 - 用这个
ContentInfo初始化SignedCms并执行验证
代码示例(.NET 5+):
using System.Security.Cryptography; using System.Security.Cryptography.Pkcs; using System.Security.Cryptography.Asn1; byte[] hashedData = ...; // 你收到的哈希值 byte[] signature = ...; // 你收到的CMS签名 Oid hashOid = Oid.FromFriendlyName("SHA512", OidGroup.HashAlgorithm); // 构造DigestInfo ASN.1结构 var digestInfo = new DigestInfoAsn { DigestAlgorithm = new AlgorithmIdentifierAsn { Algorithm = hashOid.Value }, Digest = hashedData, }; byte[] digestInfoBytes; using (var writer = new AsnWriter(AsnEncodingRules.DER)) { digestInfo.Encode(writer); digestInfoBytes = writer.Encode(); } // 创建ContentInfo,指定类型为digestInfo(OID: 1.2.840.113549.1.9.4) var contentInfo = new ContentInfo(new Oid("1.2.840.113549.1.9.4"), digestInfoBytes); var cms = new SignedCms(contentInfo, true); cms.Decode(signature); cms.CheckSignature(true);
如果使用.NET Framework或低于5的版本,DigestInfoAsn不可用,需要手动构造ASN.1字节流,或者借助BouncyCastle辅助解析。
BouncyCastle 解决方案
BouncyCastle提供了更灵活的验证方式,可以直接跳过内容哈希计算,用给定的哈希值验证签名:
核心思路是从CMS签名中提取签名者的算法信息和签名值,直接用公钥验证签名值与哈希值的匹配性。
代码示例:
using Org.BouncyCastle.Cms; using Org.BouncyCastle.Security; using Org.BouncyCastle.X509; byte[] hashedData = ...; // 你收到的哈希值 byte[] signature = ...; // 你收到的CMS签名 X509Certificate certificate = DotNetUtilities.FromX509Certificate(workerCert); // 解析CMS签名,无需传入原始数据 var cms = new CmsSignedData(signature); var signerInfos = cms.GetSignerInfos(); foreach (SignerInformation signer in signerInfos.GetSigners()) { // 获取签名算法标识符(如SHA512withRSA) string signatureAlgId = signer.SignatureAlgorithm.Algorithm.Id; ISigner verifier = SignerUtilities.GetSigner(signatureAlgId); // 初始化验证器,传入公钥 verifier.Init(false, certificate.GetPublicKey()); // 传入预计算的哈希值 verifier.BlockUpdate(hashedData, 0, hashedData.Length); // 验证签名值是否匹配 if (verifier.VerifySignature(signer.GetSignature())) { Console.WriteLine("签名验证通过"); } }
这个方法完全绕开了BouncyCastle内置的内容哈希步骤,直接使用你提供的哈希值完成验证,完美适配没有原始数据的场景。
内容的提问来源于stack exchange,提问作者NoConnection

