无法在 C++ 中使用 OpenSSL 派生 Diffie Hellman 共享机密

Unable to derive Diffie Hellman shared secret with OpenSSL in C++

我试图让自己熟悉 OpenSSL Diffie Hellman 功能,在这样做的过程中,我尝试创建一个简单的程序,它将生成两组 Diffie Hellman 私有密钥和 public 密钥,并且然后导出共享秘密。我遵循了 OpenSSL wiki 上的 Diffie Hellman 教程,我能够生成密钥,但是我无法导出共享密钥。我的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"存储的是DH密钥对,而"params"包含的是DH参数。派生上下文似乎在某种程度上与后者一起初始化,尽管它没有与之关联的私有和 public 密钥。

使用您发布的代码版本,我遇到的错误与您略有不同。我从您的代码中得到以下输出(链接到 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

第一个 "error 16" 输出只是因为 OpenSSL 当前不支持 EVP_PKEY_check() 调用 Diffie-Hellman 密钥。所以这可以安全地忽略。

第二个 "error 17" 输出(与错误队列中的 "check p not safe prime" 错误相关联)是一个更大的问题。这是因为 Diffie-Hellman 键有两种不同的类型。默认情况下,OpenSSL 使用 PKCS#3 Diffie-Hellman 密钥。但是它也支持 X9.42 Diffie-Hellman 密钥。函数 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++) {

上面是椭圆曲线 Diffie 和 Hellman。 OpenSSL 3 的工作示例(none 椭圆曲线)

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