0

Application invokes openssl for signing using

openssl rsautl -sign -in rasi.bin -inkey riktest.key -out allkiri.bin

How to convert this to .NET 6 so that invoking openssl is not required?

riktest.key is text file containing

-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAfddAQEArYjDH7msMFifeYc1AG/TkKpcz2LITI73sC0eqnlgmWi3F7PD
Bo8lWrCw32h3v/FFMrK8KuktlnBtsSLaCCz1DWuXORzHaW7EqG8O8QNzFSmhIoqp
...

This is ASP.NET 6 MVC application. Does .NET 6 System.Security.Cryptography namespace contain this OpenSsl functionality ?

Andrus
  • 26,339
  • 60
  • 204
  • 378
  • [openssl rsautl -sign](https://www.openssl.org/docs/man1.1.1/man1/openssl-rsautl.html) is simply signing with RSA. For .NET see the [RSA class](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsa?view=net-6.0) or the classes derived from this. – Topaco Mar 19 '22 at 14:49
  • I'm pretty sure dotnet itself doesn't support that keyfile format, but (the dotnet version of) BouncyCastle does. Or you can use OpenSSL to convert to PKCS8 or PKCS12-aka-PFX format which dotnet does support natively; for the latter you must have/get/create a certificate, but it needn't be from a real CA, it can be a dummy/selfsigned one. – dave_thompson_085 Mar 19 '22 at 15:56
  • .NET 5 and 6 supports PEM format natively, see `X509Certificate2.CreateFromPem`. See https://www.scottbrady91.com/c-sharp/pem-loading-in-dotnet-core-and-dotnet – Andrus Mar 19 '22 at 16:18
  • @topaco What RSA class methods should used to replace this Openssl signing? – Andrus Mar 19 '22 at 16:20
  • 1
    The OpenSSL statement uses PKCS#1 v1.5 padding and signs the data contained in _rasi.bin_ without hashing and without prepending the digest OID. The latter is not supported by .NET and you need BouncyCastle (with the `NoneWithRSA` algorithm). Since you are using BC anyway, a key import via `PemReader` would be the most effective (although as of .NET Core 3.0 PKCS#1 is also supported). [Here](https://stackoverflow.com/a/68184838/9014097) you can find an example, option 2. You only need the signature part and have to use `NoneWithRSA` as said. – Topaco Mar 19 '22 at 18:03
  • Thank you. Using `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();` worked. You may wrote this as answer – Andrus Mar 19 '22 at 23:05

1 Answers1

2

Why generally the native .NET methods cannot be used

For RSASignaturePadding.Pkcs1, the native .NET implementations SignData() and SignHash() follow the RSASSA-PKCS1-v1_5 signature scheme described in RFC8017, which applies EMSA-PKCS1-v1_5 as encoding operation: The message is hashed and the following value is signed (i.e. encrypted with the private key):

EM = 0x00 || 0x01 || PS || 0x00 || T

Here PS consists of so many 0xff values that the size of EM is equal to the size of the modulus of the key. T is the DER encoding of the DigestInfo value, which contains the digest OID and the hash, e.g. for SHA256:

3031300d060960864801650304020105000420 || H 

where H is the 32 bytes SHA56 hash of the message M to be signed.

In contrast, openssl rsautl uses the RSA algorithm directly, as mentioned in the NOTES section, i.e. the following data is signed:

EM' = 0x00 || 0x01 || PS || 0x00 || M

This cannot be achieved with the native .NET methods in general (except for a special use case, see below): SignData() hashes and therefore fails, SignHash() does not hash but internally (like SignData()) generates the DER encoding of the DigestInfo value.

An alternative is BouncyCastle, which signs with the algorithm NoneWithRSA just like openssl rsautl.

One disadvantage of this algorithm is that only short messages can be signed due to the missing hashing, since the length criterion cannot be fulfilled for longer messages (according to which the size of EM' must correspond to the size of the modulus of the key).

Key Import

The posted key is a PEM encoded private key in PKCS#1 format.

.NET supports the import of PEM encoded keys (private/public, PKCS#8/PKCS#1 format) with ImportFromPem() since .NET 5, but the import of DER encoded keys has been supported since .NET Core 3.0. A private DER encoded key in PKCS#1 format can be imported with ImportRSAPrivateKey() (the conversion between PEM and DER encoding is trivial and consists of removing header, footer and line breaks and Base64 decoding of the remaining body).

BouncyCastle supports the import of a PEM encoded key with the PemReader class.

A possible implementation of the posted OpenSSL functionality with BouncyCastle

The following code generates the same signature as the OpenSSL statement when rasi.bin holds the data from dataToSign and riktest.key holds the key from 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

Special use case - when the native C# methods can be used

If rasi.bin contains the DER encoding of the DigestInfo value, BouncyCastle is not needed.
The following example assumes that rasi.bin contains the DER encoding of the DigestInfo value for the message The quick brown fox jumps over the lazy dog with SHA256 as digest. I.e. the last 32 bytes correspond to the SHA256 hash.
A possible implementation with native .NET methods is then:

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
}

which gives the same signature, since rasi.bin is identical in both cases.

However, keep in mind that the last approach only works if rasi.bin contains the DER encoding of the DigestInfo value, while the first solution works for arbitrary data in rasi.bin (as long as the length criterion is met).

Topaco
  • 40,594
  • 4
  • 35
  • 62
  • Thank you. In my case rasi.bin is SHA256 hash of data to be signed created by `using (SHA256 shaM = SHA256.Create()) byte[] rasibin =shaM.ComputeHash(Encoding.UTF8.GetBytes(DataToBeSigned));` Can .NET6 native code from your message used in this case? – Andrus Mar 20 '22 at 20:00
  • 1
    @Andrus - No, this is not possible. In order to use the native C# methods, especially `SignHash()`, *rasi.bin* must contain the DER encoding of the *DigestInfo* value. But according to your comment, this is not the case, *rasi.bin* only holds the hash value. Therefore you have to use the BouncyCastle approach. – Topaco Mar 20 '22 at 20:33
  • cert and key can put into single file using `openssl pkcs12 -export -inkey riktest.key -in cert.txt -out composite.p12 -name "MyCert"`. Can pure .NET6 used in this case with ´composite.p12´ ? – Andrus Mar 23 '22 at 13:38
  • I can convert cert to pfx format without issues. I asked about using pfx file for this type of signing without Bouncy Castle. – Andrus Mar 25 '22 at 09:29
  • @Andrus - For the signing process discussed here, I don't see any advantage in using a PFX file (on the contrary, I would guess that this rather complicates things). However, I may be missing something, so it might be useful to post a new question with a detailed explanation. Perhaps others might have an idea. – Topaco Mar 25 '22 at 11:32
  • Advantage is removing BouncyCastle dependency from application if using pfx file allows signing in pure .NET 6 – Andrus Mar 26 '22 at 07:01
  • @Andrus - By *no advantage* I mean, no advantage by which BC could be dispensed with. As far as I know PFX does not support any signing process that violates the PKCS#1 v1.5 standard as described in RFC8017 (neither low nor high level). – Topaco Mar 26 '22 at 08:56