10 Set up keys for SSH

When we interact with a remote Git server, such as GitHub, we have to include credentials in the request. This proves we are a specific GitHub user, who’s allowed to do whatever we’re asking to do.

Git can communicate with a remote server using one of two protocols, HTTPS or SSH, and the different protocols use different credentials.

Here we describe the credential setup for the SSH protocol. If you’re not sure whether to use HTTPS or SSH, please read HTTPS versus SSH. From now on, we assume you’ve made an intentional choice to set up SSH keys.

10.1 SSH keys

SSH keys provide a more secure way of logging into a server than using a password alone. While a password can eventually be cracked with a brute force attack, SSH keys are nearly impossible to decipher by brute force alone. Generating a key pair provides you with two long strings of characters: a public and a private key. You can place the public key on any server (like GitHub!), and then unlock it by connecting to it with a client that already has the private key (your computer!). When the two match up, the system unlocks without the need for a password. You can increase security even more by protecting the private key with a passphrase.

Adapted from instructions provided by GitHub and Digital Ocean.

10.2 SSH outline and advice

High level overview of what must happen:

  • Create a public-private SSH key pair. Literally, 2 special files, in a special place. Optionally, encrypt the private key with a passphrase (best practice).
  • Add the private key to your ssh-agent. If you protected it with a passphrase, you may have additional configuration.
  • Add your public key to your GitHub profile.

Advice:

  • If you are new to programming and the shell, you’ll probably find HTTPS easier at first (chapter 9). You can always switch to SSH later. You can use one method from computer A and the other from computer B.
  • You should swap out your SSH keys periodically. Something like once a year.
  • It’s best practice to protect your private key with a passphrase. This can make setup and usage harder, so if you’re not up for that (yet), either don’t use a passphrase or seriously consider using HTTPS instead.
  • Don’t do weird gymnastics in order to have only one key pair, re-used over multiple computers. You should probably have one key per computer (I do this). Some people even have one key per computer, per service (I do not do this).
  • It is normal to associate multiple public keys with your GitHub account. For example, one public key for each computer you connect with.

10.3 Do you already have keys?

You can check this from RStudio or from the shell.

Global advice: if you do have existing keys, but have no clue where they came from or why you created them, you should seriously consider creating a new SSH key pair. It’s up to you to figure out whether/how to delete the old ones. But don’t let that keep you from creating new keys and moving forward.

10.3.1 From RStudio

Go to Tools > Global Options…> Git/SVN. If you see something like ~/.ssh/id_rsa in the SSH RSA Key box, you definitely have existing keys.

Caveat: RStudio only looks for a key pair named id_rsa and id_rsa.pub. This makes sense, because historically that has been the most common.

However, these days both GitHub and GitLab are encouraging users to generate SSH keys with the Ed25519 algorithm, which results in a key pair named id_ed25519 and id_ed25519.pub. At the time of writing, RStudio will not display such a key pair, which can be confusing. Therefore, it’s probably a good idea to also check for existing keys in the shell.

10.3.2 From the shell

Go to the shell (appendix A).

List existing keys:

ls -al ~/.ssh/

If you are told ~/.ssh/ doesn’t exist, you don’t have SSH keys!

If you see a pair of files like id_rsa.pub and id_rsa or id_ed25519 and id_ed25519.pub, you have a key pair already. The typical pattern is id_FOO.pub (the public key) and id_FOO (the private key), where FOO reflects the key type. If you’re happy to stick with your existing keys, skip to the sections about adding a key to the ssh-agent and GitHub.

10.4 Create an SSH key pair

10.4.1 Option 1: Set up from RStudio

Go to Tools > Global Options…> Git/SVN > Create RSA Key….

RStudio prompts you for a passphrase. It is optional, but also a best practice. Configuring your system for smooth operation with a passphrase-protected key introduces more moving parts. If you’re completely new at all this, skip the passphrase (or use HTTPS!) and implement it next time, when you are more comfortable with system configuration. I did not use a passphrase at first, but I do now, and record it in a password manager.

Click “Create” and RStudio will generate an SSH key pair, stored in the files ~/.ssh/id_rsa and ~/.ssh/id_rsa.pub.

Note that RStudio currently only generates RSA keys, whereas the standard recommendation by GitHub and GitLab is to use Ed25519 keys. If you want to comply with that advice, generate your keys in the shell for now.

10.4.2 Option 2: Set up from the shell

Create the key pair like so, but substitute a comment that means something to you, especially if you’ll have multiple SSH keys in your life. Consider the email associated with your GitHub account or the name of your computer or some combination, e.g. your_email@example.com or macbook-pro or jane-2020-macbook-pro.

ssh-keygen -t ed25519 -C "DESCRIPTIVE-COMMENT"

If it appears that your system is too old to support the Ed25519 algorithm, do this instead:

ssh-keygen -t rsa -b 4096 -C "DESCRIPTIVE-COMMENT"

Accept the proposal to save the key in the default location. Just press Enter here:

Enter file in which to save the key (/Users/jenny/.ssh/id_ed25519):

You have the option to protect the key with a passphrase. It is optional, but also a best practice. Configuring your system for smooth operation with a passphrase-protected key introduces more moving parts. If you’re completely new at all this, skip the passphrase and implement it next time, when you are more comfortable with system configuration. I did not use a passphrase at first, but I do now, and record it in a password manager.

Enter passphrase (empty for no passphrase):
Enter same passphrase again: 

The process should complete now and should have looked like this:

~ % ssh-keygen -t ed25519 -C "jenny-2020-mbp"        
Generating public/private ed25519 key pair.
Enter file in which to save the key (/Users/jenny/.ssh/id_ed25519): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /Users/jenny/.ssh/id_ed25519.
Your public key has been saved in /Users/jenny/.ssh/id_ed25519.pub.
The key fingerprint is:
SHA256:XUEaY/elhcQJz3M9jx/SdC0zh10lCA7uNpqgkm5G/R0 jenny-2020-mbp
The key's randomart image is:
+--[ED25519 256]--+
|        . =o==oo*|
|       . + =.=+B+|
|        . o . @oB|
|       . . .  oO+|
|  . .   S .  ..o.|
| o o . E .    ...|
|+ . . + .       .|
|.+   . .         |
|o.               |
+----[SHA256]-----+

10.4.3 Add key to ssh-agent

Tell your ssh-agent about the key and, especially, set it up to manage the passphrase, if you chose to set one.

Things get a little OS-specific around here. When in doubt, consult GitHub’s instructions for SSH, which is kept current for Mac, Windows, and Linux. It also accounts for more unusual situations than I can.

10.4.3.1 Mac OS

Make sure ssh-agent is enabled. Here’s what success look like (the pid will vary):

~ % eval "$(ssh-agent -s)"
Agent pid 15360

Sometimes this fails like so:

~ % eval "$(ssh-agent -s)"
mkdtemp: private socket dir: No such file or directory

A similar failure might be reported as “Permission denied”. You should try again, but as the superuser. Don’t forget to use exit to go back to your normal user account, when you are done!

~ % sudo su
Password:
sh-3.2# eval "$(ssh-agent -s)"
Agent pid 15385
sh-3.2# exit
exit

Add your key to the ssh agent. If you set a passphrase, you’ll be challenged for it here. Give it. The -K option stores your passphrase in the keychain.

~ % ssh-add -K ~/.ssh/id_ed25519
Enter passphrase for /Users/jenny/.ssh/id_ed25519: 
Identity added: /Users/jenny/.ssh/id_ed25519 (jenny-2020-mbp)

If you’re on macOS Sierra 10.12.2 and higher, you need to do one more thing. Create a file ~/.ssh/config with these contents:

Host *
  AddKeysToAgent yes
  UseKeychain yes
  IdentityFile ~/.ssh/id_ed25519

You can omit the line about UseKeychain if you didn’t use a passphrase. But if you did, this should store your passphrase persistently in the keychain. Otherwise, you will have to enter it every time you log in. Useful StackOverflow thread: How can I permanently add my SSH private key to Keychain so it is automatically available to ssh?.

10.4.3.2 Windows

In a Git Bash shell, make sure ssh-agent is running:

$ eval $(ssh-agent -s)
Agent pid 59566

Add your key, substituting the correct name for your key.

$ ssh-add ~/.ssh/id_ed25519

10.4.3.3 Linux

In a shell, make sure ssh-agent is running:

$ eval "$(ssh-agent -s)"
Agent pid 59566

Add your key, substituting the correct name for your key.

ssh-add ~/.ssh/id_ed25519

10.5 Provide public key to GitHub

Now we store a copy of your public key on GitHub.

10.5.1 RStudio to clipboard

Go to Tools > Global Options…> Git/SVN. If your key pair is named like id_rsa.pub and id_rsa, RStudio will see it and offer to “View public key”. Do that and accept the offer to copy to your clipboard.

If your key pair is named differently, such as id_ed25519.pub and id_ed25519, you’ll have to copy the public key another way.

10.5.2 Shell to clipboard

Copy the public key onto your clipboard. For example, open ~/.ssh/id_ed25519.pub in an editor and copy the contents to your clipboard. Or do one of the following at the command line:

  • Mac OS: pbcopy < ~/.ssh/id_ed25519.pub
  • Windows: clip < ~/.ssh/id_ed25519.pub
  • Linux: xclip -sel clip < ~/.ssh/id_ed25519.pub

Linux: if needed, install xclip via apt-get or yum. For example, sudo apt-get install xclip.

10.5.3 On GitHub

Now we register the public key with GitHub. Click on your profile pic in upper right corner and go to Settings > SSH and GPG keys. Click “New SSH key”. Paste your public key in the “Key” box. Give it an informative title, presumably repeating the descriptive comment you used above, during key creation. Click “Add SSH key”.

In theory, we’re done! You can use ssh -T git@github.com to test your connection to GitHub. If you’re not sure what to make of the output, see the link for details. Of course, the best test is to work through the realistic usage examples elsewhere in this guide.

10.6 Troubleshooting

10.6.1 HTTPS URL when you meant to use SSH

If you think you have SSH set up correctly and yet you are still challenged for credentials, consider this: for the repo in question, have you possibly set up GitHub, probably called origin, as an HTTPS remote, instead of SSH?

How to see the remote URL(s) associated with the current repo in the shell:

git remote -v

An SSH remote will look like this:

git@github.com:USERNAME/REPOSITORY.git

whereas an HTTPS remote will look like this:

https://github.com/USERNAME/REPOSITORY.git

You can fix this with git remote set-url, which is demonstrated in URL determines the protocol.

10.6.2 git2r – or some other tool – can’t find SSH keys on Windows

Have you seen this error message?

Error in .local(object, ...) : 
  Error in 'git2r_push': error authenticating: failed connecting agent

We’ve seen it when working with Git/GitHub from R via the git2r package.

The root cause is confusion about the location of .ssh/ on Windows. R’s idea of your home directory on Windows often differs from the default location of config files for Git and ssh, such as .ssh/. On *nix systems, these generally coincide and there’s no problem.

Two important directories on Windows are the user’s HOME and USERPROFILE. R usually associates ~ with HOME, but Git and ssh often consult USERPROFILE for their config files. On my Windows 10 VM, I see:

normalizePath("~")
#> [1] "C:\\Users\\JennyVM\\Documents"

as.list(Sys.getenv(
  c("HOME", "USERPROFILE")
))
#> $HOME
#> [1] "C:/Users/JennyVM/Documents"
#> 
#> $USERPROFILE
#> [1] "C:\\Users\\JennyVM"

list.files(
  Sys.getenv("USERPROFILE"),
  pattern = "ssh|git",
  include.dirs = TRUE,
  all.files = TRUE
)
#> [1] ".gitconfig" ".ssh"

Two workarounds:

  • Tell git2r explicitly where to find your public and private key and pass the resulting cred object to your git2r calls.

    cred <- git2r::cred_ssh_key(
      publickey = "~/../.ssh/id_rsa.pub",
      privatekey = "~/../.ssh/id_rsa"
    )
  • Create a symbolic link so that .ssh/ in R’s home directory points to your actual .ssh/ directory. Example contributed by Ian Lyttle on Windows 7 using Command Prompt:

    MKLINK /D "C:\Users\username\Documents\.ssh" "C:\Users\username\.ssh"

Finally, if git2r seems unable to get your SSH passphrase from ssh-agent, install the getPass package:

install.packages("getPass")

and git2r should launch a popup where you can enter your passphrase. Thanks to Ian Lyttle for this tip.

This link provides a great explanation of the uncertainty about where .ssh/ and user’s .gitconfig are located on Windows: git on Windows - location of configuration files. Bottom line: place your config and keys where your main tool expects them to be and create symbolic links to help other tools find this stuff.

10.6.3 Other

Other things to double-check:

  • Did you add the SSH to your ssh-agent?
  • Did you configure Mac OS Sierra or High Sierra to persistently store your passphrase in the keychain?
  • Did you add the public key to GitHub?