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

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

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


    【解决方案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】:

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

    序列化公钥:

    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() 以获取八位字节,它们只是无符号字符 *。

    要反序列化公钥:

    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 使用的字节最少。

    【讨论】:

    • 感谢您提供后续答复!
    • 反序列化公钥可能已经工作了 4 年零 5 个月,但今天听起来很复杂!!! :p EVP_PKEY_get1_EC_KEY 应该从八位字节创建 EC_POINT 但它从输入中获取 EC_POINT *!我试图通过 EC_POINT_new 来创建它,但后来无法使用 create EVP_KEY 创建密钥!
    猜你喜欢
    • 2011-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-12
    • 1970-01-01
    • 2012-09-10
    • 2012-04-05
    • 1970-01-01
    相关资源
    最近更新 更多