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