Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert an ECDSA key to PEM format

Tags:

openssl

ecdsa

I have a private raw key of myetherwallet with a passphrase "testwallet", now I am trying to convert it to a PEM format using OpenSSL following this answer.

echo "a140bd507a57360e2fa503298c035854f0dcb248bedabbe7a14db3920aaacf57" | xxd -r -p - | openssl ec -inform der -pubin -noout -passin pass:testwallet -text

But this error appears:

read EC key
unable to load Key
140084694296480:error:0D06B08E:asn1 encoding routines:ASN1_D2I_READ_BIO:not enough data:a_d2i_fp.c:247:

UPDATE: I don't have the public key, instead I want to generate it so later I can also generate the Ethereum address corresponds..

like image 753
H Aßdøµ Avatar asked Jan 04 '18 18:01

H Aßdøµ


2 Answers

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 
like image 83
dave_thompson_085 Avatar answered Nov 05 '22 17:11

dave_thompson_085


Elliptic Curve Private Key Format:

ECPrivateKey ::= SEQUENCE {
 version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
 privateKey     OCTET STRING,
 parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
 publicKey  [1] BIT STRING OPTIONAL
}

So publicKey is OPTIONAL and theoretically can be missed.

Here is example of my DER secp256k1 private key:

30740201 01042092 E768CB72 0DC16924 27D156DB 39630748 0D1507B9 A4958450
2574B9A0 922F4BA0 0706052B 8104000A A1440342 00041954 9737B704 D1789A57
82E3430E 8259F904 71326081 054854D2 A5D096F9 686D05B0 30D98BA3 C60C056E
204CEF61 C0AC5B53 A9A6B9A0 5AFF9DA2 6CA4B65B 2E84

Trying to decompose:

$ openssl asn1parse -inform DER -in <(echo "30740201 01042092 E768CB72 0DC16924 27D156DB 39630748 0D1507B9 A4958450 2574B9A0 922F4BA0 0706052B 8104000A A1440342 00041954 9737B704 D1789A57 82E3430E 8259F904 71326081 054854D2 A5D096F9 686D05B0 30D98BA3 C60C056E 204CEF61 C0AC5B53 A9A6B9A0 5AFF9DA2 6CA4B65B 2E84" | xxd -r -p)

ASN.1 parsing result:

 0:d=0  hl=2 l= 116 cons: SEQUENCE          
 2:d=1  hl=2 l=   1 prim: INTEGER           :01
 5:d=1  hl=2 l=  32 prim: OCTET STRING      [HEX DUMP]:92E768CB720DC1692427D156DB396307480D1507B9A49584502574B9A0922F4B
39:d=1  hl=2 l=   7 cons: cont [ 0 ]        
41:d=2  hl=2 l=   5 prim: OBJECT            :secp256k1
48:d=1  hl=2 l=  68 cons: cont [ 1 ]        
50:d=2  hl=2 l=  66 prim: BIT STRING  

Detailed (see https://bitcoin.stackexchange.com/a/66622/22979):

30 - ASN.1
74 - Length of all following bytes (116 bytes)

  02 - Type (integer)
  01 - Length of integer (1 byte)
  01 - Value of integer (1)

  04 - Type (octet string)
  20 - Length of string (32 bytes)
  92E768CB720DC1692427D156DB396307480D1507B9A49584502574B9A0922F4B - Private Key

  A0 - Tag 0
  07 - Length of tag (7 bytes)
  06 - Type (Object ID)
  05 - Length of the Object ID (5 bytes)
  2b 81 04 00 0a - The object ID of the curve secp256k1

  A1 - Tag 1
  44 - Length of tag (68 bytes)
  03 - Type – Bit string
  42 - Length of the bit string (66 bytes)
  00 - ???
  04 - Uncompressed Public Key
  19549737B704D1789A5782E3430E8259F90471326081054854D2A5D096F9686D - Public Key X coord
  05B030D98BA3C60C056E204CEF61C0AC5B53A9A6B9A05AFF9DA26CA4B65B2E84 - Public Key Y coord

I removed Public Key object and fixed ASN.1 length from 116 bytes (0x74) to 46 bytes (0x2e):

$ openssl asn1parse -inform DER -in <(echo "302E020101042092E768CB720DC1692427D156DB396307480D1507B9A49584502574B9A0922F4BA00706052B8104000A" | xxd -r -p)

Got result:

 0:d=0  hl=2 l=  46 cons: SEQUENCE          
 2:d=1  hl=2 l=   1 prim: INTEGER           :01
 5:d=1  hl=2 l=  32 prim: OCTET STRING      [HEX DUMP]:92E768CB720DC1692427D156DB396307480D1507B9A49584502574B9A0922F4B
39:d=1  hl=2 l=   7 cons: cont [ 0 ]        
41:d=2  hl=2 l=   5 prim: OBJECT            :secp256k1

Trying to get Public Key:

$ openssl ec -inform DER -in <(echo "302E020101042092E768CB720DC1692427D156DB396307480D1507B9A49584502574B9A0922F4BA00706052B8104000A" | xxd -r -p)

Result:

read EC key
writing EC key
-----BEGIN EC PRIVATE KEY-----
MC4CAQEEIJLnaMtyDcFpJCfRVts5YwdIDRUHuaSVhFAldLmgki9LoAcGBSuBBAAK
-----END EC PRIVATE KEY-----

One more try:

$ openssl ec -inform DER -text -in <(echo "302E020101042092E768CB720DC1692427D156DB396307480D1507B9A49584502574B9A0922F4BA00706052B8104000A" | xxd -r -p)

Result:

read EC key
Segmentation fault: 11

I used OSX system openssl – looks like it is LibreSSL 2.2.7.

Added: Reported a bug to LibreSSL: https://github.com/libressl-portable/portable/issues/395 UPDATE: In latest macOS 10.15.1 preinstalled openssl (LibreSSL 2.8.3) have this bug fixed.

Then I installed latest openssl: brew install openssl

/usr/local/Cellar/openssl/1.0.2n/bin/openssl ec -inform DER -text -noout -in <(echo "302E020101042092E768CB720DC1692427D156DB396307480D1507B9A49584502574B9A0922F4BA00706052B8104000A" | xxd -r -p)

And got:

read EC key
Private-Key: (256 bit)
priv:
    00:92:e7:68:cb:72:0d:c1:69:24:27:d1:56:db:39:
    63:07:48:0d:15:07:b9:a4:95:84:50:25:74:b9:a0:
    92:2f:4b
pub: 
    04:19:54:97:37:b7:04:d1:78:9a:57:82:e3:43:0e:
    82:59:f9:04:71:32:60:81:05:48:54:d2:a5:d0:96:
    f9:68:6d:05:b0:30:d9:8b:a3:c6:0c:05:6e:20:4c:
    ef:61:c0:ac:5b:53:a9:a6:b9:a0:5a:ff:9d:a2:6c:
    a4:b6:5b:2e:84
ASN1 OID: secp256k1

Final solution:

$ /usr/local/Cellar/openssl/1.0.2n/bin/openssl ec -inform DER -text -noout -in <(cat <(echo -n "302e0201010420") <(echo -n "***") <(echo -n "a00706052b8104000a") | xxd -r -p) 2>/dev/null | tail -6 | head -5 | sed 's/[ :]//g' | tr -d '\n' && echo

Replace *** with a hexademical private key.

like image 4
k06a Avatar answered Nov 05 '22 16:11

k06a