Convert a plain public key to PEM

走远了吗. 提交于 2020-01-13 19:21:27

问题


I have generated an EC key pair using Prime-256v1 from a trusted application and export the public key to Normal OS. Key size is 65 bytes. The public key is in plain format (only key hex).

The exported public key needs to be given to a library (third party). The library expects the public key in PEM format.

After searching for some time, my understanding is first convert from plain key to DER format, and then convert the result to PEM. But I have not been able to find any APIs for the conversion from plain key to DER or PEM.

Found this API which PEM_ASN1_write((i2d_of_void*)i2d_PUBKEY,PEM_STRING_PUBLIC,outfile,ctx->cert->key->public_key,NULL,NULL,0,NULL,NULL); which convert from a file pointer. But I am not able to do file operations as not file storage possible. I am getting public key in a buffer.

I am doing this in C program, if any sample code or API's to convert plain hex key to PEM.

Thanks in advance


回答1:


Using the openssl utility the command you can use is:

openssl ec -in .\prime256pubkey.cer -pubin -inform der -pubout -outform pem -out .\prime256pubkey.pem

To reproduce this with code you need to use these main openssl api's

  • d2i_EC_PUBKEY_bio - to read in the DER formatted EC public key
  • PEM_write_bio_EC_PUBKEY - to write out the EC public key in PEM format

An openssl example, turned into C++ code around the C openssl API, would be:

template<typename T, typename D>
std::unique_ptr<T, D> make_handle(T* handle, D deleter)
{
    return std::unique_ptr<T, D>{handle, deleter};
}

bool convert_der_ec_pubkey_to_pem()
{
    // read in DER ec public key
    auto infile = make_handle(BIO_new_file("prime256pubkey.cer", "rb"), BIO_free);
    if(!infile) return false;

    auto const eckey = make_handle(d2i_EC_PUBKEY_bio(infile.get(), nullptr), EC_KEY_free);
    if(!eckey) return false;

    infile.reset();

    // write out PEM ec public key
    auto outfile = make_handle(BIO_new_file("prime256pubkey.pem", "w"), BIO_free);
    if(!outfile) return false;

    return PEM_write_bio_EC_PUBKEY(outfile.get(), eckey.get()) != 0;
}



回答2:


The value you provided in a comment 4bb5f0c58cc71806ec4d228b730dd252947e679cce05f71d434787fe228f14c799cf8965780bb308aa722ac179bfa5fd57592a72cbdcfe89ab61ad5d77251186d is the wrong length. It is 129 hex digits, aka nibbles, but an encoded point for prime256v1 (aka secp256r1 or P-256) must either be 65 octets beginning with 04 (uncompressed) or 33 octets beginning with 02 or 03 (compressed). When a hex string (or decimal or octal for that matter) represents an integer, you can remove or add leading zeros without changing the number, but an EC point is not an integer.

You say your code is providing 65 bytes which is correct for uncompressed. Use that.

Unlike most other PK algorithms, OpenSSL does not have an algorithm-specific DER (and thus PEM) format for ECC, so anything accurately described as wanting a PEM publickey (and not a certificate, which is the conventional way of transmitting a publickey) must be using the X.509/PKIX SubjectPublicKeyInfo format, which OpenSSL names PUBKEY (as shown in Shane's answer) and maps from or to an EVP_PKEY structure in the program. To construct this from the raw public point, you must first combine it with the curve identification to form an actual EC publickey, and then identify that as EC to form a generic publickey, and write it. OpenSSL can do I/O (including PEM) to and from memory using a BIO of type 'mem', without any file(s). (And making this firmly a programming question and not a commandline question, which would really be offtopic and belong in another Stack, although many are asked here anyway.)

/* SO #56218946 */
#include <stdio.h>
#include <stdlib.h>
#include <openssl/ec.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/objects.h>
#include <openssl/pem.h>
#ifdef _WIN32
#include <openssl/applink.c>
#endif

void err (const char *label){  // for test; improve for real code
  fprintf (stderr, "Error in %s:\n", label);
  ERR_print_errors_fp (stderr);
  exit (1);
}

int main (void) //(int argc, char**argv)
{
  ERR_load_crypto_strings(); /* or SSL_load_error_strings */
  //OPENSSL_add_all_algorithms_noconf(); /* for PKCS#8 */

  // test data -- replace for real use
  char hex [] = "04bb5f0c58cc71806ec4d228b730dd252947e679cce05f71d434787fe228f14c799cf8965780bb308aa722ac179bfa5fd57592a72cbdcfe89ab61ad5d77251186d";
  unsigned char raw [65]; for( int i = 0; i < 65; i++ ){ sscanf(hex+2*i, "%2hhx", raw+i); }

  EC_KEY *eck = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); /* or OBJ_txt2nid("prime256v1") */
  if( !eck ) err("ECCnewbyname");
  EC_KEY_set_asn1_flag(eck, OPENSSL_EC_NAMED_CURVE); /* needed below 1.1.0 */
  const unsigned char *ptr = raw;
  if( !o2i_ECPublicKey (&eck, &ptr, sizeof(raw)) ) err("o2iECPublic=point");
  EVP_PKEY * pkey = EVP_PKEY_new(); 
  if( !EVP_PKEY_assign_EC_KEY(pkey, eck) ) err("PKEYassign");

  BIO *bio = BIO_new(BIO_s_mem());
  if( !PEM_write_bio_PUBKEY (bio, pkey) ) err("PEMwrite");
  char *pem = NULL; long len = BIO_get_mem_data (bio, &pem);
  fwrite (pem, 1, len, stdout); // for test; for real use as needed
  return 0;
}

(ADDED) Alternatively, since X9.62 point encoding is fixed size for a given curve, the DER-encoded SPKI structure for a given curve actually consists of a fixed header followed by the point value, so you could instead concatenate with that fixed header and do generic PEM conversion: output dashes-BEGIN line, output base64 with line breaks, output dashes-END line. Although it is not hard to work out the header if one knows how ASN.1 works, a shortcut is to generate the SPKI encoding for a dummy key with e.g. openssl ecparam -genkey -name prime256v1 -outform der and remove the last 65 bytes (or 33 if compressed using -conv_form). Compare to the Java variants at Loading raw 64-byte long ECDSA public key in Java .



来源:https://stackoverflow.com/questions/56218946/convert-a-plain-public-key-to-pem

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!