How does one access the raw ECDH public key, private key and params inside OpenSSL's EVP_PKEY structure?

StackOverflow https://stackoverflow.com/questions/18155559

Вопрос

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:

  1. How would get_peerkey() actually extract just the public key from pkey for sending to the peer?
  2. How would the code extract the private key and params from pKey to store them for later use after the key exchange?
  3. How would 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.

Это было полезно?

Решение

To answer my own question, there's a different path for the private key and the public key.

To serialize the public key:

  1. Pass the EVP_PKEY to EVP_PKEY_get1_EC_KEY() to get an EC_KEY.
  2. Pass the EC_KEY to EC_KEY_get0_public_key() to get an EC_POINT.
  3. Pass the EC_POINT to EC_POINT_point2oct() to get octets, which are just unsigned char *.

To deserialize the public key:

  1. Pass the octets to EC_POINT_oct2point() to get an EC_POINT.
  2. Pass the EC_POINT to EC_KEY_set_public_key() to get an EC_KEY.
  3. Pass the EC_KEY to EVP_PKEY_set1_EC_KEY to get an EVP_KEY.

To serialize the private key:

  1. Pass the EVP_PKEY to EVP_PKEY_get1_EC_KEY() to get an EC_KEY.
  2. Pass the EC_KEY to EC_KEY_get0_private_key() to get a BIGNUM.
  3. Pass the BIGNUM to BN_bn2mpi() to get an mpi, which is a format written to unsigned char *.

To deserialize the private key:

  1. Pass the mpi to BN_mpi2bn() to get a BIGNUM.
  2. Pass the BIGNUM to EC_KEY_set_private_key() to get an EC_KEY.
  3. Pass the EC_KEY to EVP_PKEY_set1_EC_KEY to get an EVP_KEY.

It is also possible to convert the BIGNUM to hex, decimal, or "bin", although I think that mpi used the fewest bytes.

Другие советы

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

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:

// A parameter build for the public key.
OSSL_PARAM_BLD* paramBuild = OSSL_PARAM_BLD_new();
if (paramBuild == NULL) {
  return;
}

// This is just an example. Set the curve
// you used to generate the public and private keys.
const char curveName[] = "secp384r1";

// Set the curve name to the parameter build.
if (OSSL_PARAM_BLD_push_utf8_string(paramBuild,
    OSSL_PKEY_PARAM_GROUP_NAME, curveName, 0) != 1) {
  OSSL_PARAM_BLD_free(paramBuild);
  return;
}

// Set the serialized public key.
if (OSSL_PARAM_BLD_push_octet_string(paramBuild, OSSL_PKEY_PARAM_PUB_KEY,
    serializedPublicKey, serializedPublicKeyLen) != 1) {
  OSSL_PARAM_BLD_free(paramBuild);
  return;
}

// Convert the OSSL_PARAM_BLD to an OSSL_PARAM.
OSSL_PARAM* params = OSSL_PARAM_BLD_to_param(paramBuild);
if (params == NULL) {
  OSSL_PARAM_BLD_free(paramBuild);    
  return;
}

// Create a EVP_PKEY context.
EVP_PKEY_CTX* publicKeyCtx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
if (publicKeyCtx == NULL) {
  OSSL_PARAM_BLD_free(paramBuild);
  OSSL_PARAM_free(params); 
  return;  
}

// Initialize the EVP_PKEY context.
if (EVP_PKEY_fromdata_init(publicKeyCtx) <= 0) {
  OSSL_PARAM_BLD_free(paramBuild);
  OSSL_PARAM_free(params);
  EVP_PKEY_CTX_free(publicKeyCtx);
  return;
}

// Create the peer public key object.
EVP_PKEY* publicKey = NULL;
if (EVP_PKEY_fromdata(publicKeyCtx, &publicKey,
    EVP_PKEY_PUBLIC_KEY, params) <= 0) {
  OSSL_PARAM_BLD_free(paramBuild);
  OSSL_PARAM_free(params);
  EVP_PKEY_CTX_free(publicKeyCtx);
  return;
}

// Free auxiliary things...
OSSL_PARAM_BLD_free(paramBuild);
OSSL_PARAM_free(params);
EVP_PKEY_CTX_free(publicKeyCtx);


// 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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top