使用同一私钥时Pkcs11Interop与RSACryptoServiceProvider签名结果不一致
ePass3003Auto HSM签名与RSACryptoServiceProvider结果不一致问题
问题描述
使用ePass3003Auto硬件安全模块(HSM)对字符串签名:已将含私钥的证书存入HSM,通过Pkcs11Interop包搭配CKM.CKM_SHA256_RSA_PKCS机制得到签名结果X;用RSACryptoServiceProvider指定HashAlgorithmName.SHA256和RSASignaturePadding.Pkcs1签名同一数据得到结果Y,二者完全不同。
已做排查
- 确认RSACryptoServiceProvider使用的私钥与HSM中存储的一致,SHA1算法测试时两者签名结果相同;
- 验证传入两种方式的原始数据字节数组完全一致,编码无问题;
- 尝试过x86和x64编译配置,因HSM驱动无架构区分,结果无差异;
- 用其他工具通过该HSM签名同一数据得到结果Y,说明RSACryptoServiceProvider的结果正确,问题出在Pkcs11Interop的实现上。
简化代码
using Net.Pkcs11Interop.Common; using Net.Pkcs11Interop.HighLevelAPI; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.OpenSsl; using Org.BouncyCastle.Security; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; namespace TestSigningProblem { internal class Program { internal static string SignUsingHSM(string data, string tokenId, string pin) { const string modulePath = @"C:\Windows\System32\ShuttleCsp11_3003.dll"; var factories = new Pkcs11InteropFactories(); using (var pkcs11Library = factories.Pkcs11LibraryFactory.LoadPkcs11Library(factories, modulePath, AppType.MultiThreaded)) { // 查找存在令牌的第一个插槽 var slot = pkcs11Library.GetSlotList(SlotsType.WithOrWithoutTokenPresent).First(); // 打开读写会话 using (var session = slot.OpenSession(SessionType.ReadWrite)) { // 以普通用户身份登录 session.Login(CKU.CKU_USER, pin); // 指定签名机制 var mechanism = session.Factories.MechanismFactory.Create(CKM.CKM_SHA256_RSA_PKCS); var sourceData = Encoding.UTF8.GetBytes(data); var pkcs11UriBuilder = new Pkcs11UriBuilder { ModulePath = modulePath, PinValue = pin, Type = CKO.CKO_PRIVATE_KEY }; var searchTemplate = Pkcs11UriUtils.GetObjectAttributes(new Pkcs11Uri(pkcs11UriBuilder.ToString()), session.Factories.ObjectAttributeFactory); var allObjects = session.FindAllObjects(searchTemplate); var foundObject = allObjects.FirstOrDefault(x => session.GetAttributeValue(x, new List<CKA>() { CKA.CKA_ID }).First().GetValueAsString().StartsWith(tokenId, StringComparison.OrdinalIgnoreCase)); if (foundObject == null) { throw new Exception("Certificate not found"); } // 签名数据 var signature = session.Sign(mechanism, foundObject, sourceData); session.Logout(); return ConvertUtils.BytesToBase64String(signature); } } } internal static string SignUsingPrivateKey(string data, string privateKey) { var pem = $"-----BEGIN PRIVATE KEY-----\n{privateKey}\n-----END PRIVATE KEY-----"; // 添加头部和尾部 var pemReader = new PemReader(new StringReader(pem)); var rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)pemReader.ReadObject()); var cryptoServiceProvider = new RSACryptoServiceProvider(); cryptoServiceProvider.ImportParameters(rsaParams); var dataBytes = Encoding.UTF8.GetBytes(data); return Convert.ToBase64String(cryptoServiceProvider.SignData(dataBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)); } static void Main(string[] args) { var data = "T"; var privateKey = File.ReadAllText(@"private.key"); Console.WriteLine(SignUsingHSM(data, "********************************", "********")); Console.WriteLine(SignUsingPrivateKey(data, privateKey)); Console.ReadKey(); } } }
问题原因与解决方案
核心原因
CKM.CKM_SHA256_RSA_PKCS机制要求传入的是已完成SHA256哈希的摘要数据,而非原始明文;但当前代码直接传入了原始数据的字节数组。
而RSACryptoServiceProvider.SignData方法会自动完成「原始数据SHA256哈希 → PKCS#1 v1.5签名」的完整流程,因此两者的输入本质不同,导致签名结果不一致。
修复代码
修改SignUsingHSM方法,先对原始数据做SHA256哈希,再传入签名方法:
internal static string SignUsingHSM(string data, string tokenId, string pin) { const string modulePath = @"C:\Windows\System32\ShuttleCsp11_3003.dll"; var factories = new Pkcs11InteropFactories(); using (var pkcs11Library = factories.Pkcs11LibraryFactory.LoadPkcs11Library(factories, modulePath, AppType.MultiThreaded)) { var slot = pkcs11Library.GetSlotList(SlotsType.WithOrWithoutTokenPresent).First(); using (var session = slot.OpenSession(SessionType.ReadWrite)) { session.Login(CKU.CKU_USER, pin); var mechanism = session.Factories.MechanismFactory.Create(CKM.CKM_SHA256_RSA_PKCS); // 先对原始数据计算SHA256哈希 var sourceData = Encoding.UTF8.GetBytes(data); byte[] hashedData; using (var sha256 = SHA256.Create()) { hashedData = sha256.ComputeHash(sourceData); } var pkcs11UriBuilder = new Pkcs11UriBuilder { ModulePath = modulePath, PinValue = pin, Type = CKO.CKO_PRIVATE_KEY }; var searchTemplate = Pkcs11UriUtils.GetObjectAttributes(new Pkcs11Uri(pkcs11UriBuilder.ToString()), session.Factories.ObjectAttributeFactory); var allObjects = session.FindAllObjects(searchTemplate); var foundObject = allObjects.FirstOrDefault(x => session.GetAttributeValue(x, new List<CKA>() { CKA.CKA_ID }).First().GetValueAsString().StartsWith(tokenId, StringComparison.OrdinalIgnoreCase)); if (foundObject == null) { throw new Exception("证书未找到"); } // 传入哈希后的数据执行签名 var signature = session.Sign(mechanism, foundObject, hashedData); session.Logout(); return ConvertUtils.BytesToBase64String(signature); } } }
补充说明
如果HSM支持CKM.CKM_RSA_PKCS机制,也可以选择传入带PKCS#1 v1.5格式前缀的哈希值(即把SHA256哈希用30 31 30 0D 06 09 60 86 48 01 65 03 04 02 01 05 00 04 20前缀拼接),不过直接先哈希再用CKM_SHA256_RSA_PKCS是更简洁的实现方式。
内容的提问来源于stack exchange,提问作者Ahmad Badkoubehei
相关产品推荐
相关产品推荐

