我有一个文件,其中有几个公共密钥用于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。
答案 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)。但所有这些都留给了读者。
答案 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;
}
}
}
}
}
}