C#从文本文件中的公钥获取CngKey对象

pet*_*riq 2 .net c# cryptography

我有一个文件,其中有几个ECDSA SHA256公钥.该文件看起来像:

KEY_ID: 1
STATUS: VALID
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaq6djyzkpHdX7kt8DsSt6IuSoXjp
WVlLfnZPoLaGKc/2BSfYQuFIO2hfgueQINJN3ZdujYXfUJ7Who+XkcJqHQ==
-----END PUBLIC KEY-----

KEY_ID: 2
STATUS: VALID
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE+Y5mYZL/EEY9zGji+hrgGkeoyccK
D0/oBoSDALHc9+LXHKsxXiEV7/h6d6+fKRDb6Wtx5cMzXT9HyY+TjPeuTg==
-----END PUBLIC KEY-----

KEY_ID: 3
STATUS: VALID
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkvgJ6sc2MM0AAFUJbVOD/i34YJJ8
ineqTN+DMjpI5q7fQNPEv9y2z/ecPl8qPus8flS4iLOOxdwGoF1mU9lwfA==
-----END PUBLIC KEY-----
Run Code Online (Sandbox Code Playgroud)

如何为这些键中的一个(或全部)获取CngKey对象(或CngKey列表)?

我尝试过类似的东西

string plainTextKey = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaq6djyzkpHdX7kt8DsSt6IuSoXjpWVlLfnZPoLaGKc/2BSfYQuFIO2hfgueQINJN3ZdujYXfUJ7Who+XkcJqHQ==";
byte[] publicKeyBytes = Convert.FromBase64String(plainTextKey);
CngKey ret = CngKey.Import(publicKeyBytes, CngKeyBlobFormat.EccPublicBlob);
Run Code Online (Sandbox Code Playgroud)

但导入方法会因无效参数而抛出System.Security.Cryptography.CryptographicException.

bar*_*njs 9

EccPublicBlob映射到BCRYPT_ECCPUBLIC_BLOB格式类型,而不是X.509 SubjectPublicKeyInfo.

如果所有的密钥都在secp256r1/NIST P-256上,那么就有一种非常简单的hacky方法.

您可能已经注意到所有密钥都以MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE.我们很快就会明白为什么.

兑换

MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaq6djyzkpHdX7kt8DsSt6IuSoXjp
WVlLfnZPoLaGKc/2BSfYQuFIO2hfgueQINJN3ZdujYXfUJ7Who+XkcJqHQ==
Run Code Online (Sandbox Code Playgroud)

到字节(或者,这里,十六进制):

30 59 30 13 06 07 2A 86 48 CE 3D 02 01 06 08 2A 
86 48 CE 3D 03 01 07 03 42 00 04 6A AE 9D 8F 2C 
E4 A4 77 57 EE 4B 7C 0E C4 AD E8 8B 92 A1 78 E9 
59 59 4B 7E 76 4F A0 B6 86 29 CF F6 05 27 D8 42 
E1 48 3B 68 5F 82 E7 90 20 D2 4D DD 97 6E 8D 85 
DF 50 9E D6 86 8F 97 91 C2 6A 1D
Run Code Online (Sandbox Code Playgroud)

这是DER编码的X.509 SubjectPublicKeyInfo blob.

使用我们的DER-fu,我们看到了

// SubjectPublicKeyInfo
30 59  // SEQUENCE, 0x59 == 89 bytes of payload
   // AlgorithmIdentifier
   30 13  // SEQUENCE, 0x13 == 19 bytes of payload
      // AlgorithmIdentifier.algorithm
      06 07 2A 86 48 CE 3D 02 01  // OBJECT ID 1.2.840.10045.2.1 (id-ecPublicKey)
      // AlgorithmIdentifier.parameters
      06 08 2A 86 48 CE 3D 03 01 07 // OBJECT ID 1.2.840.10045.3.1.7 (secp256r1)
   // SubjectPublicKeyInfo.publicKey
   03 42 00  // BIT STRING, 0x42 == 66 (65) payload bytes, 0 unused bits
      // "the public key"
      04
      92F809EAC73630CD000055096D5383FE2DF860927C8A77AA4CDF83323A48E6AE
      DF40D3C4BFDCB6CFF79C3E5F2A3EEB3C7E54B888B38EC5DC06A05D6653D9707C
Run Code Online (Sandbox Code Playgroud)

由于算法标识符是id-ecPublicKey参数,因此是标识曲线的OID(在这种情况下,secp256r1/NIST P-256).并且"公钥"的格式为SEC 1 v2.0(2.3.4 Octet-String-to-Elliptic-Curve-Point Conversion).

最常见的编码是类型04,未压缩的密钥.(0x04后跟Qx填充到必要的长度,然后将Qy填充到必要的长度).

因此,对于在secp256r1上使用类型04编码的所有点,字节模式以

30 59 30 13 06 07 2A 86 48 CE 3D 02 01 06 08 2A 
86 48 CE 3D 03 01 07 03 42 00 04
Run Code Online (Sandbox Code Playgroud)

恰好与公共base64前缀对齐MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE.

什么CNG希望是[32比特标识符] [32位小端长度] [软垫QX] [软垫QY.

所以超级duper hacky版本是:

private static readonly byte[] s_secp256r1Prefix =
    Convert.FromBase64String("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE");

// For ECDH instead of ECDSA, change 0x53 to 0x4B.
private static readonly byte[] s_cngBlobPrefix = { 0x45, 0x43, 0x53, 0x31, 0x20, 0, 0, 0 };

private static CngKey ImportECDsa256PublicKey(string base64)
{
    byte[] subjectPublicKeyInfo = Convert.FromBase64String(base64);

    if (subjectPublicKeyInfo.Length != 91)
        throw new InvalidOperationException();

    byte[] prefix = s_secp256r1Prefix;

    if (!subjectPublicKeyInfo.Take(prefix.Length).SequenceEqual(prefix))
        throw new InvalidOperationException();

    byte[] cngBlob = new byte[s_cngBlobPrefix.Length + 64];
    Buffer.BlockCopy(s_cngBlobPrefix, 0, cngBlob, 0, s_cngBlobPrefix.Length);

    Buffer.BlockCopy(
        subjectPublicKeyInfo,
        s_secp256r1Prefix.Length,
        cngBlob,
        s_cngBlobPrefix.Length,
        64);

    return CngKey.Import(cngBlob, CngKeyBlobFormat.EccPublicBlob);
}
Run Code Online (Sandbox Code Playgroud)

要支持其他曲线,您需要将CNG blob的前4个字节更改为正确的"Magic"值,并将第5个字节更改为正确的长度.当然,不同的SubjectPublicKeyInfo前缀,64将不是公钥坐标长度(64 == 256/8*2).但所有这些都留给读者练习.

请参阅C#和PHP ECDH不相反.

  • 多么全面的答案啊。拍拍我的手。 (2认同)