Is there a way to get an MD5 fingerprint (signature) of a public key if I have a string of that key rather than a file?
ssh-keygen -l -E md5 -f "path/to/file"
This command will give me (among other things) the MD5 fingerprint (signature) of the key. I have read the man page for the ssh-keygen command and experimented in my shell, but I have not been able to get it to work on a string rather than a file. I can found no argument that takes a string, and I have tried piping in the string and also using STDIN to supply the string to ssh-keygen - all without success.
I have tried using Ruby and Digest::MD5.hexdigest on the string, but either I am not supplying the correct string or I need to be using some other hash mechanism because I've had no luck with that either. (I've tried various substrings of the key)
I could, of course, write the string to a temp file and then use ssh-keygen on that file, but it just seems like I shouldn't have to.
I would ultimately like to solve this problem in Ruby, but I can start with a unix utility or bash solution. If need be I can always execute the shell command from Ruby.
If it matters, I am running a bash (GNU bash, version 3.2.57(1)) shell on macOS Mojave (10.14.6) and Ruby 2.6.4
Edit: I changed the term from signature to fingerprint but left signature in parentheses. I have seen both terms used, but I believe fingerprint is the more common.
Best Answer
I decided to search for a Ruby Gem that solves the problem. I found this one: https://github.com/bensie/sshkey. Digging around the source code, I discovered that I need to Base64 decode the key part of the string, and then do a Digest::MD5.hexdigest on that to get the fingerprint of the key.
string = "ssh-rsa aabbccddqq== comment goes here" # not a real keykey = string.split(" ")[1]fingerprint = Digest::MD5.hexdigest(Base64.decode64(key))
I used the term "signature" in the original question, I have edited that question to add "fingerprint" as an alternate term
Use -
as filename to read stdin
as file.Then feed stdin
with a Bash's <<<"herestring"
With Bash:
#!/usr/bin/env bash# Fill a keystring variable for demo purposeIFS= read -r -d '' keystring <"$HOME/.ssh/id_rsa.pub"# Get ssh-keygen to read from the keystring rather than a filekey_md5="$(ssh-keygen -l -E md5 -f - <<<"$keystring" | cut -d ' ' -f2)" # Isolate and reformat the md5# Remove the MD5: prefixkey_md5="${key_md5##MD5:}"# Remove the in-between :key_md5="${key_md5//:}"# Print the md5 for testingecho "$key_md5"
Or with a POSIX shell:
#!/usr/bin/env sh# Fill a keystring variable for demo purposeIFS= read -r keystring <"$HOME/.ssh/id_rsa.pub"# Get ssh-keygen to read from the keystring rather than a file# and reformat the md5 sum with sed POSIX Extended Regex# sed commands:# s/://g deletes all colon characters# s/^[[:digit:]]\+[[:space:]]\+MD5\([[:xdigit:]]\{32\}\).*/\1/# starting with digits followed by spaces, followed by MD5# capture the group of 32 hexadecimal digits from the md5 sumkey_md5="$(echo "$keystring" \| ssh-keygen -l -E md5 -f - \| sed 's/://g;s/^[[:digit:]]\+[[:space:]]\+MD5\([[:xdigit:]]\{32\}\).*/\1/')"# Print the md5 for testingecho "$key_md5"
Alternatively, you can use the base64
command to decode the key, and the md5sum
command to compute the sum in shell like this:
#!/usr/bin/env sh# Fill a keystring variable for demo purposeIFS= read -r keystring <"$HOME/.ssh/id_rsa.pub"# Inject this sub-shell commands result into the argumentsset -- "$(# Pipe in the ssh key stringecho "$keystring" |# Get the second field containing the base64 encoded keycut -d ' ' -f2 |# Decode the key from its base64base64 -d |# Compute the md5 sum of the keymd5sum |# Get the first field containing the md5cut -d ' ' -f1)"# The key's md5 has been returned in the first argumentkey_md5="$1"# Print the md5 for testingecho "$key_md5"
Using Python:
import codecs, hashlibpubkey = "AAAA...." #Put you public key string herefingerprint = hashlib.md5(codecs.decode(bytes(pubkey,'utf-8'), 'base64')).hexdigest()print(fingerprint)
If you need a colon separated fingerprint, try:
cs_fingerprint = ":".join([ fingerprint[pair:pair+2] for pair in range(0, len(fingerprint)-1, 2) ])print(cs_fingerprint)