Я хочу использовать аппаратный модуль безопасности (HSM) для подписи строки. В частности, для этого я использую ePass3003Auto. Я сохранил сертификат в HSM, у которого есть закрытый ключ. Используя пакет Pkcs11Interop и механизм CKM.CKM_SHA256_RSA_PKCS, я получаю результат X. Чтобы проверить точность процесса подписи, я также использую RSACryptoServiceProvider для подписи тех же данных с использованием аргументов HashAlgorithmName.SHA256 и RSASignaturePadding.Pkcs1, что дает результат Y.
Что я сделал до сих пор?
RSACryptoServiceProvider, идентичен ключу, хранящемуся в HSM. Чтобы еще больше убедиться в этом, я протестировал оба метода, используя SHA1, и получил одинаковые подписи, что указывает на то, что ключи действительно одинаковы.byte, передаваемые методам подписи, также одинаковы.x86 и x64, но поскольку 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))
{
// 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();
}
}
}
Вы проверяете достоверность подписи с помощью функции Verify, а не другого метода подписи. Большинство алгоритмов подписи включают в подпись ту или иную форму переменной/случайной исходной информации, что означает, что даже подписание одного и того же объекта, одним и тем же ключом, одним и тем же способом каждый раз приведет к получению другой подписи. Я не говорю, что это относится к механизму, который вы выбрали, но предполагаю, что это плохая привычка.
@jariq спасибо за поддержку. Я воспользовался вашим предложением и опубликовал свой рабочий код как принятый ответ.
@rip... ты прав. Но я оказался в конкретной ситуации, когда мои сгенерированные подписи были недействительны, и я пытался найти причину. Поэтому я использовал другой алгоритм хеширования, чтобы подтвердить правильность моего метода подписи. Кроме того, Pkcs#1 не включает случайное заполнение и является детерминированным. Поэтому мы должны получить одну и ту же подпись, когда подписываем одни и те же данные одним и тем же закрытым ключом.
@AhmadBadkoubehei Я имею дело со многими программистами приложений безопасности (заметьте, не «профессионалами». Назовем их «грамматистами-любителями»), которые думают, что каждая схема подписи детерминирована. Вы четко понимаете разницу между детерминистическим и вероятностным, поэтому вы не из их числа :)





Для тех, у кого могут возникнуть подобные проблемы, я публикую свой рабочий код на основе библиотеки Pkcs11Interop.X509Store, предложенной jariq в комментариях. Этот пакет зависит от библиотеки Pkcs11Interop, которая позволяет выполнять операции высокого уровня.
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;
}
ИМХО, вам следует попробовать использовать библиотеку Pkcs11Interop.X509Store, которая построена на основе Pkcs11Interop и занимается всеми деталями низкого уровня, с которыми вы пытаетесь здесь бороться.