Support for encrypted private keys with cl-ssh-keys in Common Lisp

This is a follow up of my previous post about parsing and generating OpenSSH keys in Common Lisp.

One of the features that were missing until today in the cl-ssh-keys system is support for encrypted private keys, which Iā€™m happy to share that this one is now implemented.

Here are a few examples about using cl-ssh-keys system with encrypted private keys.

First, lets load the system.

CL-USER> (ql:quickload :cl-ssh-keys)

In order to parse an encrypted private key we need to (obviously) provide a passphrase, e.g.

CL-USER> (ssh-keys:with-private-key-file (key #P"~/.ssh/id_rsa" :passphrase "my-secret-password")
           (ssh-keys:key-cipher-name key))

The passphrase for an encrypted private key can be changed by setting a new value for the passphrase using the SSH-KEYS:KEY-PASSPHRASE accessor.

This example changes the passphrase for a given key and saves it on the filesystem.

CL-USER> (ssh-keys:with-private-key-file (key #P"~/.ssh/id_rsa" :passphrase "OLD-PASSPHRASE")
           (setf (ssh-keys:key-passphrase key) "MY-NEW-PASSPHRASE")
           (ssh-keys:write-key-to-path key #P"~/.id_rsa-new-passphrase"))

We can also encrypt an existing un-encrypted key. In order to do that we simply set a passphrase using the SSH-KEYS:KEY-PASSPHRASE accessor, e.g.

CL-USER> (ssh-keys:with-private-key-file (key #P"~/.ssh/id_rsa")
           (setf (ssh-keys:key-passphrase key) "my-secret-password")
           (ssh-keys:write-key-to-path key #P"~/.id_rsa-encrypted"))

This will encrypt the key using the default cipher (aes256-ctr as of today), using 16 iteration rounds for the KDF function in order to derive an encryption key.

A passphrase of a private can also be removed by simply setting the passphrase to nil, e.g.

CL-USER> (ssh-keys:with-private-key-file (key #P"~/.ssh/id_rsa" :passphrase "PASSPHRASE")
           (setf (ssh-keys:key-passphrase key) nil)
           (ssh-keys:write-key-to-path key #P"~/.id_rsa-unencrypted"))

If we need to change the cipher for a private key we can do that as well, which is useful when you have a private key, which has been encrypted using an older or deprecated cipher.

The cipher to be used for encryption of a private key can be set by using the SSH-KEYS:KEY-CIPHER-NAME accessor. The value should be one of the known and supported ciphers as returned by SSH-KEYS:GET-ALL-CIPHER-NAMES.

First, list the known cipher names.

CL-USER> (ssh-keys:get-all-cipher-names)
("3des-cbc" "aes128-cbc" "aes192-cbc" "aes256-cbc" "aes128-ctr" "aes192-ctr" "aes256-ctr" "none")

Then set a new cipher.

CL-USER> (ssh-keys:with-private-key-file (key #P"~/.ssh/id_rsa" :passphrase "PASSPHRASE")
           (setf (ssh-keys:key-cipher-name key) "3des-cbc")
           (ssh-keys:write-key-to-path key #P"~/.id_rsa-3des-cbc"))

And finally, we can change the number of iterations which is used by the KDF function in order to derive the encryption key.

By default ssh-keygen(1) and cl-ssh-keys will use 16 rounds of iterations in order to produce an encryption key. You can set this to a higher value, if needed, which would help against brute-force attacks.

CL-USER> (ssh-keys:with-private-key-file (key #P"~/.ssh/id_rsa" :passphrase "PASSPHRASE")
           (setf (ssh-keys:key-kdf-rounds key) 32)
           (ssh-keys:write-key-to-path key #P"~/.id_rsa-stronger"))

For more information and additional examples, please refer to the cl-ssh-keys repo.

Written on September 25, 2020