如何访问 OpenSSL 的 EVP_PKEY 结构中的原始 ECDH 公钥、私钥和参数?

问题描述 投票:0回答:3

我正在使用 OpenSSL 的 c 库生成椭圆曲线 Diffie-Hellman (ECDH) 密钥对,遵循第一个代码示例此处。它用这一行掩盖了公钥的实际交换:

peerkey = get_peerkey(pkey);

pkey
变量和返回值都是
EVP *
类型。
pkey
包含之前生成的公钥、私钥和参数,返回值仅包含对等体的公钥。所以这就提出了三个问题:

  1. get_peerkey()
    实际上如何从
    pkey
    中提取公钥以发送给对等方?
  2. 代码如何从
    pKey
    中提取私钥和参数来存储它们以供密钥交换后使用?
  3. get_peerkey()
    如何从对等方的原始公钥生成新的
    EVP_PKEY
    结构?

我见过 OpenSSL 函数

EVP_PKEY_print_public()
EVP_PKEY_print_private()
EVP_PKEY_print_params()
,但这些函数用于生成人类可读的输出。我还没有找到任何将人类可读的公钥转换回
EVP_PKEY
结构的等效方法。

c openssl pki elliptic-curve diffie-hellman
3个回答
52
投票

回答我自己的问题,私钥和公钥有不同的路径。

序列化公钥:

  1. 将 EVP_PKEY 传递给 EVP_PKEY_get1_EC_KEY() 以获取 EC_KEY。
  2. 将 EC_KEY 传递给 EC_KEY_get0_public_key() 以获取 EC_POINT。
  3. 将 EC_POINT 传递给 EC_POINT_point2oct() 以获取八位字节,这些八位字节只是 unsigned char *。

反序列化公钥:

  1. 将八位字节传递给 EC_POINT_oct2point() 以获取 EC_POINT。
  2. 将 EC_POINT 传递给 EC_KEY_set_public_key() 以获取 EC_KEY。
  3. 将 EC_KEY 传递给 EVP_PKEY_set1_EC_KEY 以获取 EVP_KEY。

序列化私钥:

  1. 将 EVP_PKEY 传递给 EVP_PKEY_get1_EC_KEY() 以获取 EC_KEY。
  2. 将 EC_KEY 传递给 EC_KEY_get0_private_key() 以获取 BIGNUM。
  3. 将 BIGNUM 传递给 BN_bn2mpi() 以获得 mpi,这是写入的格式 无符号字符 *.

反序列化私钥:

  1. 将 mpi 传递给 BN_mpi2bn() 以获得 BIGNUM。
  2. 将 BIGNUM 传递给 EC_KEY_set_private_key() 以获取 EC_KEY。
  3. 将 EC_KEY 传递给 EVP_PKEY_set1_EC_KEY 以获取 EVP_KEY。

也可以将 BIGNUM 转换为十六进制、十进制或“bin”,尽管我认为 mpi 使用的字节最少。


3
投票

OpenSSL 3.x.x

序列化公钥:

// We assume the public and private keys have been already generated.
// EVP_PKEY* keyPair...

// Get the serialized public key length.
size_t serializedPublicKeyLen = 0;
if (EVP_PKEY_get_octet_string_param(keyPair, OSSL_PKEY_PARAM_PUB_KEY,
    NULL, 0, &serializedPublicKeyLen) != 1) {
  return;
}

// Allocate memory for the serialized public key.
unsigned char* serializedPublicKey = (unsigned char*)OPENSSL_malloc(serializedPublicKeyLen);
if (serializedPublicKey == NULL) {
  return;
}

// Get the serialized public key.
if (EVP_PKEY_get_octet_string_param(keyPair, OSSL_PKEY_PARAM_PUB_KEY,
   serializedPublicKey, serializedPublicKeyLen, &serializedPublicKeyLen) != 1) {
  return;
}

// Deallocate the memory when you finish using the serialized public key.
OPENSSL_free(serializedPublicKey);

反序列化公钥:

#include <openssl/evp.h>
#include <openssl/ec.h>
#include <openssl/core_names.h>

EVP_PKEY* ImportPublicKey(const unsigned char* public_key_x693, size_t public_key_len)
{
    EVP_PKEY_CTX* public_key_ctx = EVP_PKEY_CTX_new_from_name(nullptr, "ec", nullptr);

    if (EVP_PKEY_fromdata_init(public_key_ctx) != 1)
    {
        // Handle Errors
    }

    char curve_name[] = SN_X9_62_prime256v1;

    OSSL_PARAM params[3];
    params[0] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, curve_name, 0);
    params[1] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_PUB_KEY, (void*)public_key_x693, public_key_len);
    params[2] = OSSL_PARAM_construct_end();

    EVP_PKEY* public_key = nullptr;
    if (EVP_PKEY_fromdata(public_key_ctx, &public_key, EVP_PKEY_PUBLIC_KEY, params) != 1)
    {
        // Handle Errors
    }

    EVP_PKEY_CTX_free(public_key_ctx);
    return public_key;
}

// Now you can use publicKey for EVP_PKEY_derive_set_peer.
// Call EVP_PKEY_free when you finish using it.

要序列化私钥,您会得到 BIGNUM:

BIGNUM* privateKey = NULL;
EVP_PKEY_get_bn_param(keyPair, OSSL_PKEY_PARAM_PRIV_KEY, &privateKey);

然后,您使用 BIGNUM 序列化函数之一:https://www.openssl.org/docs/man3.0/man3/BN_bn2bin.html

要反序列化私钥,您可以使用上面链接中的 BIGNUM 反序列化函数之一,然后通过 OSSL_PARAM_BLD_push_BN 和 OSSL_PKEY_PARAM_PRIV_KEY 将其推送到参数构建。


0
投票

上面的实现看起来太复杂了。

openssl/evp.h
具有函数
i2d_PublicKey()
d2i_PublicKey()
分别用于与公钥的二进制表示形式相互转换(私钥也有等效函数 - 请参阅:https://www.openssl.org/docs /manmaster/man3/d2i_PublicKey.html

一个小代码示例:

vector<unsigned char> ecdhPubkeyData(EVP_PKEY *key)
{
    int len = i2d_PublicKey(key, 0); // with 0 as second arg it gives length
    vector<unsigned char> ret(len);
    unsigned char *ptr = ret.data();
    len = i2d_PublicKey(key, &ptr);
    return ret;
}

// Make sure you free the returned pointer when you are done with it
EVP_PKEY *ecdhPubkeyFromData(vector <unsigned char> const &pubkeyData)
{       // You do need to put in in an existing EVP_PKEY that is assigned
        // an EC_KEY, because it needs to know what curve you use
    EC_KEY *ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
    EVP_PKEY *ret = EVP_PKEY_new();
    EVP_PKEY_assign_EC_KEY(ret, ec_key);
    unsigned char const *ptr = pubkeyData.data();
    d2i_PublicKey(EVP_PKEY_EC, &ret, &ptr, pubkeyData.size());
    return ret;
}
// PS: In a real example you want to check if any of these functions
// return NULL or some error code

我使用 C++ 向量来包含二进制数据,但当然你也可以使用 C 样式数组:-)

我绝对不是 OpenSSL 专家,所以如果我在这个实现中做了一些严重错误的事情,请告诉我:-p

© www.soinside.com 2019 - 2024. All rights reserved.