我正在使用 OpenSSL 的 c 库生成椭圆曲线 Diffie-Hellman (ECDH) 密钥对,遵循第一个代码示例此处。它用这一行掩盖了公钥的实际交换:
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
结构的等效方法。
回答我自己的问题,私钥和公钥有不同的路径。
序列化公钥:
反序列化公钥:
序列化私钥:
反序列化私钥:
也可以将 BIGNUM 转换为十六进制、十进制或“bin”,尽管我认为 mpi 使用的字节最少。
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 将其推送到参数构建。
上面的实现看起来太复杂了。
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