How to convert an ECDSA key to PEM format

You are claiming your raw key is in OpenSSL’s DER format, which it isn’t. Also you are claming a private key is a public key, which it isn’t, and claiming it’s password-encrypted which is wrong either way: public keys are never encrypted and private keys in OpenSSL’s ‘traditional’ aka ‘legacy’ algorithm-specific DER formats (for ECC, defined by SECG SEC1) cannot be encrypted. (OTOH private keys in PKCS8 format can be password-encrypted in either DER or PEM, although PEM is more convenient. And FWIW PKCS12 format is always password-encrypted, and always DER.)

An ECC (ECDSA, ECDH, ECMQV, etc) key is always relative to some ‘curve’ (more exactly, prime-order subgroup over a curve with an identified generator aka base point). For bitcoin this is secp256k1, but your question doesn’t say it’s limited to bitcoin and this answer would require modification for other applications using other curves.

If you also have the public key (as an uncompressed point), you can simply use the solution from https://bitcoin.stackexchange.com/questions/66594/signing-transaction-with-ssl-private-key-to-pem . Concatenate the hex strings:

  a pre_string : 30740201010420
  the privkey  : (32 bytes as 64 hexits) 
  a mid_string : a00706052b8104000aa144034200 (identifies secp256k1) 
  the pubkey   : (65 bytes as 130 hexits)

and then either convert the hex to binary and read as DER, or convert the hex (probably via binary) to base64 and wrap with -----BEGIN/END EC PRIVATE KEY----- lines to make it PEM.

If you don’t have the public key, you can modify this slightly. Concatenate the hex strings

302e0201010420 privkey_32bytes_64hexits a00706052b8104000a 

and convert to binary, then read into openssl ec -inform d . Note OpenSSL will derive the public key from the private key given the curve, but not actually store it in the PEM output, so reading with software other than OpenSSL is not guaranteed. You might need to use openssl ec -text [-noout] (on either PEM or DER input as convenient) to get the public key value, then go back and create the fuller encoding that includes the public key as above.


ADDED: since you seem not to comprehend the words in the answer, I’ll lay this out in as much detail as I can.

The value a140bd507a57360e2fa503298c035854f0dcb248bedabbe7a14db3920aaacf57 is the raw private key represented in hex. A secp256k1 private value is 32 bytes in binary; when binary is represented in hex each byte takes two hex digits, so 32 bytes takes 64 hex digits. All of this value is the raw private key. There is no part consisting of 25 digits OR 25 bytes that has any useful meaning whatever. Do not take any 25-anything part of this value.

To construct the OpenSSL/SECG representation of a private key with no public key, put the hex string representing the private key — all of it, without modification — between the two other hex strings I showed as the second option:

 302e0201010420 a140bd507a57360e2fa503298c035854f0dcb248bedabbe7a14db3920aaacf57 a00706052b8104000a 

Then convert this combined hex string to binary, and read the result into openssl ec -inform d:

$ echo 302e0201010420 a140bd507a57360e2fa503298c035854f0dcb248bedabbe7a14db3920aaacf57 a00706052b8104000a | xxd -r -p >48101258.1
$ openssl ec -inform d <48101258.1
read EC key
writing EC key
-----BEGIN EC PRIVATE KEY-----
MC4CAQEEIKFAvVB6VzYOL6UDKYwDWFTw3LJIvtq756FNs5IKqs9XoAcGBSuBBAAK
-----END EC PRIVATE KEY-----

The result is PEM format — but PEM format not including the public key, which you indicate you want. To see the fields including the derived public key, add -text; to see only the fields and not the PEM output, add -noout:

$ openssl ec -inform d <48101258.1 -text -noout
read EC key
Private-Key: (256 bit)
priv:
    a1:40:bd:50:7a:57:36:0e:2f:a5:03:29:8c:03:58:
    54:f0:dc:b2:48:be:da:bb:e7:a1:4d:b3:92:0a:aa:
    cf:57
pub:
    04:20:ea:6d:8c:e7:bc:bb:48:33:69:b2:91:1c:75:
    e5:60:2a:34:28:be:44:96:e9:7f:14:ad:52:fd:4a:
    6a:a0:e3:60:83:9c:6e:db:32:2a:22:55:7c:70:1e:
    d0:fa:1e:06:cf:57:4f:be:17:bd:6a:85:51:69:c5:
    65:96:72:cf:a9
ASN1 OID: secp256k1

Now if you want a PEM-format key including the public key, take both the hex strings for the private key (all 64 digits) AND the newly-shown hex value for the public key, and plug them in to my first option. Also note an ECC public key is a curve point which can be in two forms, compressed or uncompressed; the form generated here is uncompressed. If you need compressed, I’ll add that later. A secp256k1 point in uncompressed form is 65 bytes, represented in hex as 130 hex digits. (Which openssl ec formats as 4 lines each of 15 bytes with 5 bytes left over.)

$ echo 30740201010420 a140bd507a57360e2fa503298c035854f0dcb248bedabbe7a14db3920aaacf57 a00706052b8104000aa144034200 \
> 04:20:ea:6d:8c:e7:bc:bb:48:33:69:b2:91:1c:75: e5:60:2a:34:28:be:44:96:e9:7f:14:ad:52:fd:4a: \
> 6a:a0:e3:60:83:9c:6e:db:32:2a:22:55:7c:70:1e: d0:fa:1e:06:cf:57:4f:be:17:bd:6a:85:51:69:c5: \
> 65:96:72:cf:a9 | xxd -r -p >48101258.2
$ # note xxd -r -p ignores the colons; other hex programs may need them removed instead
$ openssl ec -inform d <48101258.2
read EC key
writing EC key
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIKFAvVB6VzYOL6UDKYwDWFTw3LJIvtq756FNs5IKqs9XoAcGBSuBBAAK
oUQDQgAEIOptjOe8u0gzabKRHHXlYCo0KL5Elul/FK1S/UpqoONgg5xu2zIqIlV8
cB7Q+h4Gz1dPvhe9aoVRacVllnLPqQ==
-----END EC PRIVATE KEY-----

ADDED 2019-02 for DavidS: as correctly shown in k06a’s answer

  • the first part of my midstring (or the entire suffix for my private-only option) a00706052b8104000a is a context-tag and length a007 for an OID tag and length 0605 containing 2b8104000a which is 1.3.132.0.10 which is secp256k1 and

  • the remainder of my midstring a144034200 is a context tag and length containing the tag length and unused-bits header for a BITSTRING which is the raw publickey as an uncompressed point.

To do secp256r1 aka P-256 or prime256v1 instead, you need to change the AlgId.OID to 1.2.840.10045.3.1.7 which is encoded as a00a 0608 2a8648ce3d030107. The privatekey and publickey values for p256r1 are the same sizes as for p256k1, but the AlgId is longer, so you also need to change the length of the outer SEQUENCE giving

30770201010420 privatekey32bytes # note 77 
a00a06082a8648ce3d030107 a144034200 publicpoint65bytes 

Leave a Comment