Reputation: 2541
I'm using OpenSSL's c library to generate an elliptic curve Diffie-Hellman (ECDH) key pair, following the first code sample here. It glosses over the actual exchange of public keys with this line:
peerkey = get_peerkey(pkey);
The pkey
variable and the return value are both of type EVP *
. pkey
contains the public key, private key, and params generated earlier, and the return value only contains the peer's public key. So this raises three questions:
get_peerkey()
actually extract just the public key from pkey
for sending to the peer?pKey
to store them for later use after the key exchange?get_peerkey()
generate a new EVP_PKEY
structure from the peer's raw public key?I've seen the OpenSSL functions EVP_PKEY_print_public()
, EVP_PKEY_print_private()
, and EVP_PKEY_print_params()
but these are for generating human-readable output. And I haven't found any equivalent for converting a human-readable public key back into an EVP_PKEY
structure.
Upvotes: 27
Views: 14751
Reputation: 509
OpenSSL 3.x.x
To serialize the public key:
// 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);
To deserialize the public key:
#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.
To serialize the private key, you get BIGNUM instead:
BIGNUM* privateKey = NULL;
EVP_PKEY_get_bn_param(keyPair, OSSL_PKEY_PARAM_PRIV_KEY, &privateKey);
Then, you use one of the BIGNUM serialization function: https://www.openssl.org/docs/man3.0/man3/BN_bn2bin.html
To deserialize the private key, you use one of the BIGNUM deserialization function from the link above, then push it to the parameter build via OSSL_PARAM_BLD_push_BN with OSSL_PKEY_PARAM_PRIV_KEY.
Upvotes: 4
Reputation: 9
The implementation above seems too complicated. openssl/evp.h
has functions i2d_PublicKey()
and d2i_PublicKey()
to respectively convert to and from a binary representation of the public key (and there are equivalent functions for the private key - see: https://www.openssl.org/docs/manmaster/man3/d2i_PublicKey.html)
A small code example:
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
I am using C++ vectors to contain the binary data, but of course you could just use C-style arrays too :-)
I am absolutely not an OpenSSL expert, so let me know if I am doing something horribly wrong in this implementation :-p
Upvotes: 0
Reputation: 2541
To answer my own question, there's a different path for the private key and the public key.
To serialize the public key:
To deserialize the public key:
To serialize the private key:
To deserialize the private key:
It is also possible to convert the BIGNUM to hex, decimal, or "bin", although I think that mpi used the fewest bytes.
Upvotes: 53