The default password-based encryption for openSSH RSA private keys (generated with ssh-keygen -t rsa
) has inadequate protection against brute forcing due to a weak Password-Based Key Derivation Function (PBKDF) because it used OpenSSL's key derivation with just one round of MD5.
TL;DR: Rather generate new keys with ssh-keygen -t ed25519
which by default should use the more secure PBKDF with the PKCS8 format. RSA key generation defaults to the weak PBKDF stored in the PEM format.
However, if you still need RSA to work with older Unix systems, network devices, and other systems' SSH servers are not yet upgraded with Ed25519 support, this provides info to avoid the less secure default of saving private keys in the legacy PEM format when generating RSA keys. Backward compatibility is not that much of a concern since only the SSH client needs a reasonably modern version of SSH.
The older PEM format with weak private key encryption looks like this:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,3F215EB97E7B45D4
Note:
- The
3F215EB97E7B45D4
hex part is used as the IV and salt for the non-standard PBKDF. - Only one round of hashing makes this very susceptible to brute force attacks.
- In the above example, older 3DES isn't the main issue. Even if using
AES-128-CBC
, it's still got the single hash round problem. - Sharing the IV as a SALT is also a fairly minor issue compared to forcing just one round of hashing.
To convert old keys to the new PKCS8 format with bcrypt, the following worked for me:
ssh-keygen -i -p -f id_rsa -o
When creating new RSA keys, using -o
is also the way to go else you default to the less secure PBKDF and legacy PEM format:
ssh-keygen -t rsa -o
To check what encryption scheme is in use in the new PKCS8 format, run:
openssl base64 -d < ~/.ssh/id_rsa | strings
To see more detail:
openssl base64 -d < ~/.ssh/id_rsa | hexdump -C
By default, mine was using aes256-ctr
:
00000000 6f 70 65 6e 73 73 68 2d 6b 65 79 2d 76 31 00 00 |openssh-key-v1..|
00000010 00 00 0a 61 65 73 32 35 36 2d 63 74 72 00 00 00 |...aes256-ctr...|
00000020 06 62 63 72 79 70 74 <snip> |.bcrypt...<snip>|
Alternate ways to look into the header and see which algorithm and key size is in use:
grep -oP '^[A-Za-z0-9+/]*={0,2}$' ~/.ssh/id_rsa | base64 -d | hexdump -C
openssl base64 -d < ~/.ssh/id_rsa | od -c
Notes:
- Keys are encoded using ASN.1, but cannot be parsed as ASN.1 until decrypted using a paraphrase.
grep -oP '^[A-Za-z0-9+/]*={0,2}$'
selects only lines that contain valid base64 encoding and skips file headers and footers like-----BEGIN OPENSSH PRIVATE KEY-----
and-----END OPENSSH PRIVATE KEY-----
.
OpenSSH v8.1 release notes (~Oct 2019):
- All: support PKCS8 as an optional format for storage of private keys to disk. The OpenSSH native key format remains the default, but PKCS8 is a superior format to PEM if interoperability with non-OpenSSH software is required, as it may use a less insecure key derivation function than PEM's.
The wording is cumbersome, but basically, 8.1+ allows for using this more secure PBKDF format.
I tested a version of OpenSSH 7.6 (~Dec 2017) and noted it generates RSA keys by default using the older weak PEM format. However, you can force the newer format with the ssh-keygen -o
option. As per the man page for on Ubuntu 18.04 LTS:
-o Causes ssh-keygen to save private keys using the new OpenSSH for‐ mat rather than the more compatible PEM format. The new format has increased resistance to brute-force password cracking but is not supported by versions of OpenSSH prior to 6.5. Ed25519 keys always use the new private key format.
So the PKCS8 format should work with openssh client versions >= 6.5.
- How do I determine if an existing SSH private key is secure?
- Improving the security of your SSH private key files
- Clear code example of how the IV and Salt are shared and come from the hex in header of the PEM file.
- stronger encryption for SSH keys. Answer on this gives:
- Good explanation that only one round of hashing was the main issue.
- A nice story about how PEM formats became popular for encoding binary key content in plain-text files.