I'm trying to convert some older CAPI code to use CNG, specifically with the goal of hydrating certificates with ephemeral private keys. (Not supported by CAPI, as I understand it.)
We use a certificate's private key (PK) to sign data. I expected the two implementations to produce compatible output, but to my surprise they don't.
// Hydrate a cert with PK. Make PK a file-based key so we can get the container and use CAPI.
// (Note, although .NET Framework 4.8, the LangVersion is 8.0 so I can use using.)
using var cert = new X509Certificate2(testCertData, "passwd", X509KeyStorageFlags.MachineKeySet);
// Some arbitrary data to sign.
byte[] data = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
byte[] signatureCsp, signatureCng;
// Sign with CAPI. This is what we're doing today.
// The cert PK's crypto provider doesn't support SHA256, so we build a different one.
var csp = (RSACryptoServiceProvider)cert.PrivateKey;
var cp = new CspParameters{
KeyContainerName = csp.CspKeyContainerInfo.KeyContainerName,
KeyNumber = csp.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2,
};
using (var rsaCsp = new RSACryptoServiceProvider(cp)) {
signatureCsp = rsaCsp.Sign(data);
}
// Sign with CNG. This is what I want to do.
using (var rsaCng = cert.GetRSAPrivateKey()) {
signatureCng = rsaCng.Sign(data);
}
// The signatures are different. In fact they're different lengths, 128 and 256 bytes respectively.
Console.WriteLine(Convert.ToBase64String(signatureCsp));
Console.WriteLine(Convert.ToBase64String(signatureCng));
// But maybe they're compatible? Let's see if code which verifies the first can verify the second.
using (var provider = new RSACryptoServiceProvider(cp)) {
var verifiedCsp = provider.Verify(data, signatureCsp);
var verifiedCng = provider.Verify(data, signatureCng);
Console.WriteLine("RSACryptoServiceProvider verified: {0}", verifiedCsp);
Console.WriteLine("RSACng signature verified: {0}", verifiedCng); // Nope. False.
}
The above uses the following extension methods, for uniformity, treating both cases as implementations of the abstract RSA base type and with the same crypto parameters.
public static byte[] Sign(this RSA rsa, byte[] buffer) {
return rsa.SignData(buffer, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
public static bool Verify(this RSA rsa, byte[] buffer, byte[] signature) {
return rsa.VerifyData(buffer, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
What do I need to do to sign data with the certificate's CNG key so that the output can be verified by current consumers of signatures produced by the CAPI key?