I know the MS CNG private have this format -
BCRYPT_ECCKEY_BLOB
BYTE X[cbKey] // Big-endian.
BYTE Y[cbKey] // Big-endian.
BYTE d[cbKey] // Big-endian.
Thus tried to import below key bytes -
byte[] ec256PrivKB =
{
//Magic + CBLength
0x45, 0x43, 0x53, 0x31, 0x20, 0x00, 0x00, 0x00,
//X
0xA7, 0xFB, 0xCD, 0x4D, 0x7E, 0x43, 0x6F, 0x22, 0xBD, 0x74, 0xFA, 0x1F, 0xD7, 0x10, 0xDB, 0x8C, 0xF8, 0x29, 0xC1, 0xEC, 0x5E, 0x15, 0x1E, 0xE2, 0x84, 0x56, 0x3E, 0x54, 0x6E, 0x1D, 0x5C, 0xF6,
//Y
0x6B, 0x42, 0x21, 0xD1, 0x92, 0xEB, 0x69, 0x66, 0x56, 0xD6, 0xEC, 0x4D, 0x21, 0xB7, 0xDB, 0x3C, 0x94, 0x56, 0x8D, 0x87, 0xEB, 0x1C, 0x11, 0x0F, 0x03, 0x80, 0xF6, 0x10, 0x70, 0x73, 0x7D, 0x1D,
//D
0x5E, 0xF0, 0x2A, 0x1B, 0x34, 0xE9, 0x2B, 0x96, 0xA4, 0xAE, 0x05, 0x1D, 0x33, 0x53, 0x36, 0x39, 0x7B, 0x1F, 0xF5, 0x24, 0xA4, 0xD6, 0xBD, 0x12, 0x07, 0x3F, 0x43, 0x30, 0x70, 0x32, 0x4E, 0x5D
};
Now on calling
ECDsaCng eCDsa = new ECDsaCng( CngKey.Import(ec256PrivKB, CngKeyBlobFormat.EccPrivateBlob,
CngProvider.MicrosoftSoftwareKeyStorageProvider));
It gives System.Security.Cryptography.CryptographicException: 'The requested operation is not supported.
I don't understand why it is giving this exception ?
Also how to import base64 encoded ecdsa private key into MS key storage provider i.e suppose i have below ec private key -
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICWeuFHssg5i2vJlyMHPUb+DJnylxfbkR8KJPXfYw5ikoAoGCCqGSM49
AwEHoUQDQgAE7A4wVMLQ+orOZYcFv6mLNBbAWfffPwTTw4iroyQDcytYWT+frzl3
RiFXqC1niHgduYtGBZIbwq/48ooyL9HbkA==
-----END EC PRIVATE KEY-----
Now how do i import this into CNG provider ?
Edit - Also is there any way to reciprocate this process i.e
I know how to convert from OpenSSL(PEM FORMAT)
to MS Format(RAW format)
but how to do the reverse meaning
how to convert MS
Format ECDSA Key to OpenSSL
EC Key in PEM.
Your key blob starts with BCRYPT_ECDSA_PUBLIC_P256_MAGIC, but you want BCRYPT_ECDSA_PRIVATE_P256_MAGIC (change the 0x31 to 0x32).
From that we could assume that you know how to do the conversion, but since the title asks how to do it more generally I'll continue. C# Get CngKey object from public key in text file has a straightforward (if hacky) answer to public keys. Private keys are different, but mostly straightforward.
If we take the PEM key from the question we can remove the "PEM armor" to get a base-64 blob. Turning that base-64 blob into hexadecimal we get
30 77 02 01 01 04 20 25 9E B8 51 EC B2 0E 62 DA
F2 65 C8 C1 CF 51 BF 83 26 7C A5 C5 F6 E4 47 C2
89 3D 77 D8 C3 98 A4 A0 0A 06 08 2A 86 48 CE 3D
03 01 07 A1 44 03 42 00 04 EC 0E 30 54 C2 D0 FA
8A CE 65 87 05 BF A9 8B 34 16 C0 59 F7 DF 3F 04
D3 C3 88 AB A3 24 03 73 2B 58 59 3F 9F AF 39 77
46 21 57 A8 2D 67 88 78 1D B9 8B 46 05 92 1B C2
AF F8 F2 8A 32 2F D1 DB 90
If we then open https://www.secg.org/sec1-v2.pdf section C.4 we see
ECPrivateKey ::= SEQUENCE {
version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
privateKey OCTET STRING,
parameters [0] ECDomainParameters {{ SECGCurveNames }} OPTIONAL,
publicKey [1] BIT STRING OPTIONAL
}
Via the ASN.1 (ITU-T X.680) and BER/DER (ITU-T X.690) specifications we can blend that hex dump and that structure:
// (constructed) SEQUENCE, 119 content bytes
30 77
// INTEGER, 1 content byte, value 0x01.
02 01 01
// OCTET STRING (byte[]), 32 bytes, value 25 9E ... 98 A4
04 20
25 9E B8 51 EC B2 0E 62 DA F2 65 C8 C1 CF 51 BF
83 26 7C A5 C5 F6 E4 47 C2 89 3D 77 D8 C3 98 A4
// (constructed) CONTEXT-SPECIFIC 0, 10 content bytes
A0 0A
// OBJECT IDENTIFIER, 8 content bytes, 1.2.840.10045.3.1.7
06 08 2A 86 48 CE 3D 03 01 07
// (constructed) CONTEXT-SPECIFIC 1, 68 content bytes
A1 44
// BIT STRING, 66 content bytes, 0 unused bits, value 04 EC .. DB 90
03 42
00
04 EC 0E 30 54 C2 D0 FA 8A CE 65 87 05 BF A9 8B
34 16 C0 59 F7 DF 3F 04 D3 C3 88 AB A3 24 03 73
2B 58 59 3F 9F AF 39 77 46 21 57 A8 2D 67 88 78
1D B9 8B 46 05 92 1B C2 AF F8 F2 8A 32 2F D1 DB
90
Doing a search for 1.2.840.10045.3.1.7 indicates that it is (unsurprisingly) the secp256r1 / NIST P-256 elliptic curve.
The contents of the BIT STRING start with 04
, which indicates that it is an uncompressed EC point, the first half of what remains is the X coordinate, and the second half is the Y coordinate. Nicely, this lines up with the width of the OCTET STRING for the private key, meaning the structure is valid.
You'll note that the ASN.1 structure said that the curve identifier and the public key were technically both optional. In practice they are written down, which is good, since we're going to count on it.
// structure opening up to the private key (D) value.
private static readonly byte[] s_secp256R1Prefix =
{ 0x30, 0x77, 0x02, 0x01, 0x01, 0x04, 0x20 };
// After D through the 0x04 identifying the public key is uncompressed
private static readonly byte[] s_secp256R1Infix =
{
0xA0, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE,
0x3D, 0x03, 0x01, 0x07, 0xA1, 0x44, 0x03, 0x42,
0x00, 0x04
};
private static readonly byte[] s_secp256R1PrivateCngPrefix =
{ 0x45, 0x43, 0x53, 0x32, 0x20, 0x00, 0x00, 0x00 };
private static CngKey DecodeP256ECPrivateKeyBase64(string base64)
{
byte[] derBlob = Convert.FromBase64String(base64);
if (derBlob.Length == 121 &&
derBlob.Take(s_secp256R1Prefix.Length).SequenceEqual(s_secp256R1Prefix) &&
derBlob.Skip(0x20 + s_secp256R1Prefix.Length).Take(s_secp256R1Infix.Length).
SequenceEqual(s_secp256R1Infix))
{
byte[] cngBlob = new byte[2 * sizeof(uint) + 3 * 0x20];
int offset = 0;
// Header (BCRYPT_ECDSA_PRIVATE_P256_MAGIC and 0x00000020)
Buffer.BlockCopy(
s_secp256R1PrivateCngPrefix,
0,
cngBlob,
offset,
s_secp256R1PrivateCngPrefix.Length);
offset += s_secp256R1PrivateCngPrefix.Length;
// X and Y
Buffer.BlockCopy(
derBlob,
s_secp256R1Prefix.Length + 0x20 + s_secp256R1Infix.Length,
cngBlob,
offset,
2 * 0x20);
offset += 2 * 0x20;
Buffer.BlockCopy(
derBlob,
s_secp256R1Prefix.Length,
cngBlob,
offset,
0x20);
offset += 0x20;
Debug.Assert(offset == cngBlob.Length);
return CngKey.Import(cngBlob, CngKeyBlobFormat.EccPrivateBlob);
}
throw new InvalidOperationException();
}
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