我尝试使用OpenSSL(用于验证签名)在C ++中导入 ECDSA 公钥,但 d2i_ECPKParameters 返回NULL。
使用Web Cryptographi API生成的密钥;以 spki 格式导出的公钥(导出密钥时W3 TR doc谈论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中导出公钥后,我从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==
公开:
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"
}
答案 0 :(得分:2)
...导出的公钥是spki格式......
使用ASN.1,编码密钥的结构大致如下。有关艰苦的详细信息,请参阅RFC 5480, Section 2. Subject Public Key Information Fields和RFC 3279, Section 2.3.5 ECDSA and ECDH Keys。
SEQUENCE {
ALGORITHM ID
KEY {
NAMED_CURVE or DOMAIN_PARAMETERS
PUBLIC_KEY or PRIVATE_KEY
}
}
还有一个“原始密钥”,它是没有外部序列和算法标识符的密钥材料。 OpenSSL在其手册页中将“原始密钥”称为 传统密钥 。
d2i_ECPKParameters返回NULL ...
好的,你有一个SPKI,而不是只是域参数。域参数是曲线系数( a 和 b ),模数( p ),基点( G )等,它们描述了曲线。他们没有钥匙。
因此,您应该使用d2i_PublicKey
之类的内容将密钥解析为EVP_KEY
,然后在加载密钥后获取域参数。
我见过互操作的最大问题是:
NAMED_CURVE or DOMAIN_PARAMETERS
如果是命名曲线,那么它将类似于 secp256 或 prime256v1 。如果它的域参数,那么命名曲线是“展开”或“完全展开”,它将是曲线系数( a 和 b ),模数( p ),基点( G )虽然他们指定了完全相同的东西,但它们在实践中会造成很多麻烦。
命名曲线和域参数之间的互操作导致了很多麻烦,OpenSSL上面有一个wiki页面:Elliptic Curve Cryptography | Named Curve。事实上,由于荒谬,OpenSSL Web服务器无法正常运行!
所以我在这里唯一可以说的是:认识到有什么,并且不要指望命名曲线和域参数在软件中是相同的,即使它们完全相同(除了演示细节)。 / p>
如果您提供一些测试密钥,我们可能会提供更多详细信息。我的猜测是你有一个PEM编码密钥,所以你应该使用其他功能,如PEM_read_PUBKEY。但它只是一个猜测。
答案 1 :(得分:1)
在OpenSSL中似乎没有实现函数d2i_ECPublicKey()
和i2d_ECPublicKey()
或等价物。根据用户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。使用它时,您的测试键如下所示: