Question

How can I get the public/private keys from an ECC-based X509Certificate2's into CngKey's for use with ECDsaCng and ECDiffieHellmanCng?

I'm currently using RSA 2048 bit key pairs to sign/encrypt stuff. I'm doing this by pulling the certificates from the X509Store where they are securely stored with private keys marked as non-exportable. I would like to convert the current implementation to use ECDSA and ECDH so that I can use smaller key sizes for equivalent security.

I've successfully generated ECC certs using openssl:

  1. openssl ecparam -out private.pem -name prime256v1 -genkey
  2. openssl req -new -key private.pem -x509 -nodes -days 365 -out public.cer
  3. openssl pkcs12 -export -in public.cer -inkey private.pem -out export.pfx

I've successfully installed the above generated certs in to the cert store. I can retrieve them by thumbprint, but the crypto providers for the private and public keys throw "Algorithm not supported" exceptions. Instead, I understand I'm supposed to use ECDsaCng and ECDiffieHellmanCng to sign/encrypt. But these deal in CngKey's.

Bouncy Castle isn't an option because it requires the private keys to be exportable.

CLR Security will return me a CngKey pair via GetCngPrivateKey but it cannot be used with ECDsa because the key returned by CLRSecurity is an ECDH key. Furthermore CLR Security doesn't give me a way to get just the public key from an X509Certificate2 for signature verification (where I don't even have or need the private key of the signer).

Any ideas? I'm at my wits end... Any help would be much appreciated.

Was it helpful?

Solution

You need to create the CngKey from the public key of the certificate (certificate.PublicKey.EncodedKeyValue.RawData):

The CngKey contains 8 additional bytes, the first 4 bytes (so called magic) are used for the name of the curve used (For ECDsa: ECS1, ECS3 or ECS5, For ECDH: ECK1, ECK3, ECK5), the last 4 are the length of the key incl. padding (32, 48 or 66).

The first byte of the public key from the certificate is removed (as it is always 0x04 for ECDSA public key).

So for instance for ECDSA using P-256 curve and SHA-256 hash algorithm, you will get a public key of length 65 bytes. Discard the first byte, leaving 64 bytes, then prefix with 4 bytes for curve and 4 bytes for key length i.e.:

var keyData = certificate.PublicKey.EncodedKeyValue.RawData.Skip(1).ToArray();
var keySize = BitConverter.GetBytes(keyData.Length/2);
var magic = Encoding.ASCII.GetBytes("ECS1").Concat()

var eccPublicBlobValue = magic.Concat(keySize).Concat(keyData).ToArray();

Now you have the public key (72 bytes) to create the CngKey from:

var cngKey = CngKey.Import(eccPublicBlobValue, CngKeyBlobFormat.EccPublicBlob);

var ecdsaCng = new ECDsaCng(cngKey);

And you can verify the signature:

return ecdsaCng.VerifyData(encodedBytes, signature);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top