🎁 New User? Get 20% off your first purchase with code NEWUSER20 Β· ⚑ Instant download Β· πŸ”’ Secure checkout Register Now β†’
Menu

Categories

SSH Key Auditing: Detect Weak Keys, Unused Authorized Keys, and Key Sprawl

SSH Key Auditing: Detect Weak Keys, Unused Authorized Keys, and Key Sprawl

SSH key sprawl is the silent breach risk on every Linux fleet: a former employee's key still in ~/.ssh/authorized_keys, a 1024-bit DSA key from 2010 still trusted, a service-account key shared across three teams. The keys never expire, the audit trail is local to each host, and almost no one runs the inventory until the postmortem of a breach. This guide explains the audit techniques that scale to thousands of hosts, the weak-key detection patterns, and the policy enforcement that prevents new sprawl.

Where keys actually live

  • ~/.ssh/authorized_keys per user β€” what the host trusts for inbound auth.
  • ~/.ssh/authorized_keys2 β€” historic, sometimes still consulted; remove if present.
  • /etc/ssh/sshd_config β€” AuthorizedKeysFile may redirect to a non-default path.
  • /etc/ssh/ssh_host_*_key β€” host keys, what clients verify on connect.
  • ~/.ssh/id_* β€” private keys held by users (audit for unencrypted).

Inventory all authorized_keys files

sudo find / -name authorized_keys -type f 2>/dev/null \
  -exec sh -c 'echo "== $1 =="; cat "$1"' _ {} \;
sudo find / -name 'authorized_keys*' -type f 2>/dev/null \
  -exec ls -l {} \;

Note both file paths and ownership. A file owned by root in a non-root user's home is a misconfiguration; a file world-readable is a smaller issue but worth fixing.

Show key details for every authorized entry

sudo find / -name authorized_keys -type f 2>/dev/null | while read f; do
  echo "== $f =="
  ssh-keygen -l -f "$f" 2>/dev/null
done

ssh-keygen -l prints fingerprint, bit-length, comment, and key type per entry. Output looks like:

256  SHA256:8x... user@laptop (ED25519)
3072 SHA256:Y2... bob@vault    (RSA)
1024 SHA256:7Z... legacy        (DSA)

Anything DSA, anything RSA shorter than 2048 bits, anything ECDSA P-256 with comment indicating a known weak source β€” flag for removal.

Detecting weak and deprecated key types

sudo find / -name authorized_keys -type f 2>/dev/null | while read f; do
  ssh-keygen -l -f "$f" 2>/dev/null | awk -v file="$f" '
    /\(DSA\)/                  {print "DSA       " file " | " $0}
    /\(ECDSA\)/                {print "ECDSA     " file " | " $0}
    /\(RSA\)/ && $1+0 < 2048   {print "RSA-WEAK  " file " | " $0}
    /\(RSA\)/ && $1+0 == 2048  {print "RSA-2048  " file " | " $0}'
done

Modern recommendation: Ed25519 only, with RSA-3072 or larger as a compatibility fallback for ancient clients. DSA is broken, ECDSA P-256 has known issues, and RSA-1024 is computationally feasible to attack.

Identifying unused keys

An authorised key that has never been used is the most likely to be a leftover from a past employee. Use SSH's VERBOSE logging to record the fingerprint of every successful login:

# /etc/ssh/sshd_config.d/99-audit.conf
LogLevel VERBOSE
sudo grep 'Accepted publickey' /var/log/auth.log* 2>/dev/null \
  | grep -oE 'SHA256:[A-Za-z0-9+/]+' | sort -u > /tmp/used-fingerprints.txt
sudo grep 'Accepted publickey' /var/log/auth.log* 2>/dev/null \
  | awk '{print $9, $11}' | sort -u | head

Cross-reference each fingerprint in authorized_keys with the used set. Anything not seen in the past 90 days is a candidate for removal.

Centralised key management

For fleets, stop maintaining authorized_keys per host. Two scalable options:

  1. SSH certificates. Sign user keys with a CA; the SSH server trusts the CA, not individual keys. Revocation is centralised.
    # /etc/ssh/sshd_config
    TrustedUserCAKeys /etc/ssh/ca.pub
    RevokedKeys /etc/ssh/revoked-keys
  2. AuthorizedKeysCommand. sshd queries an external script β€” typically against LDAP, Vault, or an internal API β€” for the user's authorised keys at login time.
    AuthorizedKeysCommand /usr/local/sbin/get-keys-for-user
    AuthorizedKeysCommandUser nobody

Either approach makes deprovisioning a single API call instead of an Ansible run across 500 hosts.

Enforcing key types in sshd

# /etc/ssh/sshd_config.d/99-keys.conf
PubkeyAcceptedAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256
HostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256

This refuses DSA, ECDSA, and RSA-SHA1 outright at the server. Validate with ssh-audit yourhost.com.

Restricting key capabilities

Per-key restrictions in authorized_keys drastically limit damage if a key leaks:

from="10.0.0.0/8",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,command="/usr/local/bin/backup.sh" ssh-ed25519 AAAA…

Source IP restriction, no agent forwarding (prevents pivot), and command= to lock the key to a single script.

Detect tampering

sudo find / -name authorized_keys -newer /etc/ssh/sshd_config 2>/dev/null
sudo find / -name authorized_keys -newer /var/lib/dpkg/info/openssh-server.list 2>/dev/null
echo '-w /root/.ssh/authorized_keys -p wa -k ssh-keys' | sudo tee -a /etc/audit/rules.d/ssh.rules
echo '-w /home -p wa -k home-changes' | sudo tee -a /etc/audit/rules.d/ssh.rules
sudo augenrules --load

Combined with off-host log shipping, every key addition becomes a SIEM event you can review.

The audit script

#!/bin/bash
echo "== Authorized keys files =="
sudo find / -name 'authorized_keys*' -type f 2>/dev/null
echo
echo "== Per key fingerprints =="
sudo find / -name authorized_keys -type f 2>/dev/null | while read f; do
  user=$(stat -c '%U' "$f")
  echo "[$user] $f:"
  ssh-keygen -l -f "$f" 2>/dev/null | sed 's/^/  /'
done
echo
echo "== Weak key types =="
sudo find / -name authorized_keys -type f 2>/dev/null -exec ssh-keygen -l -f {} \; \
  | grep -E '\(DSA\)|\(ECDSA\)|^10[0-2][0-9]'
echo
echo "== Unencrypted private keys in user homes =="
sudo find /home /root -name 'id_*' -type f 2>/dev/null \
  -exec sh -c 'head -1 "$1" | grep -q ENCRYPTED || echo "  unprotected: $1"' _ {} \;

Common pitfalls

  • Removing keys from authorized_keys but leaving the user account active and password-enabled.
  • Trusting SHA256: fingerprints alone; the comment field in the key tells you what the key is used for.
  • Forgetting that ~/.ssh/authorized_keys2 is still consulted by some sshd versions; remove if present.
  • Granting PermitOpen any or omitting it entirely; explicit allow lists prevent stolen keys from pivoting.

SSH key auditing is the kind of task that pays off once and prevents incidents forever. Run the audit script monthly across every host; remove keys not used in 90 days; reject DSA/ECDSA at the sshd level; and migrate to centralised key management before your fleet grows past a few dozen hosts.

Share this article:
Dargslan Editorial Team (Dargslan)
About the Author

Dargslan Editorial Team (Dargslan)

Collective of Software Developers, System Administrators, DevOps Engineers, and IT Authors

Dargslan is an independent technology publishing collective formed by experienced software developers, system administrators, and IT specialists.

The Dargslan editorial team works collaboratively to create practical, hands-on technology books focused on real-world use cases. Each publication is developed, reviewed, and...

Programming Languages Linux Administration Web Development Cybersecurity Networking

Stay Updated

Subscribe to our newsletter for the latest tutorials, tips, and exclusive offers.