cozos
cozos

Reputation: 945

Are OpenSSL generated pem keys compatible with Erlang Crypto

I have generated a private key using OpenSSL:

ARWIN-TIO:/tmp$ openssl ecparam -name prime256v1 -genkey -noout -out key.pem
ARWIN-TIO:/tmp$ cat key.pem
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIINcLTcsL/VhTBEsp1gRgHtO9lLzypm7oYjwViz3bWZCoAoGCCqGSM49
AwEHoUQDQgAE8kuZsfDhQdkkYjVRla3ShxAlsbLwOt8jUsKyebB7GGWxnBiDqRoB
bSxkkd+APIM/4+lYwIDAx5+EmIIuUIRdcA==
-----END EC PRIVATE KEY-----

And I'm struggling to use it in Erlang's Crypto module:

ARWIN-TIO:/tmp$ erl
Erlang/OTP 21 [erts-10.3.5.6] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Eshell V10.3.5.6  (abort with ^G)
1> Message = <<"Hello">>.
<<"Hello">>
2> PrivateKey = base64:decode("MHcCAQEEIINcLTcsL/VhTBEsp1gRgHtO9lLzypm7oYjwViz3bWZCoAoGCCqGSM49AwEHoUQDQgAE8kuZsfDhQdkkYjVRla3ShxAlsbLwOt8jUsKyebB7GGWxnBiDqRoBbSxkkd+APIM/4+lYwIDAx5+EmIIuUIRdcA==").
<<48,119,2,1,1,4,32,131,92,45,55,44,47,245,97,76,17,44,
  167,88,17,128,123,78,246,82,243,202,153,...>>
3> Signature = crypto:sign(ecdsa, sha256, Message, [PrivateKey, prime256v1]).
** exception error: badkey
     in function  crypto:sign/5
        called as crypto:sign(ecdsa,sha256,<<"Hello">>,
                              [<<48,119,2,1,1,4,32,131,92,45,55,44,47,245,97,76,17,44,
                                 167,88,17,128,123,78,...>>,
                               prime256v1],
                              [])

When I generate the keys using Erlang's Crypto module, it works:

ARWIN-TIO:/tmp$ erl
Erlang/OTP 21 [erts-10.3.5.6] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Eshell V10.3.5.6  (abort with ^G)
1> Message = <<"Hello">>.
<<"Hello">>
2> {PublicKey, PrivateKey} = crypto:generate_key(ecdh, crypto:ec_curve(prime256v1)).
{<<4,149,38,43,104,132,214,232,147,174,88,185,96,250,185,
   181,170,8,231,61,255,134,143,255,4,136,249,9,...>>,
 <<130,195,50,229,108,51,72,27,219,145,250,244,116,3,52,
   234,13,60,148,175,112,192,140,110,232,46,116,...>>}
3> Signature = crypto:sign(ecdsa, sha256, Message, [PrivateKey, prime256v1]).
<<48,70,2,33,0,252,243,117,254,110,176,232,185,121,156,93,
  105,74,115,115,247,83,82,17,32,167,254,223,74,...>>

I noticed that the Erlang private keys are way shorter than the OpenSSL one:

4> base64:encode(PrivateKey).
<<"gsMy5WwzSBvbkfr0dAM06g08lK9wwIxu6C50rwKaBvw=">>

Which is way shorter than the one generated with "openssl ecparam -name prime256v1 -genkey -noout -out key.pem"

Why are the OpenSSL keys different/not working with Erlang's Crypto module? How can I make them compatible?

Thanks.

Upvotes: 1

Views: 331

Answers (2)

cozos
cozos

Reputation: 945

With Maarten's guidance I managed to figure it out (did not know that my OpenSSL Private Key was "X9.62" encoded; I thought it was only in base64). It turns out that you need to use the public_key module instead of the crypto module.

Here is an example of how to use OpenSSL keys w/ Erlang.

Generate a private key:

ARWIN-TIO:/tmp$ openssl ecparam -name prime256v1 -genkey -noout -out private_key.pem
ARWIN-TIO:/tmp$ cat private_key.pem
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIGpSqNErkMHbjdeBQBI6NdlK8QgFluCJvGhkt3g5n5zboAoGCCqGSM49
AwEHoUQDQgAE43/FB66JPZ2JCbL+e7b4TVpDJAOeeaHmy7NPYL1cOVJFRBux91M4
QYBu1s5DigaQdi/Qz7KK/Yr5EMktgulXHA==
-----END EC PRIVATE KEY-----

And a corresponding public key:

ARWIN-TIO:/tmp$ openssl ec -in private_key.pem -pubout -out public_key.pub
ARWIN-TIO:/tmp$ cat public_key.pub
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE43/FB66JPZ2JCbL+e7b4TVpDJAOe
eaHmy7NPYL1cOVJFRBux91M4QYBu1s5DigaQdi/Qz7KK/Yr5EMktgulXHA==
-----END PUBLIC KEY-----

Then in Erlang you sign a message like this:

(2020-01-02 20:51:56) ARWIN-TIO:/tmp$ erl
Erlang/OTP 21 [erts-10.3.5.6] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe] [dtrace]

Eshell V10.3.5.6  (abort with ^G)
1> Message = <<"My Secret Message">>,
1> 
1> PrivatePem = lists:nth(1, public_key:pem_decode(<<"-----BEGIN EC PRIVATE KEY-----
1> MHcCAQEEIGpSqNErkMHbjdeBQBI6NdlK8QgFluCJvGhkt3g5n5zboAoGCCqGSM49
1> AwEHoUQDQgAE43/FB66JPZ2JCbL+e7b4TVpDJAOeeaHmy7NPYL1cOVJFRBux91M4
1> QYBu1s5DigaQdi/Qz7KK/Yr5EMktgulXHA==
1> -----END EC PRIVATE KEY-----">>)),
1>
1> PrivateKey = public_key:pem_entry_decode(PrivatePem),
1> Signature = public_key:sign(Message, sha256, PrivateKey).
<<48,68,2,32,64,80,146,169,96,232,174,140,196,59,46,54,
  107,199,145,184,86,181,79,168,165,107,54,157,222,...>>

And you can verify it like this:

2> PublicPem = lists:nth(1, public_key:pem_decode(<<"-----BEGIN PUBLIC KEY-----
2> MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE43/FB66JPZ2JCbL+e7b4TVpDJAOe
2> eaHmy7NPYL1cOVJFRBux91M4QYBu1s5DigaQdi/Qz7KK/Yr5EMktgulXHA==
2> -----END PUBLIC KEY-----">>)),
2> 
2> PublicKey = public_key:pem_entry_decode(PublicPem),
2> VerifyResult = public_key:verify(Message, sha256, Signature, PublicKey),
2> VerifyResult.
true

Upvotes: 0

Maarten Bodewes
Maarten Bodewes

Reputation: 94038

What you are looking at is a X9.62 encoded private key that got PEM encoded in addition. You've removed the PEM header lines and base 64 decoded, so now you just have the X9.62 encoded private key left.

As you seem to be using OpenSSL, first decode the base64 so you can do:

openssl asn1parse -inform DER -in private_prime256v1.bin

Which will give you:

     0:d=0  hl=2 l= 119 cons: SEQUENCE          
     2:d=1  hl=2 l=   1 prim: INTEGER           :01
     5:d=1  hl=2 l=  32 prim: OCTET STRING      [HEX DUMP]:
        835C2D372C2FF5614C112CA75811807B4EF652F3CA99BBA188F0562CF76D6642
    39:d=1  hl=2 l=  10 cons: cont [ 0 ]        
    41:d=2  hl=2 l=   8 prim: OBJECT            :prime256v1
    51:d=1  hl=2 l=  68 cons: cont [ 1 ]        
    53:d=2  hl=2 l=  66 prim: BIT STRING        

You haven't clearly shown the full extend of the public / private keys, but it looks like they are just the "flat" representations of the secret S and, for the public key, the uncompressed point W.

So that would be the OCTET STRING in the parsed private key (the final BIT STRING contains the optional public key, the uncompressed point W):

835C2D372C2FF5614C112CA75811807B4EF652F3CA99BBA188F0562CF76D6642

So this explains the why. I don't exactly know how to parse structures like this, but you may not have to. It seems like Erlang already has a function that does this for you called public_key:pem_decode which should just take the entire openssl generated key. According to the documentation it should also parse private keys.

As I'm not a Erlang programmer I cannot test this easily, so please let me know.

Upvotes: 3

Related Questions