2015-11-11 96 views
2

我試圖導入C++使用OpenSSL ECDSA公鑰(用於驗證簽名),但d2i_ECPKParameters返回NULL。進口WebCrypto產生ECDSA公共密鑰驗證操作

使用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; 
} 

UPDATE: 其他進口試試(但是仍然無法正常工作):

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的結果出口):

Private:

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

請創建一個測試密鑰,然後以某種編碼(hex,base64,pem)將其發佈,以便我們可以對其進行破解。 – jww

+0

@jww我加了他們。 – bsz

+0

您添加了pem編碼密鑰,但它們是您遇到問題的密鑰(聽起來像答案爲否)?如果沒有,那麼他們的演示文稿格式是什麼?如果它的ASN/1的DER,然後只是十六進制編碼併發布它們。我們總是可以剝離我們不需要的東西(比如十六進制編碼)。但是我們不能創造我們沒有的鑰匙。 – jww

回答

1

函數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) 

注:這不是一個完整的定義。它假設關鍵字包含命名曲線,而不是曲線參數(再次參見www.wwww.com的答案)。它應該與你的工作,例如,但如果你想讓它與所有可能的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 

,根據你的關鍵的密押數組定義如下:

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。使用時,您的測試密鑰如下所示: ASN.1 Editor

+0

不錯。謝謝。 :) – bsz

+0

不客氣。我添加了一個鏈接到你可能感興趣的不錯的ASN.1編輯器工具。 –

2

... the public key exported is in spki format ...

的編碼鍵碼是大致結構使用ASN.1如下。請參閱RFC 5480, Section 2. Subject Public Key Information FieldsRFC 3279, Section 2.3.5 ECDSA and ECDH Keys以瞭解詳細信息。

SEQUENCE { 
    ALGORITHM ID 
    KEY { 
     NAMED_CURVE or DOMAIN_PARAMETERS 
     PUBLIC_KEY or PRIVATE_KEY 
    } 
} 

還有一個「原始密鑰」,它是沒有外部序列和算法標識符的關鍵材料。 OpenSSL在其手冊頁中調用「原始密鑰」a 傳統密鑰


d2i_ECPKParameters return NULL...

OK,你有SPKI,而不是僅僅參數。域參數是曲線的係數(一個b),模數(p),基點(ģ)等,和它們所描述的曲線。他們沒有鑰匙。

因此,您應該使用類似d2i_PublicKey的東西來解析密鑰到EVP_KEY,然後在加載密鑰後獲取域參數。


與互操作我所看到的最大的問題是這樣的:

NAMED_CURVE or DOMAIN_PARAMETERS 

如果其命名曲線,那麼這將是像secp256prime256v1。如果它的域參數,則命名爲曲線是「退繞」或「完全展開」,並且這將是曲線的係數(一個b),模數(p),基點(G)等等。雖然他們指定了同樣的東西,但它們在實踐中會造成很大的麻煩。

命名曲線和域參數之間的相互操作會導致很多麻煩,OpenSSL上有一個wiki頁面:Elliptic Curve Cryptography | Named Curve。事實上,由於荒謬的原因,OpenSSL Web服務器無法正常運行!

因此我唯一可以說的是:認識到什麼具有,並且不要期望命名曲線和域參數在軟件中是相同的,即使它們完全相同(除了演示文稿細節)。


如果您提供了一些測試密鑰,我們可能會提供更多的細節。我的猜測是你有一個PEM編碼密鑰,所以你應該使用其他功能,如PEM_read_PUBKEY。但它只是一個猜測。