Используйте RSACng для SignData, который совместим с RSACryptoServiceProvider - PullRequest
0 голосов
/ 11 января 2020

Я пытаюсь преобразовать какой-то старый код CAPI для использования CNG, особенно с целью увлажнения сертификатов эфемерными закрытыми ключами. (Насколько я понимаю, не поддерживается CAPI.)

Мы используем закрытый ключ сертификата (PK) для подписи данных. Я ожидал, что две реализации приведут к совместимому выводу, но, к моему удивлению, они этого не сделают.

// Hydrate a cert with PK. Make PK a file-based key so we can get the container and use CAPI.
// (Note, although .NET Framework 4.8, the LangVersion is 8.0 so I can use using.)
using var cert = new X509Certificate2(testCertData, "passwd", X509KeyStorageFlags.MachineKeySet);
// Some arbitrary data to sign.
byte[] data = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };

byte[] signatureCsp, signatureCng;
// Sign with CAPI. This is what we're doing today.
// The cert PK's crypto provider doesn't support SHA256, so we build a different one.
var csp = (RSACryptoServiceProvider)cert.PrivateKey;
var cp = new CspParameters{
    KeyContainerName = csp.CspKeyContainerInfo.KeyContainerName,
    KeyNumber = csp.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2,
};
using (var rsaCsp = new RSACryptoServiceProvider(cp)) {
    signatureCsp = rsaCsp.Sign(data);
}
// Sign with CNG. This is what I want to do.
using (var rsaCng = cert.GetRSAPrivateKey()) {
    signatureCng = rsaCng.Sign(data);
}
// The signatures are different. In fact they're different lengths, 128 and 256 bytes respectively.
Console.WriteLine(Convert.ToBase64String(signatureCsp));
Console.WriteLine(Convert.ToBase64String(signatureCng));

// But maybe they're compatible? Let's see if code which verifies the first can verify the second.
using (var provider = new RSACryptoServiceProvider(cp)) {
    var verifiedCsp = provider.Verify(data, signatureCsp);
    var verifiedCng = provider.Verify(data, signatureCng);
    Console.WriteLine("RSACryptoServiceProvider verified: {0}", verifiedCsp);
    Console.WriteLine("RSACng signature         verified: {0}", verifiedCng); // Nope. False.
}

Приведенное выше описание использует следующие методы расширения для единообразия, рассматривая оба случая как реализации абстрактного базового типа RSA. и с теми же параметрами шифрования.

public static byte[] Sign(this RSA rsa, byte[] buffer) {
    return rsa.SignData(buffer, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
public static bool Verify(this RSA rsa, byte[] buffer, byte[] signature) {
    return rsa.VerifyData(buffer, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}

Что мне нужно сделать, чтобы подписать данные ключом CNG сертификата, чтобы выходные данные могли быть проверены текущими потребителями подписей, созданных ключом CAPI?

1 Ответ

1 голос
/ 17 января 2020

Вы правильно используете классы RSA и получите совместимые результаты для соответствующих ключей.

Проблема в том, что вы открываете PFX в хранилище ключей машины:

using var cert = new X509Certificate2(testCertData, "passwd", X509KeyStorageFlags.MachineKeySet);

Но когда вы снова открыли версию ключа RSACryptoServiceProvider, вы не установили флаг CspProviderFlags.UseMachineKeyStore.

var cp = new CspParameters{
    KeyContainerName = csp.CspKeyContainerInfo.KeyContainerName,
    KeyNumber = csp.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2,
};

Поскольку вы не установили флаг хранилища ключей компьютера, он использовал хранилище ключей пользователя и «открытие» ключа создало новый ключ (поскольку ранее такого имени не существовало). После того, как он был создан один раз, последовательные запуски снова откроют тот же ключ (при условии, что импорт PFX получил непротиворечивое имя ключа).

var cp = new CspParameters{
    KeyContainerName = csp.CspKeyContainerInfo.KeyContainerName,
    KeyNumber = csp.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2,
    Flags = CspProviderFlags.UseMachineKeyStore | CspProviderFlags.UseExistingKey,
};
...