SSH Key Checker

When maintaining systems with lots of SSH keys, especially if there has been sloppy maintenance, it can be difficult to identify duplicate public keys.

So...I cobbled this script together. It separates the options, key type, key and comments field. The key is reduced down to its cksum(1) and length (in bytes), which makes it easy to compare to other keys.

Here's what it looks like in operation:

# sshkeychecker nickh.org:/root/.ssh/authorized_keys 

=== nickh.org:/root/.ssh/authorized_keys ===
     cksum Size  # Comment                                  Options
======================================================================================
2336514897  141  1 root@backup.in.nickh.org                 from="7.8.9.10"
4003429508   69  2 root@dbu2.in.nickh.org                   from="7.8.9.10"
 125070557   69  3 root@ibs-linux                           from="7.8.9.10"
The first column is the cksum of the KEY part of the public key. cksum isn't cryptographically secure, but it is easy to spot same and different keys this way. I've actually thought of cropping the cksum down to five characters. This is an administrative tool, if you are looking for malicious activity, you probably want to check the whole key.

Second column is the number of bytes in the key (not the key bit length, but the number of base64 characters that make up the key). As you get used to the various key types you use, you will get used to what is an "appropriate" size, and this will help spot truncated keys or older, insecure formats. My keys are short in length, but they are new-fangled ed25519 and ecdsa keys, so quite strong.

Third column is the key number. For OpenSSH format authorized_keys files, that's also the line number. For RFC4716, it's the key number. This is useful for finding lines to fix.

Fourth column is the comments at the end of the key. This is important to figure out where the key comes from. In my humble opinion, uncommented or badly commented keys should be removed, and see who complains.

Last column is "options" -- commonly commands to run or address restrictions.

Key type is gathered, but not displayed. I just don't think it was useful for my purposes.

This was written for my use, but hopefully it is simple enough it can be customized for your needs. For example, the RFC4716 code is just something I needed, but normal people using good software could ignore or remove it. Only a few programs are left that use that stuff.

And here's the script, which has been tested on OpenBSD ksh and Linux bash:

#!/bin/sh
#
# Copyright (c) 2024 Nick Holland 
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 
# SSH keys are hard for the human eye to compare.  This reduces the key to 
# a simple cksum and size, which is much easier to compare.
# This is not cryptographically sound, but it is handy.

# If you find this useful, I'd love to hear about it -- You can send me
# an e-mail at the above address.

# If the first field is not a valid key type, it is considered an
# option.  So...this list of valid key types will probably need to be 
# updated periodically.
#   https://man.openbsd.org/sshd.8#AUTHORIZED_KEYS_FILE_FORMAT
KEYTYPES=" sk-ecdsa-sha2-nistp256@openssh.com ecdsa-sha2-nistp256 \
  ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 \
  sk-ssh-ed25519@openssh.com ssh-ed25519 ssh-dss ssh-rsa "

# if your machines are up-to-date, you can and should leave out the -O here.
# -O = use the old SCP protocol rather than SFTP.
# -q = quiet
# -p = permissions.  Probably not important.
SCP="scp -qpO"

function usageexit {
    cat <<-__ENDUSAGE
	
	Usage:
	    $0 [-h] [-r] filename [[-r] filename ...]

	filename can be either a local file or a remote file in
	host:/full/path/file format

	  -h : blocks headers
	  -r : this file is RFC4716 (ssh.com, proftpd)

	__ENDUSAGE
    exit
}

if [[ $1 == "-h" ]]; then
    NOHEADERS=y
    shift
fi

if [[ -z $1 ]]; then
    usageexit
fi

FILESTOCHECK="$*"
TMPDIR=$(mktemp -d /tmp/$(basename $0).XXXXXXX)
RFC="n"

for F in $FILESTOCHECK; do
    if [[ $F == "-r" ]]; then
	RFC="y"
	continue
    fi
    # This will fail if the filename or path has a : in it.  Unlikely to be an issue.
    if echo $F |grep -q ":"; then	# Remote file
	$SCP $F $TMPDIR/keyfile 2>/dev/null
	KEYFILE=$TMPDIR/keyfile
    else 				# Local file
	KEYFILE=$F
    fi

    if [[ ! -f $KEYFILE ]]; then
	echo "Error: $F doesn't exist"
    fi

    if [[ -z $NOHEADERS ]]; then
	echo
	echo "=== $F ==="
	printf "%10s %4s %2s %-40s %s\n" cksum Size "#" Comment Options 
	echo "======================================================================================"
    fi
    LINENUM=0

    if [[ $RFC == "n" ]]; then
	# OpenSSH.com format key files 
	# General format:
	#   [options] keytype key comments
	# Apparently, by definition, the "keytype" is identified by being one of a
	# few predefined types.  If the first field isn't one of them, it's an option.
	cat $KEYFILE |while read LINE; do
	    LINENUM=$(( $LINENUM+1 ))
	    OPTIONS=""
	    KEYTYPE=""
	    KEY=""
	    KEYCKSUM="(blank) 0"
	    COMMENT=""
	    DEACTIVATED=""
	    # Commented lines are still processed, but marked as as DEACTIVATED
	    if echo $LINE |grep -q "^#"; then
		LINE="$(echo $LINE |tr -d '#')"
		DEACTIVATED="<--DEACTIVATED"
	    fi

	    set -- $LINE
	    while test -n "$1" && ! echo "$KEYTYPES"|grep -q " $1 "; do
		OPTIONS="$OPTIONS $1"
		shift
	    done 

	    KEYTYPE=$1
	    test -n "$1" && shift
	    KEY="$1"
	    test -n "$KEY" && KEYCKSUM="$(echo $KEY|cksum)"
	    test -n "$1" && shift
	    COMMENT="$*  $COMMENT"

	    printf "%10s %4s %2d %-40s%s%s\n" $KEYCKSUM $LINENUM "$COMMENT" "$OPTIONS" "$DEACTIVATED"
	done
    else
	# RFC4716 format (multi-line ssh.com/proftpd) handler
	# By definition, lines with ":" are not the key.  Gross overimplification
	# to call them all comments, but for my purposes, this works.
	# I'm not sure how to figure out the key type except from the comments, and I'm
	# not sure they are to be trusted for that.  Fortunately, this is an unusual
	# format and a special purpose test..
	cat $KEYFILE | while read LINE; do
	    if [[ $LINE == "---- BEGIN SSH2 PUBLIC KEY ----" ]]; then
		COMMENT=""
		OPTIONS=""
		KEY=""
		TYPE="ssh-rsa"  # just assuming here.
		LINENUM=$(( $LINENUM+1 ))
	    elif echo $LINE | grep -q ":"; then
		COMMENT="$COMMENT $(echo $LINE|cut -f2 -d:)"
	    elif [[ $LINE == "---- END SSH2 PUBLIC KEY ----" ]]; then
		KEYCKSUM="$(echo $KEY|cksum)"
		COMMENT="$(echo $COMMENT|sed "s| *||")" # remove leading spaces
		printf "%10s %4s %2d %-40s%s%s\n" $KEYCKSUM $LINENUM "$COMMENT" "$OPTIONS" ""
	    else
		KEY="$KEY$LINE"
	    fi
	done #
	RFC="n"
    fi
done

rm -f $TMPDIR/keyfile
rmdir $TMPDIR

Examples

Here's a example of usage, with a local and a remote file being checked on the same command line.
$ sudo ssh sshkeychecker webserver:/root/.ssh/authorized_keys /home/nick/.ssh/authorized_keys

=== webserver:/root/.ssh/authorized_keys ===
     cksum Size  # Comment                                  Options
======================================================================================
2336514897  141  1 root@backup                              from="93.18.27.100"
4003429508   69  2 root@dbu2                                from="93.18.27.100"
 125070557   69  3 root@ibs-linux                           from="93.18.27.100"

=== /home/nick/.ssh/authorized_keys ===
     cksum Size  # Comment                                  Options
======================================================================================
3733495391  373  1 nick@fluffy
 176222493   69  2 nick@e5550
2964898971   69  3 nick@suzy
1302570765   69  4 nick@toshc55d
3042674970   69  5 nick@console


 
 

Holland Consulting home page
Contact Holland Consulting
 

since Jan 10, 2024

Copyright 2024, Nick Holland, Holland Consulting