2

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

jjose
  • 21
  • 3

2 Answers2

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

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;
}
Shane Powell
  • 12,040
  • 2
  • 45
  • 53
  • what is make_handle? where it is defined? – sree May 24 '19 at 14:19
  • 1
    It’s a simple wrapper around std:unique_ptr to turn the OpenSSL raw pointer type into a raii class. It just makes the error handling easier. If you read the code above it’s defined above the function. You don’t need to use it, just have to remember to call the free function when finished with the OpenSSL object or else you will be leaking memory. I don’t like giving example code that leaks memory. – Shane Powell May 24 '19 at 16:41
  • Thank you, Shane Powell! I have compiled the program. I am always getting the 'eckey' as NULL. Could you please also share the contents of '.cer' file? I have the raw public key like this: 4bb5f0c58cc71806ec4d228b730dd252947e679cce05f71d434787fe228f14c799cf8965780bb308aa722ac179bfa5fd57592a72cbdcfe89ab61ad5d77251186d. And the intention is to convert this public key to '.pem/.der' format. Any hints would be highly appreciated. – sree May 27 '19 at 13:21
  • 1
    If the openssl command line works as I commented then it's the same as the code. If the command line is not working for you then my code will not work as well as it's the same. Most likely you either don't have a EC public key only DER formatted file. If you drop the "-pubin" of the openssl command and it works then you have a private key in DER format. If this is the case you need to use d2i_ECPrivateKey_bio to load the certificate file. – Shane Powell May 27 '19 at 17:10
  • Thank you Shane! upvoted all your answers ;) I have the below public key: root@boss:/home/boss/workspace/sree# openssl ec -in private_key.pem -text -noout read EC key Private-Key: (256 bit) priv: b9:cd:04:cd:83:52:d2:41:67:b3:94:7e:45:c2:a7: 40:c5:17:ca:ba:f9:f5:8a:b3:34:a2:cf:e8:16:3a: cf:ff pub: 04:bb:5f:0c:58:cc:71:80:6e:c4:d2:28:b7:30:dd: 25:29:47:e6:79:cc:e0:5f:71:d4:34:78:7f:e2:28: f1:4c:79:9c:f8:96:57:80:bb:30:8a:a7:22:ac:17: 9b:fa:5f:d5:75:92:a7:2c:bd:cf:e8:9a:b6:1a:d5: d7:72:51:18:6d ASN1 OID: prime256v1 NIST CURVE: P-256 – sree May 28 '19 at 08:00
  • public key formatting: x: 04bb5f0c58cc71806ec4d228b730dd252947e679cce05f71d434787fe228f14c7 (prime256pubkey.cer) y: 99cf8965780bb308aa722ac179bfa5fd57592a72cbdcfe89ab61ad5d77251186d formated x: 02 04bb5f0c58cc71806ec4d228b730dd252947e679cce05f71d434787fe228f14c7 formated as per :https://davidederosa.com/basic-blockchain-programming/elliptic-curve-keys/ – sree May 28 '19 at 08:01
  • tried running ur command on the formated x, but the conversion to PEM failed. below is the trace: openssl ec -in ./prime256pubkey.cer -pubin -inform der -pubout -outform pem -out ./prime256pubkey.pem – sree May 28 '19 at 08:03
  • read EC key unable to load Key 140076986079040:error:0D07209B:asn1 encoding routines:ASN1_get_object:too long:crypto/asn1/asn1_lib.c:91: 140076986079040:error:0D068066:asn1 encoding routines:asn1_check_tlen:bad object header:crypto/asn1/tasn_dec.c:1118: 140076986079040:error:0D07803A:asn1 encoding routines:asn1_item_embed_d2i:nested asn1 error:crypto/asn1/tasn_dec.c:290:Type=X509_ALGOR 140076986079040:error:0D08303A:asn1 encoding routines:asn1_template_noexp_d2i:nested asn1 error:crypto/asn1/tasn_dec.c:627:Field=algor, Type=X509_PUBKEY – sree May 28 '19 at 08:03
1

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 .

dave_thompson_085
  • 24,048
  • 4
  • 34
  • 52
  • Thank you so much dave_thompson_085! I have compiled and run the code and it worked like a charm, perfect art! – sree May 29 '19 at 09:08
  • Hello dave_thompson_085, would you mind sharing few insights into openssl API's? good documentation probably. I am a beginner to cybersecurity and cryptography. It would be great if you can suggest some great materials to start with. Thanks in advance. – sree May 30 '19 at 07:58
  • sree: Stack is explicitly designed to NOT be a discussion forum and requests for off-site resources are officially off-topic. But I'm a traditionalist and use the man pages and source especially the source for `apps/*.c` and `ssl/**/*.c`. There is a wiki at wiki.openssl.org but I don't find it very useful; YMMV. – dave_thompson_085 May 31 '19 at 08:47