Openssl Microsoft CNG |如何将PEM编码的ECDSA私钥导入MS密钥存储提供程序

Openssl Microsoft CNG |如何将PEM编码的ECDSA私钥导入MS密钥存储提供程序,openssl,cryptography,pem,ecdsa,cng,Openssl,Cryptography,Pem,Ecdsa,Cng,我知道MS CNG private有这种格式- B crypt_ECCKEY_BLOB 字节X[cbKey]//大端字节。 字节Y[cbKey]//大端字节。 字节d[cbKey]//大端字节。 因此,尝试导入以下键字节- byte[] ec256PrivKB = { //Magic + CBLength 0x45, 0x43, 0x53, 0x31, 0x20, 0x00, 0x00, 0x00, //X 0xA7, 0xFB, 0xCD, 0x4D, 0x7E, 0x43, 0x6F, 0

我知道MS CNG private有这种格式-

B crypt_ECCKEY_BLOB
字节X[cbKey]//大端字节。
字节Y[cbKey]//大端字节。
字节d[cbKey]//大端字节。

因此,尝试导入以下键字节-

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
};
现在通话

ECDsaCng eCDsa = new ECDsaCng( CngKey.Import(ec256PrivKB, CngKeyBlobFormat.EccPrivateBlob,
                    CngProvider.MicrosoftSoftwareKeyStorageProvider));
它给出了
System.Security.Cryptography.cryptographyException:'请求的操作不受支持。
我不明白为什么它会给这个例外

另外,如何将base64编码的ecdsa私钥导入MS密钥存储提供程序,即假设我有以下ec私钥-

-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICWeuFHssg5i2vJlyMHPUb+DJnylxfbkR8KJPXfYw5ikoAoGCCqGSM49
AwEHoUQDQgAE7A4wVMLQ+orOZYcFv6mLNBbAWfffPwTTw4iroyQDcytYWT+frzl3
RiFXqC1niHgduYtGBZIbwq/48ooyL9HbkA==
-----END EC PRIVATE KEY-----
现在我如何将其导入CNG提供商

编辑-是否有任何方法来回报这个过程,即 我知道如何从
OpenSSL(PEM格式)
转换为
MS格式(原始格式)
但是如何做相反的意思呢
如何将PEM中的
MS
格式化ECDSA密钥转换为
OpenSSL
EC密钥。

您的密钥blob以BCRYPT_ECDSA_ustrong>PUBLIC\u P256\u MAGIC开始,但您需要BCRYPT_ECDSA_strong>PRIVATE\u P256\u MAGIC(将0x31更改为0x32)

由此我们可以假设您知道如何进行转换,但由于标题要求更一般地进行转换,因此我将继续。对公钥有一个直截了当的(如果有黑客的话)答案。私钥是不同的,但大多是直接的

如果我们从问题中取下PEM密钥,我们可以移除“PEM装甲”,得到一个base-64 blob。将这个base-64 blob转换成十六进制

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
如果我们打开第C.4节,我们会看到

ECPrivateKey ::= SEQUENCE {
  version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
  privateKey OCTET STRING,
  parameters [0] ECDomainParameters {{ SECGCurveNames }} OPTIONAL,
  publicKey [1] BIT STRING OPTIONAL
}
通过ASN.1()和BER/DER()规范,我们可以混合该十六进制转储和该结构:

// (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
执行a表示它是(毫不奇怪)secp256r1/NIST p-256椭圆曲线

位字符串的内容以
04
开头,这表示它是一个未压缩的EC点,剩下的前半部分是X坐标,后半部分是Y坐标。很好,这与私钥的八位字节字符串的宽度一致,这意味着该结构是有效的

您会注意到ASN.1结构说曲线标识符和公钥在技术上都是可选的。在实践中,它们被写下来,这是好的,因为我们将依靠它

将EC私钥blob转换为CNG blob
// 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();
}