4

I'm using OpenSSL to generate EC key pairs. I notice that the length of the keys as displayed by OpenSSL are exactly one byte longer than expected for both private and public Key.

The 00 (first Byte of private key) is ASN.1 encoded integer, it ensures the value is interpreted as non-negative and the first Byte of the public Key 04 is for the uncompressed Key.

The private key is 33 bytes (not 32 bytes), and the public key is 65 bytes, (not 64 bytes)

Private-Key: (256 bit)

priv:

  00:b5:15:94:05:96:a0:6b:03:4e:9d:49:01:ce:50:
  71:f7:6a:fe:28:24:2a:24:23:b2:0e:d6:a0:b5:6a:
  0f:65:d3

pub:

  04:db:b4:97:f3:5d:17:45:e5:79:4f:db:c2:bd:ea:
  19:2c:fd:3a:dc:bc:19:49:91:e1:e1:59:9f:0c:7a:
  9e:e7:97:c1:ad:4c:a8:53:b6:b0:af:d8:7e:58:34:
  90:e1:d1:0b:85:9d:89:e1:4c:7c:b8:b6:2d:27:70:
  62:66:9e:b2:ac

ASN1 OID: prime256v1

NIST CURVE: P-256

How can I delete the first Byte for the private and public key? or otherwise, How can I adjust the size of the private to 32 Bytes and public key to 64 Bytes?

Does the leading 00 change the value of the number?

I am using OpenSSL 1.0.2k 26 Jan 2017

1
  • Could you add the PEM version of your key to your post? Commented Jun 26, 2017 at 17:29

2 Answers 2

7

I'm not sure why you want to cut off bytes. So I'm not sure if this is a good answer to your question here goes anyway.

Here's what I found out from reading the RFCs/experimenting.

How can I adjust the size of the private to 32 Bytes and public key to 64 Bytes?

You don't have to. They are already in that form. It's just a bit confusing to read from the openssl printout.

Does the leading 00 change the value of the number?

The answer is "No" if you were going to parse it as an unsigned int anyway. (And "yes" if you were (erroneously) going to parse it as a signed int. In that case you need to keep the 0x00 prefix.)

TLDR: The leading 0x00 is a SIGN BYTE that openssl adds for the printout only. It is NOT encoded in the certificate itself. And I guess you can cut of the leading byte (one of 0x02, 0x03 or 0x04) of the pubkey because it just stores info on whether compressed or noncompressed encoding is used.

Sample key with leading nullbyte

Here's an example of a random key I generated:

$ cat leading-nullbyte.pem
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIN/37NFyCvL7brp4zljP83sNj1PvtFsp8dMR86EDwLZUoAoGCCqGSM49
AwEHoUQDQgAEK0acP7Ml6fgKy35YE7JGVP7AmNy7oJ6gl4QIqiwiSExbr4iDPfxT
81550HxXoiQiBJXBJxhgXYpIcJVmFGk20w==
-----END EC PRIVATE KEY-----

ec printout leading-nullbyte.pem

This key is shown with the "00:" prefix for the private key.

At least when using the openssl pretty printer...

$ openssl ec -noout -text -in leading-nullbyte.pem
read EC key
Private-Key: (256 bit)
priv:
    00:df:f7:ec:d1:72:0a:f2:fb:6e:ba:78:ce:58:cf:
    f3:7b:0d:8f:53:ef:b4:5b:29:f1:d3:11:f3:a1:03:
    c0:b6:54
pub:
    04:2b:46:9c:3f:b3:25:e9:f8:0a:cb:7e:58:13:b2:
    46:54:fe:c0:98:dc:bb:a0:9e:a0:97:84:08:aa:2c:
    22:48:4c:5b:af:88:83:3d:fc:53:f3:5e:79:d0:7c:
    57:a2:24:22:04:95:c1:27:18:60:5d:8a:48:70:95:
    66:14:69:36:d3
ASN1 OID: prime256v1
NIST CURVE: P-256

asn1parse leading-nullbyte.pem

...however when actually looking inside the ASN1 encoding there is no 00: prefix. It just starts with df: right away. And also: length is given as 32 (l= 32). Not 33.

    5:d=1  hl=2 l=  32 prim:  OCTET STRING
      0000 - df f7 ec d1 72 0a f2 fb-6e ba 78 ce 58 cf f3 7b   ....r...n.x.X..{

Here's those lines in context:

$ openssl asn1parse -i -dump -in leading-nullbyte.pem
    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
      0000 - df f7 ec d1 72 0a f2 fb-6e ba 78 ce 58 cf f3 7b   ....r...n.x.X..{
      0010 - 0d 8f 53 ef b4 5b 29 f1-d3 11 f3 a1 03 c0 b6 54   ..S..[)........T
   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
      0000 - 00 04 2b 46 9c 3f b3 25-e9 f8 0a cb 7e 58 13 b2   ..+F.?.%....~X..
      0010 - 46 54 fe c0 98 dc bb a0-9e a0 97 84 08 aa 2c 22   FT............,"
      0020 - 48 4c 5b af 88 83 3d fc-53 f3 5e 79 d0 7c 57 a2   HL[...=.S.^y.|W.
      0030 - 24 22 04 95 c1 27 18 60-5d 8a 48 70 95 66 14 69   $"...'.`].Hp.f.i
      0040 - 36 d3                                             6.

So the leading 0x00 is not actually encoded in the certificate file. While I have not taken a look at the openssl source code in regards to that I'm leaning towards calling this a printout bug.

According to RFC5915 (I think) an EC key is an UNSIGNED integer.

ASN.1 Editor "leading-nullbyte.pem": no actual null byte

As an afterthought: Here's also an actual hexdump of the DER contents. And again: no actual 0x00 in the file. asn1editor.leading-nullbyte.pem.png

Sample key WITHOUT leading nullbyte

Here's another random key I generated. It does NOT have the 0x00 prefix when using the openssl ec pretty printer.

So this tells me that that 0x00 prefix is not necessarily there for each EC privkey.

$ cat no-leading-nullbyte.pem
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIAGcFoPfqfFZ5TDv71ZBBCctapiVKwZikE8HfKf61V1DoAoGCCqGSM49
AwEHoUQDQgAENPjKv9vobJnz1FSlSu5cNPyPwCvcsMmIr5HH92C+mZdVtanHwlTm
29IwOE5lSE8KMywIJs8pLCvX79kJhZ/upg==
-----END EC PRIVATE KEY-----

ec printout no-leading-nullbyte.pem

$ openssl ec -noout -text -in no-leading-nullbyte.pem
read EC key
Private-Key: (256 bit)
priv:
    01:9c:16:83:df:a9:f1:59:e5:30:ef:ef:56:41:04:
    27:2d:6a:98:95:2b:06:62:90:4f:07:7c:a7:fa:d5:
    5d:43
pub:
    04:34:f8:ca:bf:db:e8:6c:99:f3:d4:54:a5:4a:ee:
    5c:34:fc:8f:c0:2b:dc:b0:c9:88:af:91:c7:f7:60:
    be:99:97:55:b5:a9:c7:c2:54:e6:db:d2:30:38:4e:
    65:48:4f:0a:33:2c:08:26:cf:29:2c:2b:d7:ef:d9:
    09:85:9f:ee:a6
ASN1 OID: prime256v1
NIST CURVE: P-256

asn1parse no-leading-nullbyte.pem

Here again length is given as 32:

    5:d=1  hl=2 l=  32 prim:  OCTET STRING
      0000 - 01 9c 16 83 df a9 f1 59-e5 30 ef ef 56 41 04 27   .......Y.0..VA.'

Full parse:

$ openssl asn1parse -i -dump -in no-leading-nullbyte.pem
    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
      0000 - 01 9c 16 83 df a9 f1 59-e5 30 ef ef 56 41 04 27   .......Y.0..VA.'
      0010 - 2d 6a 98 95 2b 06 62 90-4f 07 7c a7 fa d5 5d 43   -j..+.b.O.|...]C
   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
      0000 - 00 04 34 f8 ca bf db e8-6c 99 f3 d4 54 a5 4a ee   ..4.....l...T.J.
      0010 - 5c 34 fc 8f c0 2b dc b0-c9 88 af 91 c7 f7 60 be   \4...+........`.
      0020 - 99 97 55 b5 a9 c7 c2 54-e6 db d2 30 38 4e 65 48   ..U....T...08NeH
      0030 - 4f 0a 33 2c 08 26 cf 29-2c 2b d7 ef d9 09 85 9f   O.3,.&.),+......
      0040 - ee a6                                             ..

ASN.1 Editor no-leading-nullbyte.pem

asn1editor.no-leading-nullbyte.pem.png

Extra: How do I know that the 0x00 is a sign byte?

... by experimentation:

I generated a thousand keys like so:

$ for i in $(seq -w 1000); do echo $i; openssl ecparam -name prime256v1 -genkey -noout > key.$i.pem; done

And then I sorted and counted them by first byte like so:

$ for i in $(seq -w 1000); do openssl ec -noout -text -in key.$i.pem 2>/dev/null | grep '^priv:' -A1 | tail -1; done | sed 's/ *//' | sed 's/\(..\).*/\1/' | sort | uniq -c

    496 00
      3 01
      3 02
      4 03
      5 04
      4 05
      5 06
      3 07
      1 08
      3 09
      1 0a
      3 0b
      2 0c
      2 0d
      3 0e
      4 0f
      4 11
      6 12
      1 14
      7 15
      3 16
      7 17
      4 18
      1 19
      6 1a
      3 1b
      3 1c
      3 1d
     11 1e
      3 1f
      6 20
      4 21
      2 22
      3 23
      8 24
      3 25
      2 26
      7 27
      3 28
      7 29
      4 2a
      4 2b
      9 2c
      2 2d
      5 2e
      2 2f
      5 30
      7 31
      6 32
      3 33
      6 34
      8 35
      5 36
      2 37
      2 38
      5 39
      2 3a
      1 3b
      4 3c
      3 3d
      2 3e
      2 3f
      2 40
      3 41
      5 42
      5 43
      3 44
      4 45
      5 46
      4 47
      5 48
      5 49
      5 4a
      5 4b
      8 4c
      6 4d
      3 4e
      5 4f
      4 50
      2 51
      4 52
      2 53
      2 54
      3 55
      7 56
      2 57
      5 58
      3 59
      3 5a
      5 5b
      2 5d
      1 5e
      9 5f
      3 60
      3 61
      3 62
      6 63
      3 64
      1 65
      7 66
      4 67
      3 68
      4 69
      4 6a
      3 6b
      6 6c
      4 6d
      3 6e
      3 6f
      7 70
      4 71
      2 72
      5 73
      2 74
      1 75
      5 76
      3 77
     10 78
      2 79
      7 7a
      5 7b
      6 7c
      6 7d
      5 7e
      5 7f

The highest byte is 0x7f. And that is the final byte that still has a 0 as the most significant bit. (I won't post all thousand of them here but all the privkeys that had 0x00 as their first byte then had their second byte >= 0x80. (There were no privkeys that started with 0x0000.) And all the privkeys did NOT have 0x00 as their first byte then had their second byte <= 0x7f.)

5
  • The file here is a privatekey file, not a certificate, and as your decode shows has the private value ($d$) as OCTET STRING not INTEGER. The extra 00 on -text seems to be fixed in 1.1.0. If the publickey is compressed, removing the 02 or 03 prefix makes it impossible to recover the correct value unambiguously; for verify you can try both, but for encrypt (e.g. ECDH, ECIES) there is a 50% chance of getting it wrong and making your data unrecoverable. Removing the 04 from uncompressed makes it unusable with standard software, but if you write all your own software you can make it work. Commented Jun 28, 2017 at 1:29
  • @dave_thompson_085 Thanks. 1.: Re OctetString-vs-Int: Yup. Rfc5915 page 2 says that it's an unsigned int represented as octet string. 2.: Re v1.1.0. Thanks. Didn't know that. 3.: Re. removing header-byte: yes, I agree. You then need another mechanism to find out the encoding. (Maybe by length of the bit string.) Commented Jun 29, 2017 at 9:01
  • @StackzOfZtuff thank you for the explanation! that was helpful
    – aagargoura
    Commented Jun 30, 2017 at 11:37
  • I have other question, I saved my keys using PEM_write_ECPrivateKey() to get the private key stored in the disk! The key contain both private and public.. is there any way to get only the private key stored?
    – aagargoura
    Commented Jun 30, 2017 at 11:40
  • @Med: theoretically, yes, since the RFC specifies he pubkey part as optional. But, like Dave said, you have trouble finding software that can handle that customized storage format. Commented Jun 30, 2017 at 12:52
0

How can I delete the first Byte for the private and public key? or otherwise, How can I adjust the size of the private to 32 Bytes and public key to 64 Bytes?

Upgrading to openssl v1.1.1 will solve the first half of the problem - i.e. it won't prepend the 00 byte to the private key:

$ openssl version
OpenSSL 1.1.1  11 Sep 2018
$ openssl ecparam -name prime256v1 -genkey -noout | openssl ec -noout -text
read EC key
Private-Key: (256 bit)
priv:
    f5:2e:af:be:bc:89:44:c6:d9:2c:55:1d:fe:08:d4:
    1d:d1:39:f7:7e:0f:f7:7e:fe:54:fc:a4:d7:46:5a:
    f5:9e
pub:
    04:03:29:2a:b6:5a:81:65:62:51:a6:ff:41:c2:0b:
    51:57:19:24:81:7e:91:4e:b3:cc:17:3f:d2:b6:e3:
    af:f1:f8:c0:91:e0:7d:b9:f0:9f:09:5d:f5:de:fa:
    f3:6e:56:35:f5:f8:50:92:c8:e2:fb:41:01:b2:0e:
    f1:d5:3f:38:61
ASN1 OID: prime256v1
NIST CURVE: P-256

However, the 04 byte, representing an uncompressed public key, is still prepended to the public key.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .