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

我有一个文件,其中有几个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----- 

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

我尝试过类似的东西

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

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

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

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

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

兑换

 MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaq6djyzkpHdX7kt8DsSt6IuSoXjp WVlLfnZPoLaGKc/2BSfYQuFIO2hfgueQINJN3ZdujYXfUJ7Who+XkcJqHQ== 

到字节(或者,这里,hex):

 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 

这是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 

由于算法标识符是id-ecPublicKey因此参数是标识曲线的OID(在这种情况下,secp256r1 / NIST P-256)。 并且“公钥”的格式来自SEC 1 v2.0 (2.3.4八位字节 – 字符串 – 椭圆 – 曲线点转换)。

最常见的编码是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 

这恰好与MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE的公共base64前缀MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE

CNG想要的是[32位标识符] [32位小端长度] [填充Qx] [填充Qy]。

所以超级hapery版本是:

 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); } 

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

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

我已经解析了这样的文件40年了。 像这样的代码有很长的历史

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; namespace ConsoleApplication62 { enum State { FIND_KEY, GET_STATUS, GET_KEY_STRINGS } class Program { const string FILENAME = @"c:\temp\test.txt"; static void Main(string[] args) { new Key(FILENAME); } } public class Key { public static List keys = new List(); public int id { get; set; } public Boolean status { get; set; } List keysStrs = new List(); public Key() { } public Key(string filename) { StreamReader reader = new StreamReader(filename); string inputLine = ""; State state = State.FIND_KEY; Key newKey = null; while((inputLine = reader.ReadLine()) != null) { inputLine = inputLine.Trim(); if(inputLine.Length > 0) { switch (state) { case State.FIND_KEY : if(inputLine.StartsWith("KEY_ID:")) { newKey = new Key(); keys.Add(newKey); int id = int.Parse(inputLine.Substring(inputLine.LastIndexOf(" "))); newKey.id = id; state = State.GET_STATUS; } break; case State.GET_STATUS: if (inputLine.StartsWith("STATUS:")) { string status = inputLine.Substring(inputLine.LastIndexOf(" ")).Trim(); newKey.status = status == "VALID" ? true : false; state = State.GET_KEY_STRINGS; } break; case State.GET_KEY_STRINGS: if (!inputLine.StartsWith("-")) { newKey.keysStrs.Add(inputLine.Trim()); } else { if (inputLine.Contains("END PUBLIC KEY")) { state = State.FIND_KEY; } } break; } } } } } }