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 .