SSH keys

6 minute read

I suppose that you use SSH keys for password-less logins. I also assume that you heard about RSA, the most common public-key cryptosystem. I use consciously RSA keys mostly at work but they are used generally in many systems. I think that it’s good idea to know much more about them than: this is a private key – keep it and don’t share with any body, and this one it’s a public key – you can send it to anybody. In this post I’m going to find out more about keys. I will focus only on OpenSSH solution widely used on Linux.

Create keys

Let’s start from the beginning. I create small RSA keys – only 768 bits for tests purpose and to reduce output. In real life you should not go down under 2048 bits.

dirdival@pld:/tmp/ssh$ ssh-keygen -b 768 -t rsa -C "writing to /dev/null" -f /tmp/ssh/id_rsa_wtdn
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /tmp/ssh/id_rsa_wtdn.
Your public key has been saved in /tmp/ssh/id_rsa_wtdn.pub.
The key fingerprint is:
af:42:a8:f9:c2:d6:80:1c:b0:45:21:7d:ae:8f:1b:89 writing to /dev/null
The key's randomart image is:
+--[ RSA  768]----+
|.oo.             |
|..o .            |
|.o o             |
|..  .            |
|.... .  S        |
|.oo.. .  .       |
|E.oB .    .      |
|  B.o .  .       |
| ..+.  ..        |
+-----------------+

How you can see it’s easy and takes a few seconds. What we can find on the output of ssh-keygen? At least two things are obvious, we have here full paths to public and private keys.

dirdival@pld:/tmp/ssh$ file id_rsa_wtdn
id_rsa_wtdn: PEM RSA private key
dirdival@pld:/tmp/ssh$ file id_rsa_wtdn.pub
id_rsa_wtdn.pub: OpenSSH RSA public key

Let’s see them:

dirdival@pld:/tmp/ssh$ cat id_rsa_wtdn
-----BEGIN RSA PRIVATE KEY-----
MIIBywIBAAJhAL/faiDQ2Oy97TWTahzI7JNI3qRKaiQNyCpqhMlIVOy9FIQe+DDZ
1cLrsQYY/D9P/nJxnYLSMOkZLu/DDME2sKzBsRw09AQSRPREGylvnvx7Dp2xMkB7
/h6jCKPsJdruLQIDAQABAmB5HDVqB0mVjYCoG6eUCcNCaHGYNBxxK33YQCoWvyBT
2jmT99RjSWyjP5AasDSwZfW3KsG8a7kwRclty27aLE5OJksReDNhzryYQW41bNDz
OajMzjAx4lLd+NhaRCs0QmECMQDk0U8KJVIuE7EUQdT56ZFCebhEfUgoQIEO7Unr
apgbG2Vyz7x5idpX5n0nW78aQqUCMQDWqo4n/iu/Hk80cmR0rwcz8T0fNi1+169k
tGQSWnaNPkrO9n6raPRW7jY6ELkuTukCMQCRBuDz60e1EKIR1s/oPlPlMETMlCNh
79Bc56UMYxlZRPn91RD+b5NGVz5H7eyn9kkCMQDEWlmRh1IojObSCFiOypKCFoVc
CUhwH4WVTdPDXe/WnkX7LUkMLQJiiZ4cWrOoAhECMC1i2yvcbJ3FBHMSFVJ2GOG1
aBeIp3F+cBnbRyueHuGTAnAozSvzwSHa/SqocUppgg==
-----END RSA PRIVATE KEY-----
dirdival@pld:/tmp/ssh$ cat id_rsa_wtdn.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAYQC/32og0Njsve01k2ocyOyTSN6kSmokDcgqaoTJSFTsvRSEHvgw2dXC67EGGPw/T/5ycZ2C0jDpGS7vwwzBNrCswbEcNPQEEkT0RBspb578ew6dsTJAe/4eowij7CXa7i0= writing to /dev/null

If you are a programmer, I suppose that you can easily recognise here base64 format. In both keys it’s used to cary data. First key (private) is stored in PEM. Second key (public) is written in OpenSSH format, but you can transform it into PEM if you wish:

l@pld:/tmp/ssh$ ssh-keygen -f id_rsa_wtdn.pub -e -m pem > id_rsa_wtdn.pub.pem
dirdival@pld:/tmp/ssh$ cat id_rsa_wtdn.pub.pem
-----BEGIN RSA PUBLIC KEY-----
MGgCYQC/32og0Njsve01k2ocyOyTSN6kSmokDcgqaoTJSFTsvRSEHvgw2dXC67EG
GPw/T/5ycZ2C0jDpGS7vwwzBNrCswbEcNPQEEkT0RBspb578ew6dsTJAe/4eowij
7CXa7i0CAwEAAQ==
-----END RSA PUBLIC KEY-----

Moreover, on the output from the application, we have fingerprint of public key. The most common use of it is confirmation that you are going to use known machine. When you first time connect to some server you can see similar comment:

dirdival@pld:~$ ssh dirdival@localhost
The authenticity of host 'localhost (127.0.0.1)' can't be established.
RSA key fingerprint is 78:1e:56:b2:fc:e8:d0:2e:38:04:2b:8d:77:a1:e4:38.
Are you sure you want to continue connecting (yes/no)? 

After confirmation fingerprint of this machine will be stored in ~/.ssh/known_hosts file. The key point is: if somebody decide to make nasty things, for example attract you to different machine, then you you can see this warning:

dirdival@pld:/tmp/ssh$ ssh dirdival@localhost
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
7e:f1:c3:c5:6f:9a:80:c5:d1:37:f8:87:be:ae:e7:cd.
Please contact your system administrator.
Add correct host key in /home/dirdival/.ssh/known_hosts to get rid of this message.
Offending RSA key in /home/dirdival/.ssh/known_hosts:44
  remove with: ssh-keygen -f "/home/dirdival/.ssh/known_hosts" -R localhost
RSA host key for localhost has changed and you have requested strict checking.
Host key verification failed.

There is no magic with fingerprint, you can generate it manually. It’s ordinary md5sum of public key, so first we have to extract it from base64 and after that make calculations. BTW, watch out on used data – I took them from oryginal (OpenSSH format) not from PEM:

dirdival@pld:/tmp/ssh$ echo AAAAB3NzaC1yc2EAAAADAQABAAAAYQC/32og0Njsve01k2ocyOyTSN6kSmokDcgqaoTJSFTsvRSEHvgw2dXC67EGGPw/T/5ycZ2C0jDpGS7vwwzBNrCswbEcNPQEEkT0RBspb578ew6dsTJAe/4eowij7CXa7i0= | base64 -d | md5sum
af42a8f9c2d6801cb045217dae8f1b89  -

You can do it in more convenient way, using command:

dirdival@pld:/tmp/ssh$ ssh-keygen -l -f /tmp/ssh/id_rsa_wtdn.pub 
768 af:42:a8:f9:c2:d6:80:1c:b0:45:21:7d:ae:8f:1b:89  writing to /dev/null (RSA)

The last crucial observation about fingerprints, because you can miss it. When you connect to a server the md5 checksum is taken from the server configuration not from your account. Default keys are stored in /etc/ssh

dirdival@pld:~$ ls -lh /etc/ssh/ssh_host_rsa_key.pub
-rw-r--r-- 1 root root 390 cze 17  2012 /etc/ssh/ssh_host_rsa_key.pub
dirdival@pld:~$ ssh-keygen -lf /etc/ssh/ssh_host_rsa_key.pub
2048 78:1e:56:b2:fc:e8:d0:2e:38:04:2b:8d:77:a1:e4:38  root@pld (RSA)

Simpler, above fingerprint refers to my machine and was shown when first time I connected to localhost.

Finally, the last thing from the ssh-kegen output is randomart image:

+--[ RSA  768]----+
|.oo.             |
|..o .            |
|.o o             |
|..  .            |
|.... .  S        |
|.oo.. .  .       |
|E.oB .    .      |
|  B.o .  .       |
| ..+.  ..        |
+-----------------+

Meaning of randomart is similar to fingerprint. For many people it’s easier to remember image than a set of characters. And again, the same remark – when you connect to a server then machine’s randomart is presented, not for your key.


Look deeper

We know the basics, the time has come to find out more about data stored in keys. When we extract data carried by base64 then we can see that real key has binary form.

dirdival@pld:/tmp/ssh$ cat id_rsa_wtdn | sed 's|^-.*||g' | base64 -d | xxd
0000000: 3082 01cb 0201 0002 6100 bfdf 6a20 d0d8  0.......a...j ..
0000010: ecbd ed35 936a 1cc8 ec93 48de a44a 6a24  ...5.j....H..Jj$
0000020: 0dc8 2a6a 84c9 4854 ecbd 1484 1ef8 30d9  ..*j..HT......0.
0000030: d5c2 ebb1 0618 fc3f 4ffe 7271 9d82 d230  .......?O.rq...0
0000040: e919 2eef c30c c136 b0ac c1b1 1c34 f404  .......6.....4..
0000050: 1244 f444 1b29 6f9e fc7b 0e9d b132 407b  .D.D.)o..{...2@{

To be more specific, data are stored in ASN.1 format. Because it’s a raw format it’s a little tricky to work with it. I tried find a library in C/C++ or in python which allows works with ASN.1 in simple way but I didn’t find any. Thankfully, openssl has build in option allows extract data:

dirdival@pld:/tmp/ssh$ openssl rsa -in id_rsa_wtdn -text
Private-Key: (768 bit)
modulus:
    00:bf:df:6a:20:d0:d8:ec:bd:ed:35:93:6a:1c:c8:
    ec:93:48:de:a4:4a:6a:24:0d:c8:2a:6a:84:c9:48:
    54:ec:bd:14:84:1e:f8:30:d9:d5:c2:eb:b1:06:18:
    fc:3f:4f:fe:72:71:9d:82:d2:30:e9:19:2e:ef:c3:
    0c:c1:36:b0:ac:c1:b1:1c:34:f4:04:12:44:f4:44:
    1b:29:6f:9e:fc:7b:0e:9d:b1:32:40:7b:fe:1e:a3:
    08:a3:ec:25:da:ee:2d
publicExponent: 65537 (0x10001)
privateExponent:
    79:1c:35:6a:07:49:95:8d:80:a8:1b:a7:94:09:c3:
    42:68:71:98:34:1c:71:2b:7d:d8:40:2a:16:bf:20:
    53:da:39:93:f7:d4:63:49:6c:a3:3f:90:1a:b0:34:
    b0:65:f5:b7:2a:c1:bc:6b:b9:30:45:c9:6d:cb:6e:
    da:2c:4e:4e:26:4b:11:78:33:61:ce:bc:98:41:6e:
    35:6c:d0:f3:39:a8:cc:ce:30:31:e2:52:dd:f8:d8:
    5a:44:2b:34:42:61
prime1:
    00:e4:d1:4f:0a:25:52:2e:13:b1:14:41:d4:f9:e9:
    91:42:79:b8:44:7d:48:28:40:81:0e:ed:49:eb:6a:
    98:1b:1b:65:72:cf:bc:79:89:da:57:e6:7d:27:5b:
    bf:1a:42:a5
prime2:
    00:d6:aa:8e:27:fe:2b:bf:1e:4f:34:72:64:74:af:
    07:33:f1:3d:1f:36:2d:7e:d7:af:64:b4:64:12:5a:
    76:8d:3e:4a:ce:f6:7e:ab:68:f4:56:ee:36:3a:10:
    b9:2e:4e:e9
exponent1:
    00:91:06:e0:f3:eb:47:b5:10:a2:11:d6:cf:e8:3e:
    53:e5:30:44:cc:94:23:61:ef:d0:5c:e7:a5:0c:63:
    19:59:44:f9:fd:d5:10:fe:6f:93:46:57:3e:47:ed:
    ec:a7:f6:49
exponent2:
    00:c4:5a:59:91:87:52:28:8c:e6:d2:08:58:8e:ca:
    92:82:16:85:5c:09:48:70:1f:85:95:4d:d3:c3:5d:
    ef:d6:9e:45:fb:2d:49:0c:2d:02:62:89:9e:1c:5a:
    b3:a8:02:11
coefficient:
    2d:62:db:2b:dc:6c:9d:c5:04:73:12:15:52:76:18:
    e1:b5:68:17:88:a7:71:7e:70:19:db:47:2b:9e:1e:
    e1:93:02:70:28:cd:2b:f3:c1:21:da:fd:2a:a8:71:
    4a:69:82
writing RSA key
-----BEGIN RSA PRIVATE KEY-----
MIIBywIBAAJhAL/faiDQ2Oy97TWTahzI7JNI3qRKaiQNyCpqhMlIVOy9FIQe+DDZ
1cLrsQYY/D9P/nJxnYLSMOkZLu/DDME2sKzBsRw09AQSRPREGylvnvx7Dp2xMkB7
/h6jCKPsJdruLQIDAQABAmB5HDVqB0mVjYCoG6eUCcNCaHGYNBxxK33YQCoWvyBT
2jmT99RjSWyjP5AasDSwZfW3KsG8a7kwRclty27aLE5OJksReDNhzryYQW41bNDz
OajMzjAx4lLd+NhaRCs0QmECMQDk0U8KJVIuE7EUQdT56ZFCebhEfUgoQIEO7Unr
apgbG2Vyz7x5idpX5n0nW78aQqUCMQDWqo4n/iu/Hk80cmR0rwcz8T0fNi1+169k
tGQSWnaNPkrO9n6raPRW7jY6ELkuTukCMQCRBuDz60e1EKIR1s/oPlPlMETMlCNh
79Bc56UMYxlZRPn91RD+b5NGVz5H7eyn9kkCMQDEWlmRh1IojObSCFiOypKCFoVc
CUhwH4WVTdPDXe/WnkX7LUkMLQJiiZ4cWrOoAhECMC1i2yvcbJ3FBHMSFVJ2GOG1
aBeIp3F+cBnbRyueHuGTAnAozSvzwSHa/SqocUppgg==
-----END RSA PRIVATE KEY-----

Wide explanation, of all things, you can find in RFC3447, but for us the most important part of it is RSA private key syntax (ASN.1 format):

RSAPrivateKey ::= SEQUENCE {
    version           Version,
    modulus           INTEGER,  -- n
    publicExponent    INTEGER,  -- e
    privateExponent   INTEGER,  -- d
    prime1            INTEGER,  -- p
    prime2            INTEGER,  -- q
    exponent1         INTEGER,  -- d mod (p-1)
    exponent2         INTEGER,  -- d mod (q-1)
    coefficient       INTEGER,  -- (inverse of q) mod p
    otherPrimeInfos   OtherPrimeInfos OPTIONAL
}

How you can see even names of variables were preserved literally from RSA description. If you wish work with RSA keys I think that the best idea is to use python pyCrypto library:

dirdival@pld:/tmp/ssh$ python
Python 2.7.8 (default, Sep  9 2014, 22:08:43) 
[GCC 4.9.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from Crypto.PublicKey import RSA
>>> with open('id_rsa_wtdn', 'r') as f_key:
...     key = RSA.importKey(f_key.read())
... 
>>> print key
<_RSAobj @0x7f500c0dc7e8 n(768),e,d,p,q,u,private>
>>> print key.n
1163616635019420761386261995137901271228838614127186019741901603856804328038954590877898525874213409268054756033077874229189030905487038052163785842808263839009258009430984845545437464884708604868843164432625393806203016420246875693
>>> print key.e
65537
>>> print key.p
35218253818953980768506387638248335216461812827191381970186920298380955706090279876136514739796509906400604388475557
>>> print key.q
33040156987941811619529556650925749142469923966751395910821444032135576521646935164258543453311179838626615782166249
>>> print key.p * key.q
1163616635019420761386261995137901271228838614127186019741901603856804328038954590877898525874213409268054756033077874229189030905487038052163785842808263839009258009430984845545437464884708604868843164432625393806203016420246875693

I think that is enough if we are talking about basic RSA keys knowledge. I hope that you enjoyed it.

Updated: