Как я могу подписать файл, используя RSA и SHA256 с .NET? - PullRequest
43 голосов
/ 16 сентября 2011

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

Это код, который я пытался использовать, но я получаю System.Security.Cryptography.CryptographicException "Invalid algorithm specified.".

X509Certificate pXCert = new X509Certificate2(@"keyStore.p12", "password");
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)pXCert.PrivateKey;
string id = CryptoConfig.MapNameToOID("SHA256");
return csp.SignData(File.ReadAllBytes(filePath), id);

Согласно этому ответу это невозможно сделать (RSACryptoServiceProvider не поддерживает SHA-256), но я надеялся, что это возможно при использовании другогобиблиотека, как Bouncy Castle.

Я новичок в этом, и я нахожу Bouncy Castle очень запутанным.Я портирую Java-приложение на C #, и мне нужно использовать тот же тип шифрования для подписи файлов, поэтому я застрял с RSA + SHA256.

Как я могу это сделать, используя Bouncy Castle, OpenSSL.NET, Security.Cryptography или другая сторонняя библиотека, о которой я не слышал?Я предполагаю, что если это может быть сделано в Java, то это может быть сделано в C #.

ОБНОВЛЕНИЕ:

это то, что я получил по ссылке в anwser Пупу

        X509Certificate2 cert = new X509Certificate2(KeyStoreFile, password");
        RSACryptoServiceProvider rsacsp = (RSACryptoServiceProvider)cert.PrivateKey;
        CspParameters cspParam = new CspParameters();
        cspParam.KeyContainerName = rsacsp.CspKeyContainerInfo.KeyContainerName;
        cspParam.KeyNumber = rsacsp.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2;
        RSACryptoServiceProvider aescsp = new RSACryptoServiceProvider(cspParam);
        aescsp.PersistKeyInCsp = false;
        byte[] signed = aescsp.SignData(File.ReadAllBytes(file), "SHA256");
        bool isValid = aescsp.VerifyData(File.ReadAllBytes(file), "SHA256", signed);

Проблема в том, что я не получаю те же результаты, что и с оригинальным инструментом.Насколько я могу судить по прочтению кода, CryptoServiceProvider, который выполняет фактическую подпись, не использует PrivateKey из файла хранилища ключей.Это правильно?

Ответы [ 9 ]

55 голосов
/ 19 сентября 2011

RSA + SHA256 может и будет работать ...

Ваш более поздний пример может работать не все время, он должен использовать OID хеш-алгоритма, а не его имя.Согласно первому примеру, это получается из вызова на CryptoConfig.MapNameToOID(AlgorithmName), где AlgorithmName - это то, что вы предоставляете (т. Е. «SHA256»).

Сначала вам понадобитсяэто сертификат с закрытым ключом.Обычно я читаю мой из хранилища LocalMachine или CurrentUser, используя файл открытого ключа (.cer) для идентификации закрытого ключа, а затем перечисляю сертификаты и сопоставляю их по хэшу ...

X509Certificate2 publicCert = new X509Certificate2(@"C:\mycertificate.cer");

//Fetch private key from the local machine store
X509Certificate2 privateCert = null;
X509Store store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach( X509Certificate2 cert in store.Certificates)
{
    if (cert.GetCertHashString() == publicCert.GetCertHashString())
        privateCert = cert;
}

вы получите там, как только вы получили сертификат с закрытым ключом, мы должны восстановить его.Это может потребоваться из-за способа, которым сертификат создает свой закрытый ключ, но я не совсем уверен, почему.В любом случае, мы делаем это, сначала экспортируя ключ, а затем повторно импортируя его, используя любой промежуточный формат, который вам нравится, самый простой - xml:

//Round-trip the key to XML and back, there might be a better way but this works
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
key.FromXmlString(privateCert.PrivateKey.ToXmlString(true));

Как только это будет сделано, мы теперь можем подписать часть данных какследует:

//Create some data to sign
byte[] data = new byte[1024];

//Sign the data
byte[] sig = key.SignData(data, CryptoConfig.MapNameToOID("SHA256"));

Наконец, проверка может быть проведена непосредственно с открытым ключом сертификата без необходимости восстановления, как мы делали с закрытым ключом:

key = (RSACryptoServiceProvider)publicCert.PublicKey.Key;
if (!key.VerifyData(data, CryptoConfig.MapNameToOID("SHA256"), sig))
    throw new CryptographicException();
19 голосов
/ 24 декабря 2014

Использование privateKey.toXMLString (true) или privateKey.exportParameters (true) невозможно использовать в защищенной среде, поскольку для них требуется экспорт вашего закрытого ключа, что НЕ является хорошей практикой.* Лучшим решением является явная загрузка «расширенного» поставщика шифрования следующим образом:

// Find my openssl-generated cert from the registry
var store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, "myapp.com", true);
var certificate = certificates[0];
store.Close();
// Note that this will return a Basic crypto provider, with only SHA-1 support
var privKey = (RSACryptoServiceProvider)certificate.PrivateKey;
// Force use of the Enhanced RSA and AES Cryptographic Provider with openssl-generated SHA256 keys
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
var cspparams = new CspParameters(enhCsp.ProviderType, enhCsp.ProviderName, privKey.CspKeyContainerInfo.KeyContainerName);
privKey = new RSACryptoServiceProvider(cspparams);
9 голосов
/ 20 февраля 2015

Я остановился на , изменив файл ключа, указав соответствующий поставщик услуг шифрования , полностью исключив проблему в .NET.

Поэтому, когда я создаю файл PFX из частного PEMключ и открытый сертификат CRT, я делаю это следующим образом:

openssl pkcs12 -export -aes256 -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider" -inkey priv.pem -in pub.crt -out priv.pfx

Основной частью является -CSP «Microsoft Enhanced RSA и AES Cryptographic Provider» .

(-inkey указывает файл закрытого ключа, а -in указывает открытый сертификат для включения.)

Возможно, вам придется настроить его для имеющихся форматов файлов.Примеры командной строки на этой странице могут помочь с этим: https://www.sslshopper.com/ssl-converter.html

Я нашел это решение здесь: http://hintdesk.com/c-how-to-fix-invalid-algorithm-specified-when-signing-with-sha256/

7 голосов
/ 21 мая 2014

Вот как я справился с этой проблемой:

 X509Certificate2 privateCert = new X509Certificate2("certificate.pfx", password, X509KeyStorageFlags.Exportable);

 // This instance can not sign and verify with SHA256:
 RSACryptoServiceProvider privateKey = (RSACryptoServiceProvider)privateCert.PrivateKey;

 // This one can:
 RSACryptoServiceProvider privateKey1 = new RSACryptoServiceProvider();
 privateKey1.ImportParameters(privateKey.ExportParameters(true));

 byte[] data = Encoding.UTF8.GetBytes("Data to be signed"); 

 byte[] signature = privateKey1.SignData(data, "SHA256");

 bool isValid = privateKey1.VerifyData(data, "SHA256", signature);
5 голосов
/ 27 июня 2012

Когда вы используете сертификат для получения RSACryptoServiceProvider, действительно важно, что является основным поставщиком CryptoAPI. По умолчанию, когда вы создаете сертификат с помощью 'makecert', это «RSA-FULL», который поддерживает только хэши SHA1 для подписи. Вам нужен новый RSA-AES, который поддерживает SHA2.

Таким образом, вы можете создать свой сертификат с дополнительной опцией: -sp "Microsoft Enhanced RSA и AES Cryptographic Provider" (или эквивалентно -sy 24), и тогда ваш код будет работать без всяких манипуляций с ключами.

4 голосов
/ 21 марта 2017

Вот как я подписал строку без необходимости изменять сертификат (для провайдера шифрования Microsoft Enhanced RSA и AES).

        byte[] certificate = File.ReadAllBytes(@"C:\Users\AwesomeUser\Desktop\Test\ServerCertificate.pfx");
        X509Certificate2 cert2 = new X509Certificate2(certificate, string.Empty, X509KeyStorageFlags.Exportable);
        string stringToBeSigned = "This is a string to be signed";
        SHA256Managed shHash = new SHA256Managed();
        byte[] computedHash = shHash.ComputeHash(Encoding.Default.GetBytes(stringToBeSigned));


        var certifiedRSACryptoServiceProvider = cert2.PrivateKey as RSACryptoServiceProvider;
        RSACryptoServiceProvider defaultRSACryptoServiceProvider = new RSACryptoServiceProvider();
        defaultRSACryptoServiceProvider.ImportParameters(certifiedRSACryptoServiceProvider.ExportParameters(true));
        byte[] signedHashValue = defaultRSACryptoServiceProvider.SignData(computedHash, "SHA256");
        string signature = Convert.ToBase64String(signedHashValue);
        Console.WriteLine("Signature : {0}", signature);

        RSACryptoServiceProvider publicCertifiedRSACryptoServiceProvider = cert2.PublicKey.Key as RSACryptoServiceProvider;
        bool verify = publicCertifiedRSACryptoServiceProvider.VerifyData(computedHash, "SHA256", signedHashValue);
        Console.WriteLine("Verification result : {0}", verify);
3 голосов
/ 16 сентября 2011

Согласно этому блогу он должен работать с FX 3.5 (см. Примечание ниже). Однако важно помнить, что большая часть криптографии .NET основана на CryptoAPI (даже если CNG все больше и больше раскрывается в последних выпусках FX).

Ключ заключается в том, что поддержка алгоритма CryptoAPI зависит от используемого поставщика услуг шифрования (CSP), который немного варьируется в зависимости от версии Windows (т.е. что работает в Windows 7, может не работать в Windows 2000).

Прочтите комментарии (из записи блога), чтобы увидеть возможный обходной путь, при котором вы указываете AES CSP (вместо значения по умолчанию) при создании экземпляра RSACCryptoServiceProvider . Это, кажется, работает для некоторых людей, YMMV.

Примечание: это сбивает с толку многих людей, потому что все выпущенные платформы .NET включают в себя управляемую реализацию SHA256, которая не может использоваться CryptoAPI. FWIW Mono не страдает от таких проблем; -)

1 голос
/ 13 декабря 2018

Использование может использовать это на более поздних платформах.

    public byte[] GetSignature(byte[] inputData)
    {
        using (var rsa = this.signingCertificate.GetRSAPrivateKey())
        {
            return rsa.SignData(inputData, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        }
    }

    public bool ValidateSignature(byte[] inputData, byte[] signature)
    {
        using (var rsa = this.signingCertificate.GetRSAPublicKey())
        {
            return rsa.VerifyData(inputData, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        }
    }

signingCertificate выше - это X509Certificate2 с закрытым ключом. Этот метод не требует импорта существующих ключей и работает в защищенной среде.

0 голосов
/ 19 сентября 2011

Я заметил аналогичные проблемы в .NET с использованием неверного закрытого ключа (или это были просто ошибки? Я не помню), когда сертификат, с которым я работаю, не находится в хранилище сертификатов пользователя / компьютера. Установка его в сохраненный исправила проблему для моего сценария, и все стало работать как положено - возможно, вы можете попробовать это.

...