Working in IT and Security has given me so many opportunities to dive into a subject and really try to understand not only what the best practices are, but the why behind them. Most recently I was generating an SSH identity to use on the servers I help manage. While generating the key pair I noticed that the field for entering in a password to protect the keys was completely optional. This got me thinking, if my users don’t use passwords on their keys, would I know? How can I audit their security practices and enforce keys that are encrypted? I opened a new tab and searched for “how can I tell if an ssh key has a password”. Looking at the results it’s clear that I am not the only admin who has had that question.

Fairly quickly I came across my first answer. If the file is encoded in PEM format (standard for older versions of OpenSSH) just opening the file should tell you what you want to know.

$ cat rsa-key-PEM
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED

Well that was easy, what about the newer OpenSSH format?

$ cat rsa-key-RFC4716
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbm…

Not quite as easy. However, I did find a few options that worked. These methods all centered around using the key in some manner. i.e trying to authenticate to a server or change the password on the key itself. One method stood out to me as a safer way to check. The snippet below attempts to generate the public key from the private key. If it works without authentication, the key is unencrypted.

#! /bin/bash
key=”<Path/to/Key>”
if output=”$(setsid </dev/null 2>&1 env -i ssh-keygen -y -f $key)”;
then
echo “Unencrypted private key!”
echo $output
else
echo $output
fi

Simple enough, but all of those solutions have a similar problem. For these tests to work, I need access to read the key. This may not seem like such a bad idea, but the thing to keep in mind is these keys are credentials! They should be protected in the same way that we protect other credentials on the network.

That still leaves me with an issue on my hands. I can tell if my users are setting a password, but only if I am willing to compromise the security of their private keys so I can validate it. This isn’t a trade that I am willing to make. To get around this, I changed how I was looking at it. What can the file itself tell me about its’ contents without me needing to have access to read it. At this point I had generated plenty of test keys. Looking at the file details gave me the next idea to test.

$ ls -l 
-rw------- 1 user user 1876 Dec 5 08:11 rsa-test
-rw------- 1 user user 1876 Dec 5 08:53 rsa-test2
-rw------- 1 user user 1876 Dec 7 15:38 rsa-test3
-rw------- 1 user user 1823 Dec 5 08:13 rsa-test-nopass
-rw------- 1 user user 1823 Dec 6 11:14 rsa-test-nopass1
-rw------- 1 user user 1823 Dec 7 09:33 rsa-test-nopass2
-rw------- 1 user user 1823 Dec 7 13:46 rsa-test-nopass3

All of the keys that were generated with a common bit length and had a password were all the same size! It was looking like I might have uncovered a slightly better way to see if keys had passwords. To better test this, I decided to write a python script that will generate a number of keys at different bit lengths, both with and without passwords. At first, it looked like I was on the right track, MacOS devices seemed to generate keys with a standard size. Unfortunately the first sign of issues was when I ran the script against some of my test Linux servers. All of them reported different results.

Results from MacOS 10.14.6

OpenSSH_7.9p1, LibreSSL 2.7.3
Printing results. All values are in bytes
[encrypted-key, key size, unencrypted-key, key size, difference between encrypted/unencrypted keys
dsa results:
[‘dsa-1024-pass’, 1434, ‘dsa-1024-nopass’, 1393, 41]
ecdsa results:
[‘ecdsa-256-pass’, 557, ‘ecdsa-256-nopass’, 513, 44]
[‘ecdsa-384-pass’, 667, ‘ecdsa-384-nopass’, 622, 45]
[‘ecdsa-521-pass’, 801, ‘ecdsa-521-nopass’, 748, 53]
rsa results:
[‘rsa-1024-pass’, 1097, ‘rsa-1024-nopass’, 1052, 45]
[‘rsa-2048-pass’, 1876, ‘rsa-2048-nopass’, 1831, 45]
[‘rsa-3072-pass’, 2655, ‘rsa-3072-nopass’, 2610, 45]
[‘rsa-4096-pass’, 3434, ‘rsa-4096-nopass’, 3389, 45]
[‘rsa-8192-pass’, 6550, ‘rsa-8192-nopass’, 6505, 45]
[‘rsa-16384-pass’, 12782, ‘rsa-16384-nopass’, 12737, 45]
Ed25519 results:
[‘Ed25519–256-pass’, 464, ‘Ed25519–256-nopass’, 419, 45]

Results from Ubuntu 19.10

OpenSSH_8.0p1 Ubuntu-6build1, OpenSSL 1.1.1c  28 May 2019
Printing results. All values are in bytes
[encrypted-key, key size, unencrypted-key, key size, difference between encrypted/unencrypted keys
dsa results:
['dsa-1024-pass', 1434, 'dsa-1024-nopass', 1381, 53]
ecdsa results:
['ecdsa-256-pass', 557, 'ecdsa-256-nopass', 505, 52]
['ecdsa-384-pass', 667, 'ecdsa-384-nopass', 610, 57]
['ecdsa-521-pass', 781, 'ecdsa-521-nopass', 736, 45]
rsa results:
['rsa-1024-pass', 1097, 'rsa-1024-nopass', 1044, 53]
['rsa-2048-pass', 1876, 'rsa-2048-nopass', 1823, 53]
['rsa-3072-pass', 2655, 'rsa-3072-nopass', 2602, 53]
['rsa-4096-pass', 3434, 'rsa-4096-nopass', 3381, 53]
['rsa-8192-pass', 6550, 'rsa-8192-nopass', 6497, 53]
['rsa-16384-pass', 12782, 'rsa-16384-nopass', 12729, 53]
Ed25519 results:
['Ed25519-256-pass', 464, 'Ed25519-256-nopass', 411, 53]

Testing more distributions, I found that even the same OpenSSH version with a different package maintainer didn’t have the common output. Now, if you knew the specific environment where the keys were generated, it would be possible extrapolate general landscape of the keys in your enterprise. While this may seem like progress in the right direction, the fact here is identifying these keys by their file size was not going to be large scale solution that I thought I needed. There were just too many variable at play. As much as that was frustrating to learn, there is a better take away that we should focus on.

Stop trying to analyze your users’ keys.

For many other systems that we use, reaching out to check the configuration on the endpoints in your enterprise is a valid strategy (think OS and application patching). With how there is such a variance in how these keys are generated, the methods required to find if those keys are encrypted their endpoints is just not worth the return. It would be better to expand the security configuration at the server where the user is trying to authenticate.

As an example, if the reason that you wanted to know if the key has a password on it is to ensure that it can not be stolen and used by another user, consider enforcing OATH-TOTP via google authenticator. This is a fairly basic change that, depending on the user, may be easier than handling multiple passwords when authenticating to servers. Going further, implementing an sssd configuration would enable you to define not just access to your servers via an LDAP group membership, but also set levels of access within your servers according to those same groups by leveraging a few custom entries in the sudoers file. Additionally, this would allow for access to all of the servers configured to authenticate via LDAP to be shut off via disabling of a user in Active Directory. In my opinion, that is one of the benefits of running off of a Microsoft (or another directory based) back end.

At the end of the day, those of us who work in IT and Security are tasked with designing and maintaining systems that will need to be accessed by our users. Rather than endlessly chasing down the local configurations and pointing to the security policy, focus your efforts to secure these systems in a way that enforces standards that the business deems necessary.