Roland's homepage

My random knot in the Web

Examining an OpenSSH ECDSA public key

Intrigued by this question, I investigated the structure of an OpenSSH ECDSA public key.

First, a test key is generated.

> ssh-keygen -t ecdsa -f testkey
Generating public/private ecdsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in testkey
Your public key has been saved in
The key fingerprint is:
The key's randomart image is:
+---[ECDSA 256]---+
|==.              |
|O++.   o         |
|=BX+    =        |
|=*+*o. . +       |
|ooo=o . S .      |
|+ *.   o .       |
|== o    .        |
|E.+              |
|.+.              |

The public key contains a single line which consists of three pieces;

  1. The name of the key algorithm, ecdsa-sha2-nistp256,
  2. The key material in base64 encoding,
  3. A comment in the form of user@hostname.domain.

The key material is what is interesting. Let’s look at the beginning of the key;

In [1]: import base64

In [3]: key = "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdH" \
...: "AyNTYAAABBBCFeupSBqgm0Glpskrctk/iyRcmdzRLny" \
...: "iept0cIXtP4XygWiiptxcrvJ3iFhyYxxV6a26gkvn8Ub2QGn5k1gsE=";

In [7]: base64.b64decode(key)[:40]
Out[7]: b'\x00\x00\x00\x13ecdsa-sha2-nistp256\x00\x00\x00\x08nistp256\x00\x00\x00A\x04'

It seems that the 4-byte sequences that are mostly filled with zeroes could be big-endian integers. Let’s test that hypothesis;

In [8]: import struct

In [9]: bkey = base64.b64decode(key);

In [10]: struct.unpack(">i", bkey[0:4])
Out[10]: (19,)

In [11]: len("ecdsa-sha2-nistp256")
Out[11]: 19

In [12]: struct.unpack(">i", bkey[23:27])
Out[12]: (8,)

In [13]: len("nistp256")
Out[13]: 8

In [14]: 4+19+4+8+4
Out[14]: 39

In [15]: len(bkey)-39
Out[15]: 65

In [19]: struct.unpack(">i", bkey[35:39])
Out[19]: (65,)

The 4-byte sequences are indeed the lengths of the data that follows.

The first two data pieces are simply strings. That means that the final data piece must be the public key.

The key starts with 0x04. According to this answer, that value indicates a “non-compressed point”. It is followed by the 256-bit X and Y coordinates of the starting point on the elliptic curve.

In [31]: import binascii

In [32]: binascii.hexlify(bkey[40:40+32])
Out[32]: b'215eba9481aa09b41a5a6c92b72d93f8b245c99dcd12e7ca27a9b747085ed3f8'

In [33]: binascii.hexlify(bkey[40+32:])
Out[33]: b'5f28168a2a6dc5caef277885872631c55e9adba824be7f146f64069f993582c1'

All in all an interesting and instructive exploration.

For comments, please send me an e-mail.

←  Python & standard output redirection on ms-windows Getting the first or last item from a dictview without a list  →