I am looking for a cross platform way to share public keys for ECDSA signing. I had a great thing going from a performance perspective with CngKey and the standard .NET crypto libraries, but then I couldn't figure out how a 33 (or 65) byte public key (using secp256r1/P256) was getting turned into 104 bytes by MS.. Ergo, I couldn't support cross platform signing and verifying..
I'm using BouncyCastle now, but holy handgranade is it SLOW!
So, looking for suggestions for the following requirements:
The client has to be able to sign the message, the server has to be able to validate the signature with a public key that was exchanged at registration to the service.
Anyways, Ideas would be awesome... Thanks
So I have figured out the format of a CngKey exported in ECCPublicKeyBlob and ECCPrivateKeyBlob. This should allow others to interop between other key formats and CngKey for Elliptcal Curve signing and such.
ECCPrivateKeyBlob is formatted (for P256) as follows
ECCPublicKeyBlob is formatted (for P256) as follows
So given a uncompressed Public key in Hex from another language, you can trim the first byte, add those 8 bytes to the front and import it using
CngKey.Import(key,CngKeyBlobFormat.EccPrivateBlob);
Note: The key blob format is documented by Microsoft.
The KEY TYPE and KEY LENGTH are defined in BCRYPT_ECCKEY_BLOB struct as:
{ ulong Magic; ulong cbKey; }
ECC public key memory format:
BCRYPT_ECCKEY_BLOB
BYTE X[cbKey] // Big-endian.
BYTE Y[cbKey] // Big-endian.
ECC private key memory format:
BCRYPT_ECCKEY_BLOB
BYTE X[cbKey] // Big-endian.
BYTE Y[cbKey] // Big-endian.
BYTE d[cbKey] // Big-endian.
The MAGIC values available in .NET are in Microsoft's official GitHub dotnet/corefx BCrypt/Interop.Blobs.
internal enum KeyBlobMagicNumber : int
{
BCRYPT_ECDH_PUBLIC_P256_MAGIC = 0x314B4345,
BCRYPT_ECDH_PRIVATE_P256_MAGIC = 0x324B4345,
BCRYPT_ECDH_PUBLIC_P384_MAGIC = 0x334B4345,
BCRYPT_ECDH_PRIVATE_P384_MAGIC = 0x344B4345,
BCRYPT_ECDH_PUBLIC_P521_MAGIC = 0x354B4345,
BCRYPT_ECDH_PRIVATE_P521_MAGIC = 0x364B4345,
BCRYPT_ECDSA_PUBLIC_P256_MAGIC = 0x31534345,
BCRYPT_ECDSA_PRIVATE_P256_MAGIC = 0x32534345,
BCRYPT_ECDSA_PUBLIC_P384_MAGIC = 0x33534345,
BCRYPT_ECDSA_PRIVATE_P384_MAGIC = 0x34534345
BCRYPT_ECDSA_PUBLIC_P521_MAGIC = 0x35534345,
BCRYPT_ECDSA_PRIVATE_P521_MAGIC = 0x36534345,
...
...
}
Thanks to you I was able to import a ECDSA_P256 public key from a certificate with this code:
private static CngKey ImportCngKeyFromCertificate(X509Certificate2 cert)
{
var keyType = new byte[] {0x45, 0x43, 0x53, 0x31};
var keyLength = new byte[] {0x20, 0x00, 0x00, 0x00};
var key = cert.PublicKey.EncodedKeyValue.RawData.Skip(1);
var keyImport = keyType.Concat(keyLength).Concat(key).ToArray();
var cngKey = CngKey.Import(keyImport, CngKeyBlobFormat.EccPublicBlob);
return cngKey;
}
The 65 byte keys (public key only) start with 0x04
which needs to be removed. Then the header you described is added.
then I was able to verify a signature like that:
var crypto = ECDsaCng(cngKey);
var verify = crypto.VerifyHash(hash, sig);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With