Преобразование подписи Openssl в .NET6

Приложение вызывает 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?

openssl rsautl-знак просто подписывает с помощью RSA. Для .NET см. класс RSA или производные от него классы.
Topaco 19.03.2022 15:49

Я почти уверен, что сам dotnet не поддерживает этот формат ключевого файла, но (версия dotnet) BouncyCastle поддерживает. Или вы можете использовать OpenSSL для преобразования в формат PKCS8 или PKCS12, также известный как PFX, который dotnet изначально поддерживает; для последнего вы должны иметь/получить/создать сертификат, но он не обязательно должен быть от настоящего ЦС, он может быть фиктивным/самозаверяющим.

dave_thompson_085 19.03.2022 16:56

.NET 5 и 6 изначально поддерживают формат PEM, см. X509Certificate2.CreateFromPem. См. scottbrady91.com/c-sharp/pem-loading-in-dotnet-core-and-dotn‌​et

Andrus 19.03.2022 17:18

@topaco Какие методы класса RSA следует использовать для замены этой подписи Openssl?

Andrus 19.03.2022 17:20

Оператор OpenSSL использует заполнение PKCS#1 v1.5 и подписывает данные, содержащиеся в раси.бин, без хеширования и без добавления перед дайджестом OID. Последний не поддерживается .NET, и вам нужен BouncyCastle (с алгоритмом NoneWithRSA). Поскольку вы все равно используете BC, наиболее эффективным будет импорт ключа через PemReader (хотя, начиная с .NET Core 3.0, PKCS#1 также поддерживается). Здесь вы можете найти пример, вариант 2. Вам нужна только часть подписи и вы должны использовать NoneWithRSA, как сказано.

Topaco 19.03.2022 19:03

Спасибо. Использование 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(); сработало. Вы можете написать это как ответ

Andrus 20.03.2022 00:05
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
86
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Почему обычно нельзя использовать нативные методы .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 из вашего сообщения?

Andrus 20.03.2022 21:00

@ Андрус - Нет, это невозможно. Чтобы использовать собственные методы C#, особенно SignHash(), раси.бин должен содержать кодировку DER значения ДайджестИнформация. Но, согласно вашему комментарию, это не так, раси.бин содержит только хеш-значение. Поэтому вы должны использовать подход BouncyCastle.

Topaco 20.03.2022 21:33

сертификат и ключ можно поместить в один файл с помощью openssl pkcs12 -export -inkey riktest.key -in cert.txt -out composite.p12 -name "MyCert". Можно ли в этом случае использовать чистый .NET6 с «composite.p12»?

Andrus 23.03.2022 14:38

Я могу конвертировать сертификат в формат pfx без проблем. Я спросил об использовании файла pfx для этого типа подписи без Bouncy Castle.

Andrus 25.03.2022 10:29

@Andrus - Для обсуждаемого здесь процесса подписания я не вижу никаких преимуществ в использовании файла PFX (наоборот, я предполагаю, что это довольно усложняет ситуацию). Однако я могу что-то упустить, поэтому было бы полезно опубликовать новый вопрос с подробным объяснением. Возможно, у других могут быть идеи.

Topaco 25.03.2022 12:32

Преимущество заключается в удалении зависимости BouncyCastle из приложения, если использование файла pfx позволяет войти в чистый .NET 6.

Andrus 26.03.2022 08:01

@Andrus - Под нет преимущества я имею в виду отсутствие преимущества, без которого можно было бы обойтись без BC. Насколько мне известно, PFX не поддерживает процесс подписи, нарушающий стандарт PKCS#1 v1.5, как описано в RFC8017 (ни низкий, ни высокий уровень).

Topaco 26.03.2022 09:56

Другие вопросы по теме