Search code examples
c#pkcs#11pkcs11interophardware-security-modulersa-sha256

Different signatures when signing the same data using the same private key in Pkcs11Interop and RSACryptoServiceProvider


I want to use a Hardware Security Module (HSM) to sign a string. Specifically, I am using the ePass3003Auto to do so. I have stored the certificate in the HSM, which I have it's private key. Using the Pkcs11Interop package and the CKM.CKM_SHA256_RSA_PKCS mechanism, I get the result X. To verify the accuracy of the signing process, I also use the RSACryptoServiceProvider to sign the same data using HashAlgorithmName.SHA256 and RSASignaturePadding.Pkcs1 arguments, which produces the result Y.

What have I done so far?

  • I have checked the private key and confirmed that the key used in the RSACryptoServiceProvider is identical to the one stored in the HSM. To further ensure this, I tested both methods using SHA1 and got the same signatures, indicating that the keys are indeed the same.
  • I have verified that the data passed to both algorithms is identical and that encoding is not the issue. The byte arrays passed to the signing methods are also the same.
  • I have tried both x86 and x64 configurations, but as the HSM only has one library and there is no separate library for different architectures, there was no difference in the results.
  • I used another program to test the result of signing the same data using the HSM, which also produced the result Y. Therefore, it appears that the RSACryptoServiceProvider is generating the correct signature, and my approach using Pkcs11Interop is incorrect.

Here is a simplified version of my code:

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))
            {
                // Find first slot with token present
                var slot = pkcs11Library.GetSlotList(SlotsType.WithOrWithoutTokenPresent).First();

                // Open RW session
                using (var session = slot.OpenSession(SessionType.ReadWrite))
                {
                    // Login as normal user
                    session.Login(CKU.CKU_USER, pin);

                    // Specify signing mechanism
                    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");
                    }

                    // Sign data
                    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-----"; // Add header and footer
            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();
        }
    }
}

Solution

  • For others who may have similar problems, I post my working code based on the Pkcs11Interop.X509Store library which was suggested by jariq in comments. This package is dependent on Pkcs11Interop library which allows high level operations.

    public string Sign(string data, string serialNumber)
    {
        var libraryPath = @"C:\Windows\System32\ShuttleCsp11_3003.dll";
    
        using (var store = new Pkcs11X509Store(libraryPath, new UiPinProvider()))
        {
            var certificate = FindCertificateBySerialNumber(store, serialNumber) ?? throw new Exception("Certificate not found");
            var rsa = certificate.GetRSAPrivateKey();
            using (var sha256 = SHA256.Create())
            {
                var binaryData = Encoding.UTF8.GetBytes(data);
                var hash = sha256.ComputeHash(binaryData);
                var formatter = new RSAPKCS1SignatureFormatter(rsa);
                formatter.SetHashAlgorithm("SHA256");
                byte[] signature = formatter.CreateSignature(hash);
                return Convert.ToBase64String(signature);
            }
        }
    }
    
    public Pkcs11X509Certificate FindCertificateBySerialNumber(Pkcs11X509Store store, string serialNumber)
    {
        foreach (var slot in store.Slots)
        {
            if (slot.Token == null)
                continue;
    
            if (!slot.Token.Info.Initialized)
                continue;
    
            foreach (var certificate in slot.Token.Certificates)
            {
                if (certificate.Info?.ParsedCertificate?.SerialNumber?.Equals(serialNumber, StringComparison.OrdinalIgnoreCase) == true)
                {
                    return certificate;
                }
            }
        }
    
        return null;
    }