Tried to implement openssl command
openssl rsautl -sign -in rasi.bin -inkey riktest.key -out allkiri.bin
using BouncyCastle 2.6.0 Nuget package with code from Converting Openssl signing to .NET6 answer
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System;
using System.Text;
using System.IO;
using System.Security.Cryptography;
public class Program
{
public static void Main()
{
// 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 = SHA256.HashData(Encoding.UTF8.GetBytes("Data to sign"));
// 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));
}
}
GenerateSignatore() throws exception
System.ArgumentException: failed to construct sequence from byte[]: long form definite-length more than 31 bits at Org.BouncyCastle.Asn1.Asn1Sequence.GetInstance(Object obj) at Org.BouncyCastle.Asn1.X509.DigestInfo.GetInstance(Object obj) at Org.BouncyCastle.Crypto.Signers.RsaDigestSigner.CheckDerEncoded(Byte[] hash) at Org.BouncyCastle.Crypto.Signers.RsaDigestSigner.GenerateSignature()
In BouncyCastle 2.5.1 it works.
I fixed code so that exception is thrown in 2.6.0.
In 2.6.0
ISigner signer = SignerUtilities.GetSigner("SHA256withRSA");
creates different signature than
ISigner signer = SignerUtilities.GetSigner("NoneWithRSA");
in 2.5.1
In BouncyCastle 2.6.0 it is not possible to create same signature created using openssl command
openssl rsautl -sign -in rasi.bin -inkey riktest.key -out allkiri.bin
How to implement this using .NET 9 and latest Bouncycastle 2.6.0 ? Or can pure .NET used for this?
Reported in https://github.com/bcgit/bc-csharp/issues/621
The generated signatures are not RFC8017 compliant. The reason for this is that in the context of the OpenSSL statement and the NoneWithRSA
algorithm, the message hash H
is used as the input data and not, as would be correct, the DER encoding of the DigestInfo
value T
(see RFC 8017, sec. 8.2. RSASSA-PKCS1-v1_5).
The OpenSSL statement and the old BouncyCastle version do not validate the input any further and simply generate the (non-compliant) signature. The new BouncyCastle version, on the other hand, performs (some) validations, which leads to various exceptions if T
is invalid.
The most efficient fix would be to use signatures that are compliant with RFC 8017. If this is not possible, e.g. for compatibility reasons, this BouncyCastle solution can be used.
Alternatively, RSASSA-PKCS1-v1_5 can also be implemented on .NET 9 from the scratch, i.e. using only the native C# methods, as the logic is simple and .NET 9 supports all the required functionalities (including the import of PEM encoded keys and modular arithmetic).
A possible implementation is:
using System;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;
...
public static byte[] sign(byte[] hash, RSAParameters parameters)
{
BigInteger nBN = new BigInteger(parameters.Modulus, true, true);
BigInteger dBN = new BigInteger(parameters.D, true, true);
int keySize = ((int)nBN.GetBitLength() + 7) >> 3;
if (keySize < hash.Length + 11) throw new CryptographicException("Message too long");
byte[] hashPadded = padHash(hash, keySize);
byte[] signature = modExp(hashPadded, nBN, dBN).ToByteArray(true, true);
return signature.Length == keySize ? signature : padSignature(signature, keySize);
}
// Pad according to RFC8017, sec. 9.2 EMSA-PKCS1-v1_5, where T is to be replaced by H
private static byte[] padHash(byte[] hash, int keySize)
{
using BinaryWriter writer = new BinaryWriter(new MemoryStream());
writer.Write((byte)0);
writer.Write((byte)1);
writer.Write(Enumerable.Repeat((byte)0xFF, keySize - hash.Length - 3).ToArray());
writer.Write((byte)0);
writer.Write(hash);
return ((MemoryStream)writer.BaseStream).ToArray();
}
private static BigInteger modExp(byte[] hashPadded, BigInteger nBN, BigInteger dBN)
{
BigInteger hashPaddedBN = new BigInteger(hashPadded, true, true);
BigInteger signatureBN = BigInteger.ModPow(hashPaddedBN, dBN, nBN);
return signatureBN;
}
// Pad the signature to the key size
private static byte[] padSignature(byte[] signature, int keySize)
{
using BinaryWriter writer = new BinaryWriter(new MemoryStream());
writer.Write(Enumerable.Repeat((byte)0x00, keySize - signature.Length).ToArray());
writer.Write(signature);
return ((MemoryStream)writer.BaseStream).ToArray();
}
Use case:
// 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-----";
RSA rsa = RSA.Create();
rsa.ImportFromPem(privatePkcs1Pem);
RSAParameters rsaParameters = rsa.ExportParameters(true);
byte[] dataToSign = SHA256.HashData(Encoding.UTF8.GetBytes("Data to sign")); // 0xB4D508D432AD5DE819C3FFEB92E050B76320F17A96535600716B1374829F60EF
byte[] signature = sign(dataToSign, rsaParameters);
Console.WriteLine(Convert.ToHexString(signature)); // 858369BEC6682705974CDF767CB6441035D190073E1DB664C8E027C2C9CE0730F7E9B8A3065017DC6876A4A487684AE485892191942C871FD5BE6905110C7C3B