Я пытаюсь импортировать ECDSA открытый ключ в C ++ с OpenSSL (для проверки подписей), но d2i_ECPKParameters вернуть NULL.
Ключи, созданные с помощью API Web Cryptographi; открытый ключ экспортирован в SPKI формат (W3 TR документ говорит о структуре ASN.1 при экспорте ключа и кодировании DER для spki).
Я новичок в 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 я создаю новый массив Uint8Array из ArrayBuffer, отправляю его через 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"}
Функции d2i_ECPublicKey()
а также i2d_ECPublicKey()
или эквиваленты, кажется, не реализованы в OpenSSL. Основываясь на определениях ASN.1, на которые указывает пользователь jww, вы можете определить их самостоятельно, например так:
#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. При использовании этого ваш тестовый ключ выглядит так:
… открытый ключ экспортируется в формате spki …
Кодированный ключ приблизительно структурирован следующим образом с использованием ASN.1. Увидеть RFC 5480, раздел 2. Поля информации открытого ключа субъекта а также RFC 3279, раздел 2.3.5 ECDSA и ключи ECDH для изнурительных деталей.
SEQUENCE {
ALGORITHM ID
KEY {
NAMED_CURVE or DOMAIN_PARAMETERS
PUBLIC_KEY or PRIVATE_KEY
}
}
Существует также «необработанный ключ», который представляет собой материал ключа без внешней последовательности и идентификатора алгоритма. OpenSSL вызывает «сырой ключ» Традиционный ключ в его справочных страницах.
d2i_ECPKParameters возвращают NULL …
ОК, у вас есть SPKI, а не просто параметры домена. Параметры домена — это кривые коэффициенты ( а также б), модуль (п), базовая точка (г) и т. д., и они описывают кривую. У них нет ключа.
Так что вы должны использовать что-то вроде d2i_PublicKey
разобрать ключ в EVP_KEY
, а затем получить параметры домена после загрузки ключа.
Самая большая проблема с взаимодействием, которое я видел, заключается в следующем:
NAMED_CURVE or DOMAIN_PARAMETERS
Если это именованная кривая, то это будет что-то вроде secp256 или же prime256v1. Если его параметры домена, то именованная кривая «размотана» или «полностью развернута», и это будут коэффициенты кривой ( а также б), модуль (п), базовая точка (г) и т. д. Хотя они указывают точный То же самое, они вызывают много проблем на практике.
Взаимодействие между именованными кривыми и параметрами домена вызывает столько проблем, что на OpenSSL есть вики-страница: Криптография с эллиптическими кривыми | Именованная кривая. Фактически, веб-серверы OpenSSL не могут работать должным образом с самим собой из-за нелепости!
Таким образом, единственное, что я могу сказать здесь: быть в курсе того, что есть, и не ожидать, что именованные кривые и параметры предметной области будут одинаковыми в программном обеспечении, даже если они абсолютно одинаковы (за исключением деталей презентации).
Если вы предоставите некоторые тестовые ключи, мы, вероятно, сможем предоставить более подробную информацию. Я думаю, у вас есть ключ в кодировке PEM, поэтому вы должны использовать другие функции, такие как PEM_read_PUBKEY. Но это только предположение.