Image of the glider from the Game of Life by John Conway
Skip to content

Strengthen Your Private Encrypted SSH Keys

Recently, on Hacker News, a post came through about improving the security of your encrypted private OpenSSH keys. I want to re-blog that post here (I'm actually jealous he blogged it first), in my own words, and provide a script at the end that will automate the process for you.

First off, Martin goes into great detail about the storage format of your unencrypted private OpenSSH keys. The unencrypted key is stored in a format known as Abstract Syntax Notation One (ASN.1) (for you web nerds, it's similar in function to JSON). However, when you encrypt the key with your passphrase, it is no longer valid ASN.1. So, Martin then takes you through the process of how the key is encrypted. The big take-away from that introduction is the following, that by default:

  • Encrypted OpenSSH keys use MD5- a horribly broken cryptographic hash.
  • OpenSSH keys are encrypted with AES-128-CBC, which is fast, fast, fast.

It would be nice if our OpenSSH keys used a stronger cryptographic hash like SHA1, SHA2 or SHA3 in the encryption process, rather than MD5. Further, it would be nice if we could cause attackers who get our private encrypted OpenSSH keys to expend more computing resources when trying to brute force our passphrase. So, rather than using the speedy AES algorithm, how about 3DES or Blowfish?

This is where PKCS#8 comes into play. "PKCS" stands for "Public-key cryptography standards". There are currently 15 standards, with 2 withdrawn and 2 under development. Standard #8 defines how private key certificates are to be handled, both in unencrypted and encrypted form. Because OpenSSH use public key cryptography, and private keys are stored, it would be nice if it adhered to the standard. Turns out, it does. From the ssh-keygen(1) man page:

     -m key_format
             Specify a key format for the -i (import) or -e (export) conver‐
             sion options.  The supported key formats are: “RFC4716” (RFC
             4716/SSH2 public or private key), “PKCS8” (PEM PKCS8 public key)
             or “PEM” (PEM public key).  The default conversion format is
             “RFC4716”.

As mentioned, the supported key formats are RFC4716, PKCS8 and PEM. Seeing as though PKCS#8 is supported, it seems like we can take advantage of it in OpenSSH. So, the question then comes, what does PKCS#8 offer me in terms of security that I don't already have? Well, Martin answers this question in his post as well. Turns out, there are 2 versions of PKCS#8 that we need to address:

  • The version 1 option specifies a PKCS#5 v1.5 or PKCS#12 algorithm to use. These algorithms only offer 56-bits of protection, since they both use DES.
  • The version 2 option specifies that PKCS#5 v2.0 algorithms are used which can use any encryption algorithm such as 168 bit triple DES or 128 bit RC2.

As I mentioned earlier, we want SHA1 (or better) and 3DES (or slower). Turns out, the OpenSSL implementation of PKCS#8 version 2 uses the following algorithms:

  • PBE-SHA1-RC4-128
  • PBE-SHA1-RC4-40
  • PBE-SHA1-3DES
  • PBE-SHA1-2DES
  • PBE-SHA1-RC2-128
  • PBE-SHA1-RC2-40

PBE-SHA1-3DES is our target. So, the only question remaining, is can we convert our private OpenSSH keys to this format? If so, how? Well, because OpenSSH relies heavily on OpenSSL, we can use the openssl(1) utility to make the conversion to the new format, and due to the ssh-keygen(1) manpage quoted above, we know OpenSSH supports the PKCS#8 format for our private keys, so we should be good.

Before we go further though, why 3DES? Why not stick with the default AES? DES is slow, slow, slow. 3DES is DES chained together 3 times. Compared to AES, it's a snail racing a hare. With 3DES, the data is encrypted with a first 56-bit DES key, then encrypted with a second 56-bit DES key, the finally encrypted with a third 56-bit DES key. The result is an output that has 168-bits of security. There are no known practical attacks against 3DES, and NIST considers it secure through 2030. It's certainly appropriate to use as an encrypted storage for our private OpenSSH keys.

To convert our private key, all we need to do is rename it, run openssl(1) on the keys, then test. Here are the steps:

$ mv ~/.ssh/id_rsa{,.old}
$ umask 0077
$ openssl pkcs8 -topk8 -v2 des3 -in ~/.ssh/id_rsa.old -out ~/.ssh/id_rsa     # dsa and ecdsa are also supported

Now login to a remote OpenSSH server where the public portion of that key is installed, and see if it works. If so, remove the old key. To simplify the process, I created a script where you provide your private OpenSSH key as an argument, and it does the conversion for you. You can find that script at https://github.com/atoponce/scripts/blob/master/ssh-to-pkcs8.zsh

What's the point? Basically, you should think of it the following way:

  • We're using SHA1 rather than MD5 as part of the encryption process.
  • By using 3DES rather than AES, we've slowed down brute force attacks to a crawl. This should buy us 2-3 extra characters of entropy in our passphrase.
  • Using PKCS#8 gives us the flexibility to use other algorithms in the future, as old ones are replaced.

I agree with Martin that it's a shame OpenSSH isn't using this by default. Why stick with the original OpenSSH storage format? Compatibility isn't a concern, as the support relies solely on the client, not the server. Because every client should have a different keypair installed, there is no worry about new versus old client. Extra security is purchased through the use of SHA1 and 3DES. Computing time to create the keys was trivial, and the performance difference when using them is not noticeable compared to the traditional format. Of course, if your passphrase protecting your keys is strong, with lots and lots of entropy, then an attacker will be foiled with a brute force attack anyway. Regardless, why not make it more difficult for him by slowing him down?

Martin's post is a great read, and as such, I've converted my OpenSSH keys to the new format. I'd encourage you to do the same.

{ 15 } Comments

  1. Andreas Olsson using Opera 9.80 on GNU/Linux 64 bits | May 27, 2013 at 2:10 pm | Permalink

    Regarding that script of yours.

    Instead of running chmod afterwards, how about instead making sure that there is a proper umask in place beforehand, eliminating that small window of attack?

    Not that I expect that distinction to matter much on a desktop system. Yet, when putting something together in a script one might as well make it right.

  2. Aaron Toponce using Google Chrome 27.0.1453.93 on GNU/Linux 64 bits | May 27, 2013 at 3:50 pm | Permalink

    Good point. I'll update the post and the script.

  3. Loïc using Firefox 21.0 on GNU/Linux 64 bits | May 28, 2013 at 11:53 am | Permalink

    Hi Aaron!

    Cool post, I tried it, at first it seemed to work ok but now I have the following problem when trying to connect:
    “Agent admitted failure to sign using the key.”
    So now I can only connect with the password authentication :/
    I understand this is an issue with ssh-agent, but when trying to add the new keys, it doesn’t work. It asks for my passphrase and then nothing happens.

  4. demure using Google Chrome 27.0.1453.93 on Mac OS | May 28, 2013 at 12:01 pm | Permalink

    Thanks for sharing. Any idea if it would still be possible to update the passphrase after converting?

  5. Aaron Toponce using Google Chrome 27.0.1453.93 on GNU/Linux 64 bits | May 28, 2013 at 2:20 pm | Permalink

    Loic- You can convert back with "ssh-keygen -f ~/.ssh/id_rsa -p", and provide a passphrase.

    demure- the same advice for you as above ---^.

  6. Loïc using Firefox 21.0 on GNU/Linux 64 bits | May 28, 2013 at 5:05 pm | Permalink

    Thanks. But that’s odd, after retrying, it seems to work well. I think I did something wrong the first time :)

  7. Victor Engmark using Firefox 21.0 on Ubuntu 64 bits | May 29, 2013 at 3:59 am | Permalink

    @Loïc/@demure: You'll need to cache the SSH key before it will succeed. You can either log in again (if you have an encryption key manager like gnome-keyring-daemon), run "eval `ssh-agent` && ssh-add" to add it in the current shell, or use SSH_AUTH_SOCK=0 (see https://bugs.launchpad.net/ubuntu/+source/gnome-keyring/+bug/328127).

  8. Victor Engmark using Firefox 21.0 on Ubuntu 64 bits | May 29, 2013 at 4:05 am | Permalink

    Excellent article! I created a single command to create safe SSH keys; can you tell if it's got any security holes? https://github.com/l0b0/tilde/commit/f2f448935e47bac17eceaa897043c6d7c198c8d0#L0R1745

    One possible issue is that the key is stored in unencrypted form until the user is done with encrypting it. Ideally it should be read through a pipe, but I couldn't find a way for ssh-keygen to print the key to stdout.

  9. demure using Google Chrome 27.0.1453.93 on Mac OS | May 29, 2013 at 8:53 am | Permalink

    @victor ssh-agent works fine. I was asking if I were to decide to change the pass phase of the key, can I?

  10. Aaron Toponce using Firefox 21.0 on Windows 7 | May 29, 2013 at 9:18 pm | Permalink

    demure, yes as mentioned above: ssh-keygen -f ~/.ssh/id_rsa -p

  11. Greg Grossmeier using Firefox 23.0 on Ubuntu 64 bits | June 17, 2013 at 11:06 am | Permalink

    Added this to my todo list :)

    Thanks Aaron.

  12. Jeff using Google Chrome 32.0.1664.3 on Mac OS | October 11, 2013 at 3:14 pm | Permalink

    FYI, I ran into a problem using a key generated with this process under 10.9 - might be some quirk, it is running OpenSSH 6.2 vs. 5.9 in 10.8. I reverted to the old format for now but will look into it more later.

  13. Mikal using Google Chrome 28.0.1500.71 on Ubuntu 64 bits | November 1, 2013 at 5:45 am | Permalink

    Tripple DES doesn't provide 168 bits of security, because of the meet in the middle attack tripple DES can at best provide 112 bits of security.

    http://en.wikipedia.org/wiki/Meet-in-the-middle_attack

  14. Stewart using Firefox 25.0 on Mac OS | December 11, 2013 at 7:59 am | Permalink

    In case anybody's having issues with these hardened keys using Mavericks, I've written a blog-post on how to work around the limitation by rebuilding ssh-add and ssh-agent from source:
    http://blogs.wcode.org/2013/12/temporary-work-around-to-allow-using-pkcs8-hardened-ssh-keys-with-mavericks-10-9/

  15. c0ntr1but3 using Google Chrome 33.0.1750.117 on Windows 7 | February 27, 2014 at 4:34 pm | Permalink

    Thanks Aaron - nice post. I just wanted to highlight that you've used 3DES and triple DES interchangeably in one of your statements - they're not.

    As you quite rightly state later: 3DES encrypts 3 times. Once with each of 3 keys.

    In a previous statement however you mention "168 bit triple DES". Triple DES however does 3 passes but only uses TWO keys. First pass with first key, second pass with second key and third pass with... FIRST key :( Don't get me wrong, it's still going to be hard to break but. 3DES will be harder because you have to break 3 x 56bit keys vs 2. Obviously with Triple DES if you crack the 1st key and make your way to breaking the 2nd then you're rewarded with a nice easy re-use of the first.

    Keep up the cool posts - your writing style is easy to read!

    Cheers

Post a Comment

Your email is never published nor shared.

Switch to our mobile site