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

时间:2017-06-12 14:47:09

标签: c# .net 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-----

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

我尝试了类似

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

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

2 个答案:

答案 0 :(得分:6)

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

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

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

转换

MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaq6djyzkpHdX7kt8DsSt6IuSoXjp
WVlLfnZPoLaGKc/2BSfYQuFIO2hfgueQINJN3ZdujYXfUJ7Who+XkcJqHQ==

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

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前缀对齐。

CNG wants是什么[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# and PHP ECDH not matching

答案 1 :(得分:0)

我已经解析了这样的文件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<Key> keys = new List<Key>();

        public int id { get; set; }
        public Boolean status { get; set; }
        List<string> keysStrs = new List<string>();

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

            }
        }
    }
}