c++ - 如何将 PEM 格式的公钥转换为 CNG 密钥 blob?
问题描述
我需要使用 Windows 的Cryptography API: Next Generation来验证消息的签名。我有消息、它的签名和 PEM 格式的公钥,以及展示所需行为的原型实现。
我需要修复的代码有一个问题:将公钥转换为 CNG 使用的密钥 blob。
目前,我正在解码公钥
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETHfi8foQF4UtSNVxSFxeu7W+gMxd
SGElhdo7825SD3Lyb+Sqh4G6Kra0ro1BdrM6Qx+hsUx4Qwdby7QY0pzxyA==
-----END PUBLIC KEY-----
using CryptStringToBinaryA
,它产生以下二进制数据:
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 4C 77 E2 F1 FA
10 17 85 2D 48 D5 71 48 5C 5E BB B5 BE 80 CC 5D
48 61 25 85 DA 3B F3 6E 52 0F 72 F2 6F E4 AA 87
81 BA 2A B6 B4 AE 8D 41 76 B3 3A 43 1F A1 B1 4C
78 43 07 5B CB B4 18 D2 9C F1 C8
一切看起来都很好。据推测,这是 DER 格式,使用 ASN.1。它的大小为 91 个字节,指定椭圆曲线加密 ( ECDSA prime256v1
),其中[x]
和[y]
条目从偏移量 27 开始(每个总共 32 个字节)。
因为我需要一个密钥块来传递到BCryptImportKeyPair,特别是BCRYPT_ECCKEY_BLOB
:
typedef struct _BCRYPT_ECCKEY_BLOB
{
ULONG dwMagic;
ULONG cbKey;
} BCRYPT_ECCKEY_BLOB, *PBCRYPT_ECCKEY_BLOB;
BCRYPT_ECDSA_PUBLIC_P256_MAGIC
我的原型通过提供32 和 32 的长度,然后是上面公钥中的相应 64 个字节来创建一个 ad-hoc 。
现在可行,但我显然想要实现更强大的东西。该密钥似乎对填充适当的 CNG 密钥 blob 所需的所有数据进行编码,但我找不到任何可以帮助我解析此信息的 API。
CNG(或Wincrypt)是否提供任何允许我构建 CNG 密钥 blob 或以其他方式获取BCRYPT_KEY_HANDLE
PEM 密钥的功能?还是我要实现自己的 ASN.1 解析器?
解决方案
用于将 PEM 公钥转换为 CNG - 接下来是通用步骤:
CryptStringToBinaryA
用于将字符串转换为二进制CryptDecodeObjectEx
withX509_PUBLIC_KEY_INFO
- 将二进制转换为CERT_PUBLIC_KEY_INFO
CryptImportPublicKeyInfoEx2
- 进口CERT_PUBLIC_KEY_INFO
到CNG
代码示例
inline ULONG BOOL_TO_ERROR(BOOL f)
{
return f ? NOERROR : GetLastError();
}
ULONG PemToCNG(_In_ PCSTR pszString, _Out_ BCRYPT_KEY_HANDLE* phKey)
{
PBYTE pb = 0;
ULONG cb = 0;
ULONG dwError;
while (NOERROR == (dwError = BOOL_TO_ERROR(
CryptStringToBinaryA(pszString, 0, CRYPT_STRING_BASE64_ANY, pb, &cb, 0, 0))))
{
if (pb)
{
PCERT_PUBLIC_KEY_INFO PublicKeyInfo;
if (NOERROR == (dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
X509_PUBLIC_KEY_INFO, pb, cb,
CRYPT_DECODE_ALLOC_FLAG|
CRYPT_DECODE_NOCOPY_FLAG|
CRYPT_DECODE_SHARE_OID_STRING_FLAG,
0, &PublicKeyInfo, &cb))))
{
dwError = BOOL_TO_ERROR(CryptImportPublicKeyInfoEx2(
X509_ASN_ENCODING, PublicKeyInfo, 0, 0, phKey));
LocalFree(PublicKeyInfo);
}
break;
}
if (!(pb = (PBYTE)LocalAlloc(0, cb)))
{
dwError = GetLastError();
break;
}
}
if (pb)
{
LocalFree(pb);
}
return dwError;
}
void TestPem()
{
static const char Pem[] =
"-----BEGIN PUBLIC KEY-----\r\n"
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETHfi8foQF4UtSNVxSFxeu7W+gMxd\r\n"
"SGElhdo7825SD3Lyb+Sqh4G6Kra0ro1BdrM6Qx+hsUx4Qwdby7QY0pzxyA==\r\n"
"-----END PUBLIC KEY-----";
BCRYPT_KEY_HANDLE hKey;
if (NOERROR == PemToCNG(Pem, &hKey))
{
PBYTE pb = 0;
ULONG cb = 0;
while (0 <= BCryptExportKey(hKey, 0, BCRYPT_PUBLIC_KEY_BLOB, pb, cb, &cb, 0))
{
if (pb)
{
PSTR psz = 0;
ULONG cch = 0;
while (CryptBinaryToStringA(pb, cb, CRYPT_STRING_HEXASCII, psz, &cch))
{
if (psz)
{
DbgPrint(psz);
break;
}
psz = (PSTR)alloca(cch * sizeof(char));
}
break;
}
pb = (PBYTE)alloca(cb);
}
BCryptDestroyKey(hKey);
}
}
输出是
45 43 53 31 20 00 00 00 4c 77 e2 f1 fa 10 17 85 ECS1 ...Lw......
2d 48 d5 71 48 5c 5e bb b5 be 80 cc 5d 48 61 25 -H.qH\^.....]Ha
85 da 3b f3 6e 52 0f 72 f2 6f e4 aa 87 81 ba 2a ..;.nR.r.o.....*
b6 b4 ae 8d 41 76 b3 3a 43 1f a1 b1 4c 78 43 07 ....Av.:C...LxC.
5b cb b4 18 d2 9c f1 c8 [.......
推荐阅读
- javascript - Firebase 可调用云函数返回未定义
- javascript - 如何在字符串中找到特定单词并添加类(Javascript)
- kotlin - 如何通过 ktor-client 发送带参数的 Http Form
- java - 锚点之间的距离
- flutter - 如何找到放置 vscode 扩展的确切位置?
- c# - 访问 PresentationFramework 和 WindowsBase 内部类
- php - Chilkat php imap Connect永远挂起
- java - JPOS Restful API 发送和接收数据
- firebase - 如何提取数组的内容?
- codeigniter - 在 codeigniter 视图页面中,数据未从数据库中显示