Приложение вызывает openssl для подписи, используя
openssl rsautl -sign -in rasi.bin -inkey riktest.key -out allkiri.bin
Как преобразовать это в .NET 6, чтобы не требовался вызов openssl?
riktest.key — это текстовый файл, содержащий
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAfddAQEArYjDH7msMFifeYc1AG/TkKpcz2LITI73sC0eqnlgmWi3F7PD
Bo8lWrCw32h3v/FFMrK8KuktlnBtsSLaCCz1DWuXORzHaW7EqG8O8QNzFSmhIoqp
...
Это приложение ASP.NET 6 MVC. Содержит ли пространство имен .NET 6 System.Security.Cryptography эту функциональность OpenSsl?
Я почти уверен, что сам dotnet не поддерживает этот формат ключевого файла, но (версия dotnet) BouncyCastle поддерживает. Или вы можете использовать OpenSSL для преобразования в формат PKCS8 или PKCS12, также известный как PFX, который dotnet изначально поддерживает; для последнего вы должны иметь/получить/создать сертификат, но он не обязательно должен быть от настоящего ЦС, он может быть фиктивным/самозаверяющим.
.NET 5 и 6 изначально поддерживают формат PEM, см. X509Certificate2.CreateFromPem
. См. scottbrady91.com/c-sharp/pem-loading-in-dotnet-core-and-dotnet
@topaco Какие методы класса RSA следует использовать для замены этой подписи Openssl?
Оператор OpenSSL использует заполнение PKCS#1 v1.5 и подписывает данные, содержащиеся в раси.бин, без хеширования и без добавления перед дайджестом OID. Последний не поддерживается .NET, и вам нужен BouncyCastle (с алгоритмом NoneWithRSA
). Поскольку вы все равно используете BC, наиболее эффективным будет импорт ключа через PemReader
(хотя, начиная с .NET Core 3.0, PKCS#1 также поддерживается). Здесь вы можете найти пример, вариант 2. Вам нужна только часть подписи и вы должны использовать NoneWithRSA
, как сказано.
Спасибо. Использование RsaPrivateCrtKeyParameters privateKeyParameters = (RsaPrivateCrtKeyParameters)GetAsymmetricKeyParameterFromPem(privateKey, true); ISigner signer = SignerUtilities.GetSigner("NoneWithRSA"); signer.Init(true, privateKeyParameters);signer.BlockUpdate(dataToSign, 0, dataToSign.Length);byte[] signature = signer.GenerateSignature();
сработало. Вы можете написать это как ответ
Почему обычно нельзя использовать нативные методы .NET
Для RSASignaturePadding.Pkcs1
нативные реализации .NET SignData()
и SignHash()
следуют схеме подписи RSASSA-PKCS1-v1_5, описанной в RFC8017, которая применяет EMSA-PKCS1-v1_5 в качестве операции кодирования: сообщение хешируется, и следующее значение подписывается (т. е. шифруется закрытым ключом):
EM = 0x00 || 0x01 || PS || 0x00 || T
Здесь PS состоит из такого количества значений 0xff, что размер EM равен размеру модуля ключа. T — это DER-кодирование значения ДайджестИнформация, которое содержит дайджест OID и хеш, например. для SHA256:
3031300d060960864801650304020105000420 || H
где H — это 32-байтовый хэш SHA56 сообщения M, которое нужно подписать.
Напротив, openssl rsautl использует алгоритм RSA напрямую, как указано в разделе ПРИМЕЧАНИЯ, т. е. подписаны следующие данные:
EM' = 0x00 || 0x01 || PS || 0x00 || M
Этого нельзя достичь с помощью нативных методов .NET в целом (за исключением особого случая использования, см. ниже): SignData()
хеширует и, следовательно, терпит неудачу, SignHash()
не хеширует, а внутренне (как SignData()
) генерирует кодировку DER значения ДайджестИнформация.
Альтернативой является BouncyCastle, который подписывается алгоритмом NoneWithRSA
так же, как openssl rsautl.
Одним из недостатков этого алгоритма является то, что из-за отсутствия хеширования могут быть подписаны только короткие сообщения, так как для более длинных сообщений не может быть выполнен критерий длины (согласно которому размер EM' должен соответствовать размеру модуля ключа) .
Импорт ключей
Опубликованный ключ представляет собой закодированный PEM закрытый ключ в формате PKCS#1.
.NET поддерживает импорт закодированных ключей PEM (закрытый/открытый, формат PKCS#8/PKCS#1) с ImportFromPem()
, начиная с .NET 5, но импорт ключей, закодированных DER, поддерживается начиная с .NET Core 3.0. Частный ключ в кодировке DER в формате PKCS#1 можно импортировать с помощью ImportRSAPrivateKey()
(преобразование между кодировкой PEM и DER тривиально и состоит из удаления заголовка, нижнего колонтитула и разрывов строк, а также декодирования Base64 остального тела).
BouncyCastle поддерживает импорт закодированного ключа PEM с классом PemReader
.
Возможная реализация опубликованной функциональности OpenSSL с BouncyCastle
Следующий код генерирует ту же подпись, что и оператор OpenSSL, когда раси.бин содержит данные из dataToSign
, а riktest.key содержит ключ из privatePkcs1Pem
:
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System;
using System.IO;
...
// For testing purposes a 512 bits key is used.
// In practice, keys >= 2048 bits must be used for security reasons!
string privatePkcs1Pem = @"-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBANoHbFSEZoOSB9Kxt7t8PoBwmauaODjECHqJgtTU3h4MW5K3857+
04Flc6x6a9xxyvCKS5RtOP2gaOlOVtrph0ECAwEAAQJBALu8LpRr2RWrdV7/tfQT
HIJd8oQnbAe9DIvuwh/fF08IwApOE/iGL+Ded49eoHHu1OXycZhpHavN/sQMnssP
FNECIQDyDIW7V5UUu16ZAeupeQ7zdV6ykVngd0bb3FEn99EchQIhAOaYe3ll211q
SIXVjKHudMn3xe6Vvguc9O7cwCB+gyqNAiEAsr3kk6/de23SMZNlf8TR8Z8eyybj
BAuQ3BMaKzWpyjECIFMR0UFNYTYIyLF12aCoH2h2mtY1GW5jj5TQ72GFUcktAiAf
WWXnts7m8kZWuKjfD0MQiW+w4iAph+51j+wiL3EMAQ==
-----END RSA PRIVATE KEY-----";
byte[] dataToSign = Convert.FromHexString("3031300d060960864801650304020105000420d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592");
// Import private PKCS#1 key, PEM encoded
PemReader pemReader = new PemReader(new StringReader(privatePkcs1Pem));
AsymmetricKeyParameter privateKeyParameter = ((AsymmetricCipherKeyPair)pemReader.ReadObject()).Private;
// Sign raw data
ISigner signer = SignerUtilities.GetSigner("NoneWithRSA");
signer.Init(true, privateKeyParameter);
signer.BlockUpdate(dataToSign, 0, dataToSign.Length);
byte[] signature = signer.GenerateSignature();
Console.WriteLine(Convert.ToHexString(signature)); // 8C83CAD897EDA249FEC9EBA231061D585DAFC99177267E3E71BB8A3FCE07CC6663BF4DF7AF2E1C1945D2A6BB42EB25F042228B591FC18CDA82D92CAAE844670C
Особый вариант использования — когда можно использовать нативные методы C#.
Если раси.бин содержит кодировку DER значения ДайджестИнформация, BouncyCastle не нужен.
В следующем примере предполагается, что раси.бин содержит кодировку DER значения ДайджестИнформация для сообщения Быстрая коричневая лиса прыгает через ленивую собаку с SHA256 в качестве дайджеста. т.е. последние 32 байта соответствуют хешу SHA256.
Тогда возможная реализация с собственными методами .NET:
using System;
using System.Security.Cryptography;
...
// For testing purposes a 512 bits key is used.
// In practice, keys >= 2048 bits must be used for security reasons!
string privatePkcs1Pem = @"-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBANoHbFSEZoOSB9Kxt7t8PoBwmauaODjECHqJgtTU3h4MW5K3857+
04Flc6x6a9xxyvCKS5RtOP2gaOlOVtrph0ECAwEAAQJBALu8LpRr2RWrdV7/tfQT
HIJd8oQnbAe9DIvuwh/fF08IwApOE/iGL+Ded49eoHHu1OXycZhpHavN/sQMnssP
FNECIQDyDIW7V5UUu16ZAeupeQ7zdV6ykVngd0bb3FEn99EchQIhAOaYe3ll211q
SIXVjKHudMn3xe6Vvguc9O7cwCB+gyqNAiEAsr3kk6/de23SMZNlf8TR8Z8eyybj
BAuQ3BMaKzWpyjECIFMR0UFNYTYIyLF12aCoH2h2mtY1GW5jj5TQ72GFUcktAiAf
WWXnts7m8kZWuKjfD0MQiW+w4iAph+51j+wiL3EMAQ==
-----END RSA PRIVATE KEY-----";
byte[] sha256DigestInfoDer = Convert.FromHexString("3031300d060960864801650304020105000420d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592");
byte[] sha256HashToSign = new byte[32];
Buffer.BlockCopy(sha256DigestInfoDer, sha256DigestInfoDer.Length - sha256HashToSign.Length, sha256HashToSign, 0, sha256HashToSign.Length);
using (RSA rsa = RSA.Create())
{
rsa.ImportFromPem(privatePkcs1Pem);
byte[] signature = rsa.SignHash(sha256HashToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); // pass the SHA256 hash, internally the DER encoding of the DigestInfo is generated (which is why the digest must be specified)
Console.WriteLine(Convert.ToHexString(signature)); // 8C83CAD897EDA249FEC9EBA231061D585DAFC99177267E3E71BB8A3FCE07CC6663BF4DF7AF2E1C1945D2A6BB42EB25F042228B591FC18CDA82D92CAAE844670C
}
что дает подпись такой же, поскольку раси.бин идентична в обоих случаях.
Однако имейте в виду, что последний подход работает только в том случае, если раси.бин содержит кодировку DER значения ДайджестИнформация, тогда как первое решение работает для данных произвольный в раси.бин (при условии соблюдения критерия длины).
Спасибо. В моем случае rasi.bin — это SHA256-хэш данных для подписи, созданный using (SHA256 shaM = SHA256.Create()) byte[] rasibin =shaM.ComputeHash(Encoding.UTF8.GetBytes(DataToBeSigned));
Может ли в этом случае использоваться нативный код .NET6 из вашего сообщения?
@ Андрус - Нет, это невозможно. Чтобы использовать собственные методы C#, особенно SignHash()
, раси.бин должен содержать кодировку DER значения ДайджестИнформация. Но, согласно вашему комментарию, это не так, раси.бин содержит только хеш-значение. Поэтому вы должны использовать подход BouncyCastle.
сертификат и ключ можно поместить в один файл с помощью openssl pkcs12 -export -inkey riktest.key -in cert.txt -out composite.p12 -name "MyCert"
. Можно ли в этом случае использовать чистый .NET6 с «composite.p12»?
Я могу конвертировать сертификат в формат pfx без проблем. Я спросил об использовании файла pfx для этого типа подписи без Bouncy Castle.
@Andrus - Для обсуждаемого здесь процесса подписания я не вижу никаких преимуществ в использовании файла PFX (наоборот, я предполагаю, что это довольно усложняет ситуацию). Однако я могу что-то упустить, поэтому было бы полезно опубликовать новый вопрос с подробным объяснением. Возможно, у других могут быть идеи.
Преимущество заключается в удалении зависимости BouncyCastle из приложения, если использование файла pfx позволяет войти в чистый .NET 6.
@Andrus - Под нет преимущества я имею в виду отсутствие преимущества, без которого можно было бы обойтись без BC. Насколько мне известно, PFX не поддерживает процесс подписи, нарушающий стандарт PKCS#1 v1.5, как описано в RFC8017 (ни низкий, ни высокий уровень).