78

How can I add a host key to the SSH known_hosts file securely?

I'm setting up a development machine, and I want to (e.g.) prevent git from prompting when I clone a repository from github.com using SSH.

I know that I can use StrictHostKeyChecking=no (e.g. this answer), but that's not secure.

So far, I've found...

  1. GitHub publishes their SSH key fingerprints at GitHub's SSH key fingerprints

  2. I can use ssh-keyscan to get the host key for github.com.

How do I combine these facts? Given a prepopulated list of fingerprints, how do I verify that the output of ssh-keyscan can be added to the known_hosts file?


I guess I'm asking the following:

How do I get the fingerprint for a key returned by ssh-keyscan?

Let's assume that I've already been MITM-ed for SSH, but that I can trust the GitHub HTTPS page (because it has a valid certificate chain).

That means that I've got some (suspect) SSH host keys (from ssh-keyscan) and some (trusted) key fingerprints. How do I verify one against the other?


Related: how do I hash the host portion of the output from ssh-keyscan? Or can I mix hashed/unhashed hosts in known_hosts?

12
  • Why wouldn't it be secure for your use case? Commented Jun 16, 2017 at 10:39
  • 1
    StrictHostKeyChecking=no is vulnerable to MITM. Is ssh-keyscan secure against MITM? Commented Jun 16, 2017 at 10:48
  • I fail to understand why I'm overly worried about somebody impersonating a stranger I've never met whom I'm trusting enough to write code I'm about to download and run... Commented Jun 16, 2017 at 11:23
  • 4
    Because this is my source code in a private repo on github, and I don't want a MITM (e.g.) introducing malicious changes when I push commits. That's just one example. Commented Jun 16, 2017 at 11:28
  • I (for better or worse) choose to trust github. I don't choose to trust every random network link between me and them. Commented Jun 16, 2017 at 11:28

9 Answers 9

12

Now that GitHub provides their SSH keys and fingerprints via their metadata API endpoint (as of January 2022), you can leverage the trust you have in GitHub's TLS certificate used on api.github.com (due to it being signed by a certificate authority (CA) which is in your system's trusted root certificate store) to securely fetch their SSH host keys.

If you have jq installed you can do it with this one-liner

curl --silent https://api.github.com/meta \ | jq --raw-output '"github.com "+.ssh_keys[]' >> ~/.ssh/known_hosts 

Or if you want to use Python

curl --silent https://api.github.com/meta | \ python3 -c 'import json,sys;print(*["github.com " + x for x in json.load(sys.stdin)["ssh_keys"]], sep="\n")' \ >> ~/.ssh/known_hosts 
3
  • This answer relies on curl being correctly configured to reject fraudulent certificates. You can check that your curl is working right by doing curl https://expired.badssl.com/ which should fail. As long as that fails then this should be a great answer for the special case of github. Commented Apr 17, 2022 at 10:23
  • To quote curl's man page By default, every SSL connection curl makes is verified to be secure. So as long as you don't add a -k or --insecure argument to the curl command above or a curl config file you're fine. Commented Apr 18, 2022 at 14:53
  • - I also saw that, however that's not the guaranteed default on all installs of curl, just the default install. If the user, or someone else, has turned off the SSL check in the ~/.curlrc file then these checks will be turned off silently and then there will be a vulnerability in this process. The original answer specifically asks for a "secure" way of doing this so I think that a certain level of extra care is important in answers to this question. Commented Apr 21, 2022 at 12:33
64

The most important part of "securely" adding a key to the known_hosts file is to get the key fingerprint from the server administrator. The key fingerprint should look something like this:

2048 SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 github.com (RSA) 

In the case of GitHub, normally we can't talk directly to an administrator. However, they put the key on their web pages so we can recover the information from there.

Manual key installation

1) Take a copy of the key from the server and get its fingerprint. N.B.: Do this before checking the fingerprint.

$ ssh-keyscan -t rsa github.com | tee github-key-temp | ssh-keygen -lf - # github.com:22 SSH-2.0-babeld-f3847d63 2048 SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 github.com (RSA) 

2) Get a copy of the key fingerprint from the server administrator - in this case navigate to the page with the information on github.com

  1. Go to github.com
  2. Go to the help page (on the menu on the right if logged in; at the bottom of the homepage otherwise).
  3. In the Getting Started section go to Connecting to GitHub with SSH
  4. Go to Testing your SSH connection
  5. Copy the SHA256 fingerprint from that page into your text editor for later use.

3) Compare the keys from the two sources

By placing them directly one above the other in a text editor, it is easy to see if something has changed

2048 SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 github.com (RSA) #key recovered from github website 2048 SHA256:nThbg6kXUpJ3Gl7E1InsaspRomtxdcArLviKaEsTGY8 github.com (RSA) #key recovered with keyscan 

(Note that the second key has been manipulated, but it looks quite similar to the original - if something like this happens you are under serious attack and should contact a trusted security expert.)

If the keys are different abort the procedure and get in touch with a security expert

4) If the keys compare correctly then you should install the key you already downloaded

cat github-key-temp >> ~/.ssh/known_hosts 

Or to install for all users on a system (as root):

cat github-key-temp >> /etc/ssh/ssh_known_hosts 

Automated key installation

If you need to add a key during a build process then you should follow steps 1-3 of the manual process above.

Having done that, examine the contents of your github-key-temp file and make a script to add those contents to your known hosts file.

if ! grep github.com ~/.ssh/known_hosts > /dev/null then echo "github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==" >> ~/.ssh/known_hosts fi 

You should now get rid of any ssh commands which have StrictHostKeyChecking disabled.

9
  • Why is the order of step 1 and 2 important? Commented Dec 31, 2020 at 10:46
  • 3
    What about if ! ssh-keygen -F github.com; then? Commented Apr 1, 2021 at 22:06
  • 1
    I mean, you do grep github.com ~/.ssh/known_hosts. Why not use a more specialized tool (ssh-keygen) that does just that, but also knows the format of the file? Commented Sep 14, 2021 at 3:13
  • 1
    To be honest, I'm not sure what lines you're talking about. You add the line with echo. ssh-keygen only checks the ip/domain part, and won't change ~/.ssh/known_hosts. I'm only suggesting to change the grep part. Commented Sep 17, 2021 at 3:18
  • 1
    If you need to replace (possibly) stale host keys, you could use, for example, [ -f ~/.ssh/known_hosts ] && ssh-keygen -R github.com to remove any existing github keys, followed by echo "$GITHUB_HOST_KEY" >> ~/.ssh/known_hosts (assuming the key is stored in a variable called GITHUB_HOST_KEY). Commented Oct 7, 2021 at 9:55
53

You can mix hashed/unhashed entries in your known_hosts file.

So if you want to add github key, you can just do :

ssh-keyscan github.com >> ~/.ssh/known_hosts

If you want it hashed, add -H

ssh-keyscan -H github.com >> ~/.ssh/known_hosts

Note: this is vulnerable to MITM attack, it answers to the "Related" part of the question only.

5
  • 1
    What is the difference between hashed/unhashed? I know what hashing is, just not why it is applied in this context. Commented Feb 14, 2019 at 10:22
  • 5
    Hashing your entries allows you to hide information about the hosts you are used to connect to. Check the accepted answer at security.stackexchange.com/questions/56268/… Commented Feb 15, 2019 at 11:16
  • 10
    This doesn't answer the question. Using ssh-keyscan is subject to a man in the middle attack which means that the key you store could be a key belonging to the attacker trying to break into your system. Please see my answer for a way to check things. Commented Jun 24, 2019 at 7:26
  • 1
    Att. Getting attention for answers that have aways been incorrect - "the two top answers are actually insecure" Commented Jun 24, 2019 at 15:40
  • 1
    This answer was written at the very beginning when the question was not very clear. It is subject to the man in the middle yes, but I left it here as people keep voting +1, I guess it helps some. It also answers the "related" part of the question. Commented Mar 29, 2022 at 10:17
5

The easiest way is to manually fetch the keys using ssh-keyscan, verify them manually:

$ ssh-keyscan -t rsa github.com | ssh-keygen -lf - # github.com:22 SSH-2.0-libssh-0.7.0 2048 SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 github.com (RSA) 

And add them to your script, which will then carry the "authoritative" public key.

6
  • I've got the keys like you said, how do I 'add them to my script'? Commented Feb 14, 2019 at 10:19
  • Either as a separate file or as a string in your script. Commented Feb 14, 2019 at 11:35
  • 4
    This answer is dangerous because hidden in the "verify them manually" is a whole difficult process. I've added an answer below which atempts to explain how to do that and use the results safely. Commented Jun 18, 2019 at 15:55
  • 3
    Att. Getting attention for answers that have aways been incorrect - "the two top answers are actually insecure" Commented Jun 24, 2019 at 15:40
  • 1
    @RogerLipscombe I agree the answer could be interpreted securely - "verify them manually" isn't explained, but if you understand it to mean "following the instructions in the ssh manual page" then that would probably be fine. The risk here is that someone might not understand that and could, for example, try insecure things like checking the key after logging in to the server. That's why I called this answer "dangerous" and other one "insecure" - the detail of how you check manually is the important bit. Thanks for the accept. Commented Aug 1, 2019 at 16:30
5

I wrote simple script (add_to_known_hosts) to handle this:

It won't create duplicate entries in the known_hosts file, and it will check if the fingerprint matches one provided as second argument.

#!/usr/bin/env bash # The first argument should be hostname (or IP) # The second argument should be the SSH fingerprint from the server admin. # Example: add_to_known_hosts github.com SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8 host=$1 fingerprint=$2 ip=$(getent hosts $1 | awk '{ print $1 }') echo $ip keys=$(ssh-keyscan -t rsa $host $ip) # Iterate over keys (host and ip) while IFS= read -r key; do # Extract Host name (or IP) key_host=$(echo $key | awk '{ print $1 }') # Extracting fingerprint of key key_fingerprint=$(echo $key | ssh-keygen -lf - | awk '{ print $2 }') # Check that fingerprint matches one provided as second parameter if [[ $fingerprint != $key_fingerprint ]]; then echo "Fingerprint match failed: '$fingerprint' (expected) != '$key_fingerprint' (got)"; exit 1; fi # Add key to known_hosts if it doesn't exist if ! grep $key_host ~/.ssh/known_hosts > /dev/null then echo "Adding fingerprint $key_fingerprint for $key_host to ~/.ssh/known_hosts" echo $key >> ~/.ssh/known_hosts fi done <<< "$keys" 
1
  • I think this looks like a good answer for some circumstances. There's a definite chunk of code here which might be better put into a code repository (e.g. GitLab) together with some tests so that it can be worked on and verified more easily. Commented Mar 22, 2022 at 21:30
3

Automating SSH Known_Hosts fingerprint check

I have been trying to do this in Python on Jupyterhub for a little while but @Michael's answer was really helpful!

Let us be clear -- by design this step to confirm a known host should NOT be automated given susceptibility to man in the middle attacks (more info here, but it should be obvious why this is a concern).

Manually checking SSH RSA Fingerprint for GitHub.com with Python & Bash

The insecure workaround -o "StrictHostKeyChecking no" was alright for testing but glad to have an alternative.

  • Notice I use ! bang in Jupyter to invoke bash command for ssh-keyscan & cat
# MANUALLY GET TRUSTED RSA FINGERPRINT FOR GITHUB # https://docs.github.com/en/github/authenticating-to-github/githubs-ssh-key-fingerprints rsa_pubkey_fingerprint = 'SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8' # Get the host rsa info for github, write to a file # pipe to get the RSA FingerPrint, and finally write that fingerprint to file !ssh-keyscan -t rsa github.com | tee ./github_ssh_test | ssh-keygen -lf - >> ./fingerprint_rsa 

Assert Fingerprints match to confirm known hosts

You can now use a python assertion to ensure authenticity of our trusted RSA fingerprint vs the Scanned-over-your-internet RSA fingerprint:

assert(rsa_pubkey_fingerprint == open("./fingerprint_rsa", "r").read().split()[1]) # if the assertion passes and the trusted matches the scanned # it'll write to your known_hosts file! !cat ./github_ssh_test >> $HOME/.ssh/known_hosts # you are now good to test your SSH connection !ssh -T [email protected] 

Thanks and a much better workaround to ignoring the strict check!

1
  • Programmatic checking is a good way to go since it's unlikely to be fooled by a key that's been carefully chosen to be visually similar. The newer version of the OpenSSH ssh command does something similar to this answer where, when connecting, you can paste a key in to the (yes/no) prompt telling you that the key is unknown and it will only connect if the key matches exactly. Commented Mar 22, 2022 at 21:27
0

If you would like to check if the key exists before generating a new one:

ssh-keygen -F github.com || ssh-keyscan github.com >> ~/.ssh/known_hosts 

In addition, in my case I had to mention a specific port:

ssh-keygen -F "[<host>]:<port>" || ssh-keyscan -p <port> <host> >> ~/.ssh/known_hosts 
1
  • 2
    This answer is wrong since it uses an unprotected ssh-keyscan command to get the key. It does not answer how to "securely" get a key. Please do not use this answer. Commented Mar 22, 2022 at 21:23
0

Here's a simple way to set this up for automated install of dev machines. It doesn't require any fingerprint matching, the idea is to get a trusted set of entries and then just manually add them at install time in a way that avoids duplicates.

  1. Download the github ssh keys from here and commit that file in my setup repo. Make sure to check the certificate on the website to make sure it is valid.

  2. On a new machine, do the following:

    # create known_hosts if it doesn't exist known_hosts="$HOME/.ssh/known_hosts" test -f "$known_hosts" || touch "$known_hosts" # Remove all github entries ssh-keygen -R github.com # Add vetted github entries cat {setup-repo}/github-fingerprints.txt >> "$known_hosts" 

This is (mostly) idempotent, except that the removal of github.com entries makes a backup of the old known_hosts file each time. You can also just have a rm ~/.ssh/known_hosts.old* to get rid of all of those if you don't want them.

-2

To generate the known_hosts file, after generating your private and public keys and copied the public key to site.com, you can do

ssh -T [email protected] 
1
  • 1
    This answer is wrong since it uses an unprotected ssh command to get the key. It does not answer how to "securely" get a key. Please do not use this answer. Commented Mar 22, 2022 at 21:24

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.