4

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

Marty Neal
  • 8,741
  • 3
  • 33
  • 38

2 Answers2

3

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.

Peter Dettman
  • 3,867
  • 20
  • 34
  • Ding Ding Ding. This gave me the hint I needed. Running the following command `echo -n "Data To Sign" | openssl dgst -sha1 -sign my.pem | openssl base64` gives the same signature as my C# code. YAAY. Now I just need to figure out how to tell the C# code NOT to digest the message before signing it. – Marty Neal Jan 22 '13 at 16:40
  • Well, as far as BC goes, does just switching to SignerUtilites.GetSigner("RSA") work? For RSACryptoServiceProvider, probably you need to use SignHash, although that method appears to require that the data in question was actually created through some named hash... – Peter Dettman Jan 23 '13 at 08:38
  • Yes again, GetSigner("RSA") does the trick to match openssl with BC. I get an exception with RSACsp's SignHash. For now I'll just live with the dependency on BC, and come back to it later. Thanks for all the help! – Marty Neal Jan 24 '13 at 16:44
1

RSA signing can use different padding modes (PKCS#1, PSS, etc). With the last one it uses some random salt, so the signature will be different each time. So, at first check if openssl generates the same value each time (I did not found what default padding openssl code uses, you can change it with -rsa_padding_mode parameter), and, actually, if both signatures can be correctly verified by the other side.

Nickolay Olshevsky
  • 13,706
  • 1
  • 34
  • 48