Невозможно получить общий секрет Диффи Хеллмана с помощью OpenSSL в C++

Я пытаюсь ознакомиться с функциями OpenSSL Diffie Hellman и при этом попытался создать простую программу, которая будет генерировать два набора закрытых и открытых ключей Diffie Hellman, а затем получать общий секрет. Я следил за учебником Диффи Хеллмана в вики OpenSSL, и я могу сгенерировать ключи, однако я не могу получить общий секрет. Мой код C++ (Linux) выглядит следующим образом:

#include <iostream>
#include <openssl/dh.h>
#include <openssl/engine.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>

int main(int argc, char** argv)
{

    EVP_PKEY* params;

    EVP_PKEY_CTX *kctx1, *kctx2, *dctx1, *dctx2;

    unsigned char *skey1, *skey2;
    size_t skeylen1, skeylen2;
    EVP_PKEY *dh_1, *dh_2;
    BIO* bio_out = NULL;
    int result = 0;
    ENGINE* eng;

    BIO* fp = BIO_new_fp(stdout, BIO_NOCLOSE);

    // Initalise Diffie Hellman PKEY for client 1
    if(NULL == (dh_1 = EVP_PKEY_new())) {
        std::cout << "error 1" << std::endl;
    }

    // Initalise Diffie Hellman PKEY for client 2
    if(NULL == (dh_2 = EVP_PKEY_new())) {
        std::cout << "error 2" << std::endl;
    }

    // Initalise Diffie Hellman parameter PKEY
    if(NULL == (params = EVP_PKEY_new())) {
        std::cout << "error 3" << std::endl;
    }

    // Set Diffie Hellman paramerers
    if(1 != EVP_PKEY_set1_DH(params, DH_get_2048_256())) {
        std::cout << "error 4" << std::endl;
    }

    // Initalise client 1 PKEY Context
    if(!(kctx1 = EVP_PKEY_CTX_new(params, NULL))) {
        std::cout << "error 5" << std::endl;
    }

    // Initalise client 2 PKEY Context
    if(!(kctx2 = EVP_PKEY_CTX_new(params, NULL))) {
        std::cout << "error 6" << std::endl;
    }

    if(!kctx1) {
        std::cout << "error 7" << std::endl;
    }

    if(!kctx2) {
        std::cout << "error 8" << std::endl;
    }

    // Initalise both contexts key generators
    if(1 != EVP_PKEY_keygen_init(kctx1)) {
        std::cout << "error 9" << std::endl;
    }

    if(1 != EVP_PKEY_keygen_init(kctx2)) {
        std::cout << "error 10" << std::endl;
    }

    // Generate DH public and private keys for client 1
    if(1 != EVP_PKEY_keygen(kctx1, &dh_1)) {
        std::cout << "error 11" << std::endl;
    }

    // Generate DH public and private keys for client 2
    if(1 != EVP_PKEY_keygen(kctx2, &dh_2)) {
        std::cout << "error 12" << std::endl;
    }

    // EVP_PKEY_print_public(fp, dh_1, 3, NULL);

    // EVP_PKEY_print_public(fp, dh_2, 3, NULL);

    // Create key derivation context
    if(NULL == (dctx1 = EVP_PKEY_CTX_new(dh_1, NULL))) {
        std::cout << "error 13" << std::endl;
    }

    if(!dctx1) {
        std::cout << "error 14" << std::endl;
    }

    // Initalise first key derivation context
    if(1 != EVP_PKEY_derive_init(dctx1)) {
        std::cout << "error 15" << std::endl;
    }

    if(1 != EVP_PKEY_check(dctx1)) {
        std::cout << "error 16" << std::endl;
    }

    if(1 != EVP_PKEY_param_check(dctx1)) {
        std::cout << "error 17" << std::endl;
    }

    // Set first key derivation context peer key to the second DH PKEY
    if(1 != EVP_PKEY_derive_set_peer(dctx1, dh_2)) {
        std::cout << "error 18" << std::endl;
    }

    if(1 != EVP_PKEY_public_check(dctx1)) {
        std::cout << "error 19" << std::endl;
    }

    /* Determine buffer length */
    if(EVP_PKEY_derive(dctx1, NULL, &skeylen1) <= 0) {
    }

    // Assign memory for shared key variable
    skey1 = (unsigned char*)OPENSSL_malloc(skeylen1);

    if(result = EVP_PKEY_derive(dctx1, skey1, &skeylen1) <= 0) {
        std::cout << "Key: " << skey1 << std::endl;

        for(int i = 0; i < skeylen1; i++) {
            std::cout << std::hex << (unsigned int)skey1[i];
        }
    }

    ERR_print_errors_fp(stdout);

    std::cout << result << std::endl;
}

Мои основные опасения:

  1. Что мне нужно сделать, чтобы получить общий секрет?
  2. Нужен ли мне новый контекст для каждой пары ключей и производных ключей?

Спасибо!

РЕДАКТИРОВАТЬ:

Я получаю следующие ошибки и нет общего ключа:

140690271102784:error:05079079:Diffie-Hellman routines:DH_check_ex:unable to check generator:../crypto/dh/dh_check.c:92:

140690271102784:error:05079076:Diffie-Hellman routines:DH_check_ex:check p not safe prime:../crypto/dh/dh_check.c:96:

140690271102784:error:0507B07B:Diffie-Hellman routines:DH_check_pub_key_ex:check pubkey too large:../crypto/dh/dh_check.c:190:

140690271102784:error:0507B07A:Diffie-Hellman routines:DH_check_pub_key_ex:check pubkey invalid:../crypto/dh/dh_check.c:192:

когда я использую

    if(NULL == (dctx1 = EVP_PKEY_CTX_new(dh_1, NULL))) {
        std::cout << "error 13" << std::endl;
    }

и я получаю следующие ошибки

error 16
error 19

Key: �ȭ��U

c0c8add7c4550000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

139939742943040:error:060BA096:digital envelope routines:EVP_PKEY_check:operation not supported for this keytype:../crypto/evp/pmeth_gn.c:188:
139939742943040:error:0507C07D:Diffie-Hellman routines:dh_pkey_public_check:missing pubkey:../crypto/dh/dh_ameth.c:517:
139939742943040:error:05066064:Diffie-Hellman routines:compute_key:no private value:../crypto/dh/dh_key.c:183:
1

когда я меняю "EVP_PKEY_CTX_new(dh_1, NULL)" на "EVP_PKEY_CTX_new(params, NULL)", вот так:

    if(NULL == (dctx1 = EVP_PKEY_CTX_new(params, NULL))) {
        std::cout << "error 13" << std::endl;
    }

Разница между этими двумя переменными заключается в том, что «dh_1» хранит пару ключей Диффи-Хелмана, тогда как «params» содержит параметры Диффи-Хелмана. Похоже, что контекст деривации в некоторой степени инициализируется последним, хотя с ним не связаны закрытый и открытый ключи.


person cyber_afro    schedule 06.12.2019    source источник
comment
Вы говорите, что не можете получить общий секрет. Что происходит, когда вы пытаетесь? Вы получаете какое-то сообщение об ошибке при вызове ERR_print_errors_fp? Если так, то, что это? Как далеко зайдет процесс, прежде чем вы столкнетесь с проблемами?   -  person Matt Caswell    schedule 07.12.2019
comment
Привет @MattCaswell, я обновил свой пост, чтобы ответить на ваши вопросы. Спасибо!   -  person cyber_afro    schedule 07.12.2019


Ответы (2)


С версией кода, которую вы разместили, я получил немного другие ошибки. Я получаю следующий вывод из вашего кода (связанного с OpenSSL 1.1.1):

error 16
error 17
140673674348352:error:060BA096:digital envelope routines:EVP_PKEY_check:operation not supported for this keytype:crypto/evp/pmeth_gn.c:187:
140673674348352:error:05079076:Diffie-Hellman routines:DH_check_ex:check p not safe prime:crypto/dh/dh_check.c:93:
0

Первый вывод «ошибка 16» просто потому, что OpenSSL в настоящее время не поддерживает вызов EVP_PKEY_check() для ключей Диффи-Хеллмана. Так что на это можно смело не обращать внимания.

Второй вывод «ошибка 17» (который связан с ошибкой «check p not safe prime» в очереди ошибок) является более серьезной проблемой. Это связано с тем, что ключи Диффи-Хеллмана бывают двух разных видов. По умолчанию OpenSSL использует ключи Диффи-Хеллмана PKCS#3. Однако он также поддерживает ключи Диффи-Хеллмана X9.42. Функция DH_get_2048_256() дает вам встроенный набор параметров для ключей X9.42. Однако функция EVP_PKEY_set1_DH() ожидает, что предоставленный объект DH будет ключом PKCS#3.

На самом деле это выглядит как ошибка в OpenSSL для меня (EVP_PKEY_set1_DH() должен действительно определить, какой это тип ключа, и сделать правильно), поэтому я поднял следующую проблему OpenSSL для этого: https://github.com/openssl/openssl/issues/10592

Вы можете обойти это, заменив вызов EVP_PKEY_set1_DH() на EVP_PKEY_assign() и указав тип как EVP_PKEY_DHX следующим образом:

    if(1 != EVP_PKEY_assign(params, EVP_PKEY_DHX, DH_get_2048_256())) {
        std::cout << "error 4" << std::endl;
    }

В качестве альтернативы вы можете просто использовать параметры PKCS # 3.

Наконец, причина, по которой вы не получаете общий секрет, заключается в том, что вы печатаете его только в случае сбоя вызова EVP_PKEY_derive()! На самом деле это удается!

Я внес следующие изменения в ваш код, и он работает для меня:

--- derive.cpp  2019-12-09 11:11:15.493349734 +0000
+++ derive-new.cpp  2019-12-09 11:14:59.348715074 +0000
@@ -37,7 +37,7 @@
     }

     // Set Diffie Hellman paramerers
-    if(1 != EVP_PKEY_set1_DH(params, DH_get_2048_256())) {
+    if(1 != EVP_PKEY_assign(params, EVP_PKEY_DHX, DH_get_2048_256())) {
         std::cout << "error 4" << std::endl;
     }

@@ -96,9 +96,11 @@
         std::cout << "error 15" << std::endl;
     }

+#if 0
     if(1 != EVP_PKEY_check(dctx1)) {
         std::cout << "error 16" << std::endl;
     }
+#endif

     if(1 != EVP_PKEY_param_check(dctx1)) {
         std::cout << "error 17" << std::endl;
@@ -120,7 +122,7 @@
     // Assign memory for shared key variable
     skey1 = (unsigned char*)OPENSSL_malloc(skeylen1);

-    if(result = EVP_PKEY_derive(dctx1, skey1, &skeylen1) <= 0) {
+    if((result = EVP_PKEY_derive(dctx1, skey1, &skeylen1)) > 0) {
         std::cout << "Key: " << skey1 << std::endl;

         for(int i = 0; i < skeylen1; i++) {
person Matt Caswell    schedule 09.12.2019
comment
Удивительно, это решило мою проблему, спасибо за помощь! Кстати, как вы это узнали: Однако функция EVP_PKEY_set1_DH() ожидает, что предоставленный объект DH будет ключом PKCS#3. - person cyber_afro; 09.12.2019
comment
Я разработчик OpenSSL ;-) - person Matt Caswell; 10.12.2019

Выше показана эллиптическая кривая Диффи и Хеллмана. Рабочий пример с OpenSSL 3 (без эллиптической кривой)

https://gist.github.com/digitalhuman/2a2b85d61672e4bf83596d41351723ba

person Digital Human    schedule 23.01.2021