Search code examples
c#opensslbouncycastle

Can't get Openssl signed data to match .NET signed data or BouncyCastle signed Data


I was at this all last Friday and am still stumped.

When I run this command:

echo -n "Data To Sign" | openssl pkeyutl -sign -inkey path/to/my.pem | openssl base64

through openssl I get the output:

1c284UkFgC6pqyJ+woSU+DiWB4MabWVDVhhUbBrTtF7CkpG8MjY+KkPFsZm9ZNM8vCjZjf...Kw=

I want to replicate this behavior in C#. The first attempt I made was using openssl to create a .p12 file from my .pem file. I did this by running the two following openssl commands:

openssl req -new -key path/to/my.pem -x509 -batch > my.crt
openssl pkcs12 -export -in my.crt -inkey path/to/my.pem -out my.p12

and then use the X509Certificate2 and RSACryptoServiceProvider classes to load the .p12 and sign. Code Below:

var dataToSign = "Data To Sign";
var p12 = new X509Certificate2(@"path\to\my.p12","");
var rsa = (RSACryptoServiceProvider)p12.PrivateKey;
var signedBytes = rsa.SignData(Encoding.UTF8.GetBytes(dataToSign), "SHA1");
var result = Convert.ToBase64String(signedBytes);

which yields:

B2qM6MTjoZFSbnckezzpXrKFq67vFgsCPYBmaAbKOFmzVQLIU4a+GC6LWTMdNO4...Q0=

Unfortunately, the outputs don't match. After fighting with this for a while, I decided to go the BouncyCastle route suggested by several answers here on SO. Here's the code I came up using that library:

StreamReader sr = new StreamReader(@"path\to\my.pem");
PemReader pr = new PemReader(sr);
var pemKeyParams = (RsaPrivateCrtKeyParameters)pr.ReadObject();
var cypherParams = new RsaKeyParameters(true, pemKeyParams.Modulus, pemKeyParams.Exponent);
ISigner sig = SignerUtilities.GetSigner("SHA1withRSA");
sig.Init(true, cypherParams);
var bytes = Encoding.UTF8.GetBytes("Data To Sign");
sig.BlockUpdate(bytes, 0, bytes.Length);
byte[] signature = sig.GenerateSignature();
var result = Convert.ToBase64String(signature);

which also yields:

B2qM6MTjoZFSbnckezzpXrKFq67vFgsCPYBmaAbKOFmzVQLIU4a+GC6LWTMdNO4...Q0=

The BouncyCastle output matches the output given by the C# code that uses the native libraries in the Security namespace, but I want to match openssl's output. What am I doing wrong?

Versions -
OpenSSL 1.0.1c
.NET 4.0
BouncyCastle 1.7.0
Windows 7

my.pem -

-----BEGIN PRIVATE KEY----- MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOtk3bYdQsjeG1Xy 2KgF8ecWcPudPLEnV32OIbtA+h2hXQ853ZRsxusopm7vmqtI2/aVfc2vyw9AGY0U cjqPnyEq7et5oQydo5+aTEW3PenP9DR3MJ273ipPbrYX+I3XzJ+I6//k6DO/OAIA JLlXc9iT1pblSrHymFNEkIFiUgj3AgMBAAECgYBtP1Lmwo3MS8jECwEieh/a8D9f h4ozbd7dFqnxDicGuW1HM8PyrsljOmqD8hAGjroHpznLzFqhqU4ye9rH8wAWsKUj Qst/RjyDU3SNscyU/eg+ezuawUXafpPUEUTJ0aofdHn9GIVipiIi/4uaPP/IYtuC U2smep4C2+geqfTugQJBAP5MTaRQjoYBGKS/Bgd0JB16MHFV6FPDCX3NZ2CLTyZm o8edQZI4SbWoxkJaGqBOqDbz/dSmTLfRNmpAmC+az5sCQQDs+CyDLbs3URvD7ajx JjsJoPbuVmqPBPGmAy/4Qt3QVp9AWk+9uckU90DYMqJp5bdGoeokmA65uuEcvqbs yzfVAkEA018FIlE7RjNfEoEdN9DXvBC2d14a0JTLLOAwz1S8I4UpGWCjAjD7Q53X vYs7mogG1jaUg87+8cNaYZLzbI5XhQJANyqbajqGQB2Awj8cum81BUvU0K2LhxoW i5hoXXprmynfTyL3N2r99gSNswcuqkqRPT9KfBRuMSzhZUi5IZ05tQJBAKdQ3mJZ 1Vys2nEAXbQD5/ldi1+VF/0t4Z+JxqFBjqtsAoASBN+kSiPAnRl3r175oZ9m9gkd 5YISN0L+WD5Bf4U= -----END PRIVATE KEY-----

For the purposes of this question, I generated a new .pem, The above key does not secure anything important, but I provide it for reproducing my steps above.

Things I've considered so far:
* Encoding (ASCII vs UTF8)
* Endianess (-rev to the openssl still doesn't match either coded result)
* hashalg (I couldn't find anywhere that "SHA1" was officially the correct hash algorithm to use, but it seems to be what others were using and trying other options didn't help)
* newlines (I believe the -n on echo is required to be on par with the libraries, but removing it didn't help)
* Asking on SO (results pending :-) )

TIA


Solution

  • I would guess the openssl pkeyutl command is actually signing the data directly, instead of signing a digest of the data.

    The openssl docs on pkeyutl includes this suggestive comment:

    "Unless otherwise mentioned all algorithms support the digest:alg option which specifies the digest in use for sign, verify and verifyrecover operations. The value alg should represent a digest name as used in the EVP_get_digestbyname() function for example sha1."

    I don't see a digest option on your command line. Further, from the same docs (in section on RSA):

    "In PKCS#1 padding if the message digest is not set then the supplied data is signed or verified directly instead of using a DigestInfo structure. If a digest is set then the a DigestInfo structure is used and its the length must correspond to the digest type."

    See also the docs for openssl dgst.