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

Posted

技术标签:

【中文标题】如何访问 OpenSSL 的 EVP_PKEY 结构中的原始 ECDH 公钥、私钥和参数?【英文标题】:How does one access the raw ECDH public key, private key and params inside OpenSSL's EVP_PKEY structure? 【发布时间】:2013-08-11 21:49:19 【问题描述】:

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

peerkey = get_peerkey(pkey);

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

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

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

【问题讨论】:

【参考方案1】:

上面的实现似乎太复杂了。 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

【讨论】:

问题标记为 C,但这是 C++ 答案。【参考方案2】:

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

序列化公钥:

    将 EVP_PKEY 传递给 EVP_PKEY_get1_EC_KEY() 以获取 EC_KEY。 将 EC_KEY 传递给 EC_KEY_get0_public_key() 以获取 EC_POINT。 将 EC_POINT 传递给 EC_POINT_point2oct() 以获取八位字节,它们只是无符号字符 *。

要反序列化公钥:

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

序列化私钥:

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

反序列化私钥:

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

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

【讨论】:

感谢您提供后续答复! 反序列化公钥可能已经工作了 4 年零 5 个月,但今天听起来很复杂!!! :p EVP_PKEY_get1_EC_KEY 应该从八位字节创建 EC_POINT 但它从输入中获取 EC_POINT *!我试图通过 EC_POINT_new 来创建它,但后来无法使用 create EVP_KEY 创建密钥!

以上是关于如何访问 OpenSSL 的 EVP_PKEY 结构中的原始 ECDH 公钥、私钥和参数?的主要内容,如果未能解决你的问题,请参考以下文章

php的openssl_sign($data,$sign,$private_key)这个函数的源码是啥?

将 NSData 转换为字符串?

如何利用OpenSSL生成证书

如何利用OpenSSL生成证书

如何使用 OpenSSL 进行 AES 解密

为啥openssl自建证书只能局域网访问