You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用同一私钥时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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 05:54:52