导入 WebCrypto 生成的 ECDSA public 密钥用于验证操作
Import WebCrypto generated ECDSA public key for verify operations
我正在尝试使用 OpenSSL 在 C++ 中导入 ECDSA public 密钥(用于验证签名),但是 d2i_ECPKParameters return NULL.
使用 Web Cryptographi 生成的密钥 API; public 密钥以 spki 格式导出(W3 TR 文档在导出密钥时谈论 ASN.1 结构,以及 spki 的 DER 编码)。
我是 OpenSSL 的新手,我做错了什么?
导入:
bool ecdsa_verify(
const std::array<uint8_t, 20>& hash,
const std::experimental::basic_string_view<uint8_t>& signature,
const std::experimental::basic_string_view<uint8_t>& public_key) {
EC_GROUP* ec_group = nullptr;
const unsigned char* public_key_data = public_key.data();
ec_group = d2i_ECPKParameters(nullptr, &public_key_data, public_key.length());
if (ec_group == nullptr) {
return false; // RETURN POINT
}
EC_KEY* ec_key = EC_KEY_new();
if (ec_key == nullptr) {
EC_GROUP_free(ec_group);
return false;
}
if (!EC_KEY_set_group(ec_key, ec_group)) {
EC_GROUP_free(ec_group);
EC_KEY_free(ec_key);
return false;
}
bool is_signature_valid =
ECDSA_verify(0, hash.data(), hash.size(), signature.data(),
signature.length(), ec_key);
EC_GROUP_free(ec_group);
EC_KEY_free(ec_key);
return is_signature_valid;
}
更新:
尝试其他导入(但还是不行):
const unsigned char* public_key_data = public_key.data();
EC_KEY* ec_key =
o2i_ECPublicKey(nullptr, &public_key_data, public_key.length());
if (ec_key == nullptr) {
return false; // RETURN POINT
}
bool is_signature_valid =
ECDSA_verify(0, hash.data(), hash.size(), signature.data(),
signature.length(), ec_key);
EC_KEY_free(ec_key);
导出:
function ecdsa_export_pub_key(key) {
return window.crypto.subtle.exportKey(
"spki",
key);
}
更新 2:
我生成了 PEM 密钥(从 JS 中导出的密钥),但主要的是,我不使用 PEM 密钥。在 JavaScript 中导出 public 键后,我从 ArrayBuffer 创建了一个新的 Uint8Array,通过 WebSocket(二进制帧)将其发送到服务器,并尝试解析它。收到的 uint8_t 数组总是 158 字节长度。我使用 P-521 -- secp521r1.
导出的私钥在pkcs8!
-----BEGIN PRIVATE KEY-----
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIApK1m/qpIAZ1iENht
XJxng4bdur6YV2SpMs+uFtSiJ/n96HbjVkqSENavv7vblIow+i5QUhaOkqSNWi0B
7x695C6hgYkDgYYABAATsbs5B+ebSwoIXD6RD2NYONzSWOtt0SigPM27pdYEWpld
/6j6S34gvRHQwDSMzs6//1zVE20Mn+izNM0KPWhRewD6SotR8/2QGWB5uo8GiXx1
RLyBp+TOurQLEsYwiWSLkUIUMvPH/6WCxSNO4FzBf617PRqs7Zv3Vo98d9JH/3mI
TA==
-----END PRIVATE KEY-----
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAE7G7OQfnm0sKCFw+kQ9jWDjc0ljr
bdEooDzNu6XWBFqZXf+o+kt+IL0R0MA0jM7Ov/9c1RNtDJ/oszTNCj1oUXsA+kqL
UfP9kBlgebqPBol8dUS8gafkzrq0CxLGMIlki5FCFDLzx/+lgsUjTuBcwX+tez0a
rO2b91aPfHfSR/95iEw=
-----END PUBLIC KEY-----
一些细节:
% openssl asn1parse -inform PEM -in pub.pem
0:d=0 hl=3 l= 155 cons: SEQUENCE
3:d=1 hl=2 l= 16 cons: SEQUENCE
5:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey
14:d=2 hl=2 l= 5 prim: OBJECT :secp521r1
21:d=1 hl=3 l= 134 prim: BIT STRING
% openssl asn1parse -inform PEM -in priv.pem
0:d=0 hl=3 l= 238 cons: SEQUENCE
3:d=1 hl=2 l= 1 prim: INTEGER :00
6:d=1 hl=2 l= 16 cons: SEQUENCE
8:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey
17:d=2 hl=2 l= 5 prim: OBJECT :secp521r1
24:d=1 hl=3 l= 214 prim: OCTET STRING [HEX DUMP]:3081D3020101044200A4AD66FEAA48019D6210D86D5C9C678386DDBABE985764A932CFAE16D4A227F9FDE876E3564A9210D6AFBFBBDB948A30FA2E5052168E92A48D5A2D01EF1EBDE42EA1818903818600040013B1BB3907E79B4B0A085C3E910F635838DCD258EB6DD128A03CCDBBA5D6045A995DFFA8FA4B7E20BD11D0C0348CCECEBFFF5CD5136D0C9FE8B334CD0A3D68517B00FA4A8B51F3FD90196079BA8F06897C7544BC81A7E4CEBAB40B12C63089648B91421432F3C7FFA582C5234EE05CC17FAD7B3D1AACED9BF7568F7C77D247FF79884C
调用时的错误代码o2i_ECPublicKey:
(使用相同的数据调用,但每次错误都不同——无论如何都会重复。)
error:10067066:elliptic curve routines:ec_GFp_simple_oct2point:invalid
error:10098010:elliptic curve routines:o2i_ECPublicKey:EC lib
error:0D07207B:asn1 encoding routines:ASN1_get_object:header too long
error:10067066:elliptic curve routines:ec_GFp_simple_oct2point:invalid
error:10098010:elliptic curve routines:o2i_ECPublicKey:EC lib
更新 3:
从 C++ 中,我将接收到的数据(密钥)写到一个文件中:
% % openssl asn1parse -inform DER -in data.bin
0:d=0 hl=3 l= 155 cons: SEQUENCE
3:d=1 hl=2 l= 16 cons: SEQUENCE
5:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey
14:d=2 hl=2 l= 5 prim: OBJECT :secp521r1
21:d=1 hl=3 l= 134 prim: BIT STRING
%
% hexdump data.bin
0000000 8130 309b 0610 2a07 4886 3dce 0102 0506
0000010 812b 0004 0323 8681 0400 1300 bbb1 0739
0000020 9be7 0a4b 5c08 913e 630f 3858 d2dc eb58
0000030 d16d a028 cd3c a5bb 04d6 995a ff5d faa8
0000040 7e4b bd20 d011 34c0 ce8c bfce 5cff 13d5
0000050 0c6d e89f 34b3 0acd 683d 7b51 fa00 8b4a
0000060 f351 90fd 6019 ba79 068f 7c89 4475 81bc
0000070 e4a7 bace 0bb4 c612 8930 8b64 4291 3214
0000080 c7f3 a5ff c582 4e23 5ce0 7fc1 7bad 1a3d
0000090 edac f79b 8f56 777c 47d2 79ff 4c88
000009
十六进制编码导出的 SPKI(WebCrypto 导出的结果):
私人:
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIApK1m/qpIAZ1iENhtXJxng4bdur6YV2SpMs+uFtSiJ/n96HbjVkqSENavv7vblIow+i5QUhaOkqSNWi0B7x695C6hgYkDgYYABAATsbs5B+ebSwoIXD6RD2NYONzSWOtt0SigPM27pdYEWpld/6j6S34gvRHQwDSMzs6//1zVE20Mn+izNM0KPWhRewD6SotR8/2QGWB5uo8GiXx1RLyBp+TOurQLEsYwiWSLkUIUMvPH/6WCxSNO4FzBf617PRqs7Zv3Vo98d9JH/3mITA==
Public:
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAE7G7OQfnm0sKCFw+kQ9jWDjc0ljrbdEooDzNu6XWBFqZXf+o+kt+IL0R0MA0jM7Ov/9c1RNtDJ/oszTNCj1oUXsA+kqLUfP9kBlgebqPBol8dUS8gafkzrq0CxLGMIlki5FCFDLzx/+lgsUjTuBcwX+tez0arO2b91aPfHfSR/95iEw=
更新 4:
私钥jwk格式:
{
"crv":"P-521",
"d":"AKStZv6qSAGdYhDYbVycZ4OG3bq-mFdkqTLPrhbUoif5_eh241ZKkhDWr7-725SKMPouUFIWjpKkjVotAe8eveQu",
"ext":true,
"key_ops":["sign"],
"kty":"EC",
"x":"ABOxuzkH55tLCghcPpEPY1g43NJY623RKKA8zbul1gRamV3_qPpLfiC9EdDANIzOzr__XNUTbQyf6LM0zQo9aFF7",
"y":"APpKi1Hz_ZAZYHm6jwaJfHVEvIGn5M66tAsSxjCJZIuRQhQy88f_pYLFI07gXMF_rXs9Gqztm_dWj3x30kf_eYhM"
}
... the public key exported is in spki format ...
使用 ASN.1 的编码密钥大致结构如下。请参阅 RFC 5480, Section 2. Subject Public Key Information Fields and RFC 3279, Section 2.3.5 ECDSA and ECDH Keys 了解令人精疲力竭的详细信息。
SEQUENCE {
ALGORITHM ID
KEY {
NAMED_CURVE or DOMAIN_PARAMETERS
PUBLIC_KEY or PRIVATE_KEY
}
}
还有一个“原始密钥”,就是没有外层序列和算法标识符的密钥material。 OpenSSL 在其手册页中将“原始密钥”称为 Traditional Key。
d2i_ECPKParameters return NULL...
好的,您有一个 SPKI,而不是只是 域参数。域参数是曲线系数(a和b )、模数(p)、基点(G), 等等,他们描述了曲线。他们没有钥匙。
所以你应该使用类似d2i_PublicKey
的方法将密钥解析为EVP_KEY
,然后在加载密钥后获取域参数。
我见过的互操作的最大问题是:
NAMED_CURVE or DOMAIN_PARAMETERS
如果它是一条命名曲线,那么它将类似于 secp256 或 prime256v1 。如果它的域参数,则命名曲线是“展开”或“完全展开”,它将是曲线系数 (a 和 b),模数(p),基数point (G) 等。虽然它们指定了 exact 相同的东西,但它们导致了很多实践中的麻烦。
命名曲线和域参数之间的互操作引起了很多麻烦,以至于 OpenSSL 上有一个 wiki 页面:Elliptic Curve Cryptography | Named Curve。事实上,由于荒谬,OpenSSL 网络服务器无法 运行 正确处理自身!
所以我在这里唯一能说的是:认识到有什么,不要指望命名曲线和域参数在软件中是相等的,即使它们完全相同(演示细节除外) .
如果您提供一些测试密钥,我们可能会提供更多详细信息。我猜你有一个 PEM 编码的密钥,所以你应该使用其他函数,比如 PEM_read_PUBKEY。但这只是猜测。
函数 d2i_ECPublicKey()
和 i2d_ECPublicKey()
或等效函数似乎没有在 OpenSSL 中实现。根据用户 jww 指向的 ASN.1 定义,您可以选择自己定义它们,如下所示:
#include <openssl/asn1t.h>
/* C-struct definitions */
typedef struct ec_identifiers_st {
ASN1_OBJECT *algorithm;
ASN1_OBJECT *namedCurve;
} EC_IDENTIFIERS;
typedef struct ec_publickey_st {
EC_IDENTIFIERS *identifiers;
ASN1_BIT_STRING *publicKey;
} EC_PUBLICKEY;
/* ASN.1 definitions */
ASN1_SEQUENCE(EC_IDENTIFIERS) = {
ASN1_SIMPLE(EC_IDENTIFIERS, algorithm, ASN1_OBJECT),
ASN1_SIMPLE(EC_IDENTIFIERS, namedCurve, ASN1_OBJECT)
} ASN1_SEQUENCE_END(EC_IDENTIFIERS)
DECLARE_ASN1_ALLOC_FUNCTIONS(EC_IDENTIFIERS)
IMPLEMENT_ASN1_ALLOC_FUNCTIONS(EC_IDENTIFIERS)
ASN1_SEQUENCE(EC_PUBLICKEY) = {
ASN1_SIMPLE(EC_PUBLICKEY, identifiers, EC_IDENTIFIERS),
ASN1_SIMPLE(EC_PUBLICKEY, publicKey, ASN1_BIT_STRING)
} ASN1_SEQUENCE_END(EC_PUBLICKEY)
DECLARE_ASN1_FUNCTIONS_const(EC_PUBLICKEY)
DECLARE_ASN1_ENCODE_FUNCTIONS_const(EC_PUBLICKEY, EC_PUBLICKEY)
IMPLEMENT_ASN1_FUNCTIONS_const(EC_PUBLICKEY)
注意:这不是一个完整的定义。它假定密钥包含命名曲线,而不是曲线参数(也请再次参见 jww 的回答)。它应该适用于您的示例,但如果您希望它适用于所有可能的 EC 密钥,您应该包括一个 CHOICE
字段——很好的练习:-)
从这里开始,您可以使用函数d2i_EC_PUBLICKEY()
和i2d_EC_PUBLICKEY()
来进行来回转换。这是一个没有任何错误检查的例子:
#include <openssl/objects.h>
#include <openssl/evp.h>
#include <openssl/ec.h>
void ASN1ECPublicKeyTester(void)
{
EC_PUBLICKEY *parsedKey = NULL;
EC_KEY *ecKey = NULL;
const unsigned char *helper = NULL;
char buffer[100] = { 0 };
int nid = -1;
EVP_PKEY *evpKey;
# define COUNT(_Array) (sizeof(_Array) / sizeof(_Array[0]))
helper = testKey;
parsedKey = d2i_EC_PUBLICKEY(NULL, &helper, COUNT(testKey));
OBJ_obj2txt(buffer, COUNT(buffer), parsedKey->identifiers->algorithm, 0);
printf("Algorithm: \"%s\"\n", buffer);
OBJ_obj2txt(buffer, COUNT(buffer), parsedKey->identifiers->namedCurve, 0);
printf("Curve: \"%s\"\n", buffer);
/* o2i_ECPublicKey needs to be fed an EC_KEY that has the GROUP set */
nid = OBJ_obj2nid(parsedKey->identifiers->namedCurve);
ecKey = EC_KEY_new_by_curve_name(nid);
helper = parsedKey->publicKey->data;
o2i_ECPublicKey(&ecKey, &helper, parsedKey->publicKey->length);
/* Create EVP key for use with EVP API */
evpKey = EVP_PKEY_new();
if (1 == EVP_PKEY_set1_EC_KEY(evpKey, ecKey)) {
printf("It looks like everything worked\n");
/* EVP_PKEY now owns the key */
EC_KEY_free(ecKey);
};
/* Use your evpKey from here (and free afterwards) */
}
使用您提供的测试器密钥,输出为
Algorithm: "id-ecPublicKey"
Curve: "secp521r1"
It looks like everything worked
testKey 数组定义如下,基于您的密钥:
const unsigned char testKey[] = {
0x30, 0x81, 0x9B, 0x30, 0x10, 0x06, 0x07, 0x2A,
0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x05,
0x2B, 0x81, 0x04, 0x00, 0x23, 0x03, 0x81, 0x86,
0x00, 0x04, 0x00, 0x13, 0xB1, 0xBB, 0x39, 0x07,
0xE7, 0x9B, 0x4B, 0x0A, 0x08, 0x5C, 0x3E, 0x91,
0x0F, 0x63, 0x58, 0x38, 0xDC, 0xD2, 0x58, 0xEB,
0x6D, 0xD1, 0x28, 0xA0, 0x3C, 0xCD, 0xBB, 0xA5,
0xD6, 0x04, 0x5A, 0x99, 0x5D, 0xFF, 0xA8, 0xFA,
0x4B, 0x7E, 0x20, 0xBD, 0x11, 0xD0, 0xC0, 0x34,
0x8C, 0xCE, 0xCE, 0xBF, 0xFF, 0x5C, 0xD5, 0x13,
0x6D, 0x0C, 0x9F, 0xE8, 0xB3, 0x34, 0xCD, 0x0A,
0x3D, 0x68, 0x51, 0x7B, 0x00, 0xFA, 0x4A, 0x8B,
0x51, 0xF3, 0xFD, 0x90, 0x19, 0x60, 0x79, 0xBA,
0x8F, 0x06, 0x89, 0x7C, 0x75, 0x44, 0xBC, 0x81,
0xA7, 0xE4, 0xCE, 0xBA, 0xB4, 0x0B, 0x12, 0xC6,
0x30, 0x89, 0x64, 0x8B, 0x91, 0x42, 0x14, 0x32,
0xF3, 0xC7, 0xFF, 0xA5, 0x82, 0xC5, 0x23, 0x4E,
0xE0, 0x5C, 0xC1, 0x7F, 0xAD, 0x7B, 0x3D, 0x1A,
0xAC, 0xED, 0x9B, 0xF7, 0x56, 0x8F, 0x7C, 0x77,
0xD2, 0x47, 0xFF, 0x79, 0x88, 0x4C
};
PS:这是一款免费的分析 ASN.1 数据的好工具 ASN.1 Editor。使用它时,您的测试密钥如下所示:
我正在尝试使用 OpenSSL 在 C++ 中导入 ECDSA public 密钥(用于验证签名),但是 d2i_ECPKParameters return NULL.
使用 Web Cryptographi 生成的密钥 API; public 密钥以 spki 格式导出(W3 TR 文档在导出密钥时谈论 ASN.1 结构,以及 spki 的 DER 编码)。
我是 OpenSSL 的新手,我做错了什么?
导入:
bool ecdsa_verify(
const std::array<uint8_t, 20>& hash,
const std::experimental::basic_string_view<uint8_t>& signature,
const std::experimental::basic_string_view<uint8_t>& public_key) {
EC_GROUP* ec_group = nullptr;
const unsigned char* public_key_data = public_key.data();
ec_group = d2i_ECPKParameters(nullptr, &public_key_data, public_key.length());
if (ec_group == nullptr) {
return false; // RETURN POINT
}
EC_KEY* ec_key = EC_KEY_new();
if (ec_key == nullptr) {
EC_GROUP_free(ec_group);
return false;
}
if (!EC_KEY_set_group(ec_key, ec_group)) {
EC_GROUP_free(ec_group);
EC_KEY_free(ec_key);
return false;
}
bool is_signature_valid =
ECDSA_verify(0, hash.data(), hash.size(), signature.data(),
signature.length(), ec_key);
EC_GROUP_free(ec_group);
EC_KEY_free(ec_key);
return is_signature_valid;
}
更新: 尝试其他导入(但还是不行):
const unsigned char* public_key_data = public_key.data();
EC_KEY* ec_key =
o2i_ECPublicKey(nullptr, &public_key_data, public_key.length());
if (ec_key == nullptr) {
return false; // RETURN POINT
}
bool is_signature_valid =
ECDSA_verify(0, hash.data(), hash.size(), signature.data(),
signature.length(), ec_key);
EC_KEY_free(ec_key);
导出:
function ecdsa_export_pub_key(key) {
return window.crypto.subtle.exportKey(
"spki",
key);
}
更新 2:
我生成了 PEM 密钥(从 JS 中导出的密钥),但主要的是,我不使用 PEM 密钥。在 JavaScript 中导出 public 键后,我从 ArrayBuffer 创建了一个新的 Uint8Array,通过 WebSocket(二进制帧)将其发送到服务器,并尝试解析它。收到的 uint8_t 数组总是 158 字节长度。我使用 P-521 -- secp521r1.
导出的私钥在pkcs8!
-----BEGIN PRIVATE KEY-----
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIApK1m/qpIAZ1iENht
XJxng4bdur6YV2SpMs+uFtSiJ/n96HbjVkqSENavv7vblIow+i5QUhaOkqSNWi0B
7x695C6hgYkDgYYABAATsbs5B+ebSwoIXD6RD2NYONzSWOtt0SigPM27pdYEWpld
/6j6S34gvRHQwDSMzs6//1zVE20Mn+izNM0KPWhRewD6SotR8/2QGWB5uo8GiXx1
RLyBp+TOurQLEsYwiWSLkUIUMvPH/6WCxSNO4FzBf617PRqs7Zv3Vo98d9JH/3mI
TA==
-----END PRIVATE KEY-----
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAE7G7OQfnm0sKCFw+kQ9jWDjc0ljr
bdEooDzNu6XWBFqZXf+o+kt+IL0R0MA0jM7Ov/9c1RNtDJ/oszTNCj1oUXsA+kqL
UfP9kBlgebqPBol8dUS8gafkzrq0CxLGMIlki5FCFDLzx/+lgsUjTuBcwX+tez0a
rO2b91aPfHfSR/95iEw=
-----END PUBLIC KEY-----
一些细节:
% openssl asn1parse -inform PEM -in pub.pem
0:d=0 hl=3 l= 155 cons: SEQUENCE
3:d=1 hl=2 l= 16 cons: SEQUENCE
5:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey
14:d=2 hl=2 l= 5 prim: OBJECT :secp521r1
21:d=1 hl=3 l= 134 prim: BIT STRING
% openssl asn1parse -inform PEM -in priv.pem
0:d=0 hl=3 l= 238 cons: SEQUENCE
3:d=1 hl=2 l= 1 prim: INTEGER :00
6:d=1 hl=2 l= 16 cons: SEQUENCE
8:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey
17:d=2 hl=2 l= 5 prim: OBJECT :secp521r1
24:d=1 hl=3 l= 214 prim: OCTET STRING [HEX DUMP]:3081D3020101044200A4AD66FEAA48019D6210D86D5C9C678386DDBABE985764A932CFAE16D4A227F9FDE876E3564A9210D6AFBFBBDB948A30FA2E5052168E92A48D5A2D01EF1EBDE42EA1818903818600040013B1BB3907E79B4B0A085C3E910F635838DCD258EB6DD128A03CCDBBA5D6045A995DFFA8FA4B7E20BD11D0C0348CCECEBFFF5CD5136D0C9FE8B334CD0A3D68517B00FA4A8B51F3FD90196079BA8F06897C7544BC81A7E4CEBAB40B12C63089648B91421432F3C7FFA582C5234EE05CC17FAD7B3D1AACED9BF7568F7C77D247FF79884C
调用时的错误代码o2i_ECPublicKey:
(使用相同的数据调用,但每次错误都不同——无论如何都会重复。)
error:10067066:elliptic curve routines:ec_GFp_simple_oct2point:invalid
error:10098010:elliptic curve routines:o2i_ECPublicKey:EC lib
error:0D07207B:asn1 encoding routines:ASN1_get_object:header too long
error:10067066:elliptic curve routines:ec_GFp_simple_oct2point:invalid
error:10098010:elliptic curve routines:o2i_ECPublicKey:EC lib
更新 3:
从 C++ 中,我将接收到的数据(密钥)写到一个文件中:
% % openssl asn1parse -inform DER -in data.bin
0:d=0 hl=3 l= 155 cons: SEQUENCE
3:d=1 hl=2 l= 16 cons: SEQUENCE
5:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey
14:d=2 hl=2 l= 5 prim: OBJECT :secp521r1
21:d=1 hl=3 l= 134 prim: BIT STRING
%
% hexdump data.bin
0000000 8130 309b 0610 2a07 4886 3dce 0102 0506
0000010 812b 0004 0323 8681 0400 1300 bbb1 0739
0000020 9be7 0a4b 5c08 913e 630f 3858 d2dc eb58
0000030 d16d a028 cd3c a5bb 04d6 995a ff5d faa8
0000040 7e4b bd20 d011 34c0 ce8c bfce 5cff 13d5
0000050 0c6d e89f 34b3 0acd 683d 7b51 fa00 8b4a
0000060 f351 90fd 6019 ba79 068f 7c89 4475 81bc
0000070 e4a7 bace 0bb4 c612 8930 8b64 4291 3214
0000080 c7f3 a5ff c582 4e23 5ce0 7fc1 7bad 1a3d
0000090 edac f79b 8f56 777c 47d2 79ff 4c88
000009
十六进制编码导出的 SPKI(WebCrypto 导出的结果):
私人:
MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIApK1m/qpIAZ1iENhtXJxng4bdur6YV2SpMs+uFtSiJ/n96HbjVkqSENavv7vblIow+i5QUhaOkqSNWi0B7x695C6hgYkDgYYABAATsbs5B+ebSwoIXD6RD2NYONzSWOtt0SigPM27pdYEWpld/6j6S34gvRHQwDSMzs6//1zVE20Mn+izNM0KPWhRewD6SotR8/2QGWB5uo8GiXx1RLyBp+TOurQLEsYwiWSLkUIUMvPH/6WCxSNO4FzBf617PRqs7Zv3Vo98d9JH/3mITA==
Public:
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAE7G7OQfnm0sKCFw+kQ9jWDjc0ljrbdEooDzNu6XWBFqZXf+o+kt+IL0R0MA0jM7Ov/9c1RNtDJ/oszTNCj1oUXsA+kqLUfP9kBlgebqPBol8dUS8gafkzrq0CxLGMIlki5FCFDLzx/+lgsUjTuBcwX+tez0arO2b91aPfHfSR/95iEw=
更新 4:
私钥jwk格式:
{
"crv":"P-521",
"d":"AKStZv6qSAGdYhDYbVycZ4OG3bq-mFdkqTLPrhbUoif5_eh241ZKkhDWr7-725SKMPouUFIWjpKkjVotAe8eveQu",
"ext":true,
"key_ops":["sign"],
"kty":"EC",
"x":"ABOxuzkH55tLCghcPpEPY1g43NJY623RKKA8zbul1gRamV3_qPpLfiC9EdDANIzOzr__XNUTbQyf6LM0zQo9aFF7",
"y":"APpKi1Hz_ZAZYHm6jwaJfHVEvIGn5M66tAsSxjCJZIuRQhQy88f_pYLFI07gXMF_rXs9Gqztm_dWj3x30kf_eYhM"
}
... the public key exported is in spki format ...
使用 ASN.1 的编码密钥大致结构如下。请参阅 RFC 5480, Section 2. Subject Public Key Information Fields and RFC 3279, Section 2.3.5 ECDSA and ECDH Keys 了解令人精疲力竭的详细信息。
SEQUENCE {
ALGORITHM ID
KEY {
NAMED_CURVE or DOMAIN_PARAMETERS
PUBLIC_KEY or PRIVATE_KEY
}
}
还有一个“原始密钥”,就是没有外层序列和算法标识符的密钥material。 OpenSSL 在其手册页中将“原始密钥”称为 Traditional Key。
d2i_ECPKParameters return NULL...
好的,您有一个 SPKI,而不是只是 域参数。域参数是曲线系数(a和b )、模数(p)、基点(G), 等等,他们描述了曲线。他们没有钥匙。
所以你应该使用类似d2i_PublicKey
的方法将密钥解析为EVP_KEY
,然后在加载密钥后获取域参数。
我见过的互操作的最大问题是:
NAMED_CURVE or DOMAIN_PARAMETERS
如果它是一条命名曲线,那么它将类似于 secp256 或 prime256v1 。如果它的域参数,则命名曲线是“展开”或“完全展开”,它将是曲线系数 (a 和 b),模数(p),基数point (G) 等。虽然它们指定了 exact 相同的东西,但它们导致了很多实践中的麻烦。
命名曲线和域参数之间的互操作引起了很多麻烦,以至于 OpenSSL 上有一个 wiki 页面:Elliptic Curve Cryptography | Named Curve。事实上,由于荒谬,OpenSSL 网络服务器无法 运行 正确处理自身!
所以我在这里唯一能说的是:认识到有什么,不要指望命名曲线和域参数在软件中是相等的,即使它们完全相同(演示细节除外) .
如果您提供一些测试密钥,我们可能会提供更多详细信息。我猜你有一个 PEM 编码的密钥,所以你应该使用其他函数,比如 PEM_read_PUBKEY。但这只是猜测。
函数 d2i_ECPublicKey()
和 i2d_ECPublicKey()
或等效函数似乎没有在 OpenSSL 中实现。根据用户 jww 指向的 ASN.1 定义,您可以选择自己定义它们,如下所示:
#include <openssl/asn1t.h>
/* C-struct definitions */
typedef struct ec_identifiers_st {
ASN1_OBJECT *algorithm;
ASN1_OBJECT *namedCurve;
} EC_IDENTIFIERS;
typedef struct ec_publickey_st {
EC_IDENTIFIERS *identifiers;
ASN1_BIT_STRING *publicKey;
} EC_PUBLICKEY;
/* ASN.1 definitions */
ASN1_SEQUENCE(EC_IDENTIFIERS) = {
ASN1_SIMPLE(EC_IDENTIFIERS, algorithm, ASN1_OBJECT),
ASN1_SIMPLE(EC_IDENTIFIERS, namedCurve, ASN1_OBJECT)
} ASN1_SEQUENCE_END(EC_IDENTIFIERS)
DECLARE_ASN1_ALLOC_FUNCTIONS(EC_IDENTIFIERS)
IMPLEMENT_ASN1_ALLOC_FUNCTIONS(EC_IDENTIFIERS)
ASN1_SEQUENCE(EC_PUBLICKEY) = {
ASN1_SIMPLE(EC_PUBLICKEY, identifiers, EC_IDENTIFIERS),
ASN1_SIMPLE(EC_PUBLICKEY, publicKey, ASN1_BIT_STRING)
} ASN1_SEQUENCE_END(EC_PUBLICKEY)
DECLARE_ASN1_FUNCTIONS_const(EC_PUBLICKEY)
DECLARE_ASN1_ENCODE_FUNCTIONS_const(EC_PUBLICKEY, EC_PUBLICKEY)
IMPLEMENT_ASN1_FUNCTIONS_const(EC_PUBLICKEY)
注意:这不是一个完整的定义。它假定密钥包含命名曲线,而不是曲线参数(也请再次参见 jww 的回答)。它应该适用于您的示例,但如果您希望它适用于所有可能的 EC 密钥,您应该包括一个 CHOICE
字段——很好的练习:-)
从这里开始,您可以使用函数d2i_EC_PUBLICKEY()
和i2d_EC_PUBLICKEY()
来进行来回转换。这是一个没有任何错误检查的例子:
#include <openssl/objects.h>
#include <openssl/evp.h>
#include <openssl/ec.h>
void ASN1ECPublicKeyTester(void)
{
EC_PUBLICKEY *parsedKey = NULL;
EC_KEY *ecKey = NULL;
const unsigned char *helper = NULL;
char buffer[100] = { 0 };
int nid = -1;
EVP_PKEY *evpKey;
# define COUNT(_Array) (sizeof(_Array) / sizeof(_Array[0]))
helper = testKey;
parsedKey = d2i_EC_PUBLICKEY(NULL, &helper, COUNT(testKey));
OBJ_obj2txt(buffer, COUNT(buffer), parsedKey->identifiers->algorithm, 0);
printf("Algorithm: \"%s\"\n", buffer);
OBJ_obj2txt(buffer, COUNT(buffer), parsedKey->identifiers->namedCurve, 0);
printf("Curve: \"%s\"\n", buffer);
/* o2i_ECPublicKey needs to be fed an EC_KEY that has the GROUP set */
nid = OBJ_obj2nid(parsedKey->identifiers->namedCurve);
ecKey = EC_KEY_new_by_curve_name(nid);
helper = parsedKey->publicKey->data;
o2i_ECPublicKey(&ecKey, &helper, parsedKey->publicKey->length);
/* Create EVP key for use with EVP API */
evpKey = EVP_PKEY_new();
if (1 == EVP_PKEY_set1_EC_KEY(evpKey, ecKey)) {
printf("It looks like everything worked\n");
/* EVP_PKEY now owns the key */
EC_KEY_free(ecKey);
};
/* Use your evpKey from here (and free afterwards) */
}
使用您提供的测试器密钥,输出为
Algorithm: "id-ecPublicKey"
Curve: "secp521r1"
It looks like everything worked
testKey 数组定义如下,基于您的密钥:
const unsigned char testKey[] = {
0x30, 0x81, 0x9B, 0x30, 0x10, 0x06, 0x07, 0x2A,
0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, 0x06, 0x05,
0x2B, 0x81, 0x04, 0x00, 0x23, 0x03, 0x81, 0x86,
0x00, 0x04, 0x00, 0x13, 0xB1, 0xBB, 0x39, 0x07,
0xE7, 0x9B, 0x4B, 0x0A, 0x08, 0x5C, 0x3E, 0x91,
0x0F, 0x63, 0x58, 0x38, 0xDC, 0xD2, 0x58, 0xEB,
0x6D, 0xD1, 0x28, 0xA0, 0x3C, 0xCD, 0xBB, 0xA5,
0xD6, 0x04, 0x5A, 0x99, 0x5D, 0xFF, 0xA8, 0xFA,
0x4B, 0x7E, 0x20, 0xBD, 0x11, 0xD0, 0xC0, 0x34,
0x8C, 0xCE, 0xCE, 0xBF, 0xFF, 0x5C, 0xD5, 0x13,
0x6D, 0x0C, 0x9F, 0xE8, 0xB3, 0x34, 0xCD, 0x0A,
0x3D, 0x68, 0x51, 0x7B, 0x00, 0xFA, 0x4A, 0x8B,
0x51, 0xF3, 0xFD, 0x90, 0x19, 0x60, 0x79, 0xBA,
0x8F, 0x06, 0x89, 0x7C, 0x75, 0x44, 0xBC, 0x81,
0xA7, 0xE4, 0xCE, 0xBA, 0xB4, 0x0B, 0x12, 0xC6,
0x30, 0x89, 0x64, 0x8B, 0x91, 0x42, 0x14, 0x32,
0xF3, 0xC7, 0xFF, 0xA5, 0x82, 0xC5, 0x23, 0x4E,
0xE0, 0x5C, 0xC1, 0x7F, 0xAD, 0x7B, 0x3D, 0x1A,
0xAC, 0xED, 0x9B, 0xF7, 0x56, 0x8F, 0x7C, 0x77,
0xD2, 0x47, 0xFF, 0x79, 0x88, 0x4C
};
PS:这是一款免费的分析 ASN.1 数据的好工具 ASN.1 Editor。使用它时,您的测试密钥如下所示: