5 min read

Managing multiple SSH keys

Syed Aslam

Though it is generally the case and is considered a good practice to have only one public-private key pair per device, sometimes you might need to use more than one. You might use an SSH key pair for your personal projects and a different one for work repositories. On top of that, and you could be using another key to access the client's server.

Managing SSH keys can become cumbersome as soon as the need to use a second key arises. Traditionally, you would use ssh-add to add your keys to ssh-agent, typing in the password for each key as needed. The problem, however, is that this needs to be done every time the computer is restarted since ssh-agent will also restart and the keys have to be added along with typing in the passwords for each key all over again.

Fortunately, the OpenSSH client offers the possibility to manage several keys. The solution for this is to automate adding and storing keys and to specify which key to use which repository by creating a configuration file to specify the customizations. For this purpose, the file ~/.ssh/config with details about hosts using different keys (called "identities") is used.

SSH Config

SSH Config is a per-user configuration file for SSH communication.

Create a new file, if you don't already have one, at ~/.ssh/config. If this config file is new, you might have to do chmod 600 ~/.ssh/config for permissions.

On Linux and macOS, verify that the permissions on your IdentityFile are 400. SSH will reject, in a not very explicit manner, the SSH keys that are too readable. Tighten it a bit by:

  chmod 400 ~/.ssh/id_ed25519

The Ed25519 was introduced on OpenSSH version 6.5 and is now the recommended way to generate ssh key pair. This is EdDSA implementation using the Twisted Edwards curve. It uses elliptic curve cryptography that offers better security with faster performance compared to DSA or ECDSA. Today, the RSA is the most widely used public-key algorithm for SSH keys. But compared to Ed25519, it's slower and even considered not safe if it's generated with a key smaller than 2048-bit length.

The Ed25519 public-key is compact, contains 68 characters compared to RSA 3072 that has 544 characters. Generating the key is also almost as fast as the signing process. It's also fast to perform batch signature verification with Ed25519. It is built to be collision resilience. Hash-function collision won't break the system.

  $ ssh-keygen -t ed25519 -C "your_email@example.com"

This creates a new ssh key, using the provided email as a label. If your system doesn't support the Ed25519 algorithm, use:

  ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

I have two keys, id_25519.pub for work repositories and id_25519_personal.pub for my personal projects.

Forward, the first problem to solve using this config is to avoid having to add custom-named SSH keys using ssh-add. Assuming your private key is named ~/.ssh/id_ed25519, add the following to the config file:

Host github.com
  HostName github.com
  User github-corporate-user # GitHub username
  AddKeysToAgent yes
  UseKeychain yes
  PreferredAuthentications publickey
  IdentityFile ~/.ssh/id_ed25519

The base URI of Host github.com in config and the base URI of the repository that you want to work with must match.

Next, the second key for your personal projects with a custom hostname:

Host github-personal
  HostName github.com
  User github-personal-user # GitHub username
  UseKeychain yes
  PreferredAuthentications publickey
  IdentityFile ~/.ssh/id_ed25519_personal
  IdentitiesOnly yes
  AddKeysToAgent yes

Note the identitiesOnly yes in the above setting. This is because the configuration alone does not ensure that the specified key is used. This is because the ssh-agent has its own logic as to which key is used and the IdentityFile information only adds these keys to the list of possible keys.

To restrict the selection to the key specified here, you need the set the IdentitiesOnly - either host-specific or for all hosts. In the latter case, however, a key must be specified explicitly for each host like it is done here.

Test your connection

$ ssh -T git@github.com
$ ssh -T git@github-personal

With each command, if you've used RSA keys, you may see this kind of warning, type yes:

The authenticity of host 'github.com (192.30.252.1)' can't be established.
RSA key fingerprint is xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:
Are you sure you want to continue connecting (yes/no)?

If everything is OK, you will see these messages:

Hi your_name! You've successfully authenticated, but GitHub does not provide shell access. Hi your_name! You've successfully authenticated, but GitHub does not provide shell access.

Anyways, with the configuration in place, now you can git clone git@github-personal:github-account/interesting-project.git.

If you had already cloned the repository, you can change the remote url, if you want, by:

  git remote set-url origin git@github-personal:github-account/interesting-project.git git@github.com:some-github-user/interesting-project.git

Changes URLs for the remote. Sets first URL for remote that matches regex (first URL if no is given) to . If doesn’t match any URL, an error occurs and nothing is changed. More

Configure Git Identity

You can configure an individual repo to use a specific user / email address which overrides the global configuration. From the root of the repo, run:

  $> git config user.name "Your Name"
  $> git config user.email "your@email.com"

Or you could edit the config file to add the user identity directly $> git config --local -e and add the following:

[user]
      name = Your Name
      email = your@email.com

If you require different emails to be used for different repositories, from Git v2.13 you can set the email on a directory basis by editing the global config ~/.gitconfig using conditionals like so:

[user]
  name = Your Name
  email = your@email.com

[includeIf "gitdir:~/private/"]
  path = ~/private/.gitconfig

And then your private config at ~/private/.gitconfig would look like:

[user]
  email = other@email.com

Per repository settings

It turns out you can also define repository-specific keys overriding the configuration in ~/.ssh/config file. There are, at least, two ways to do this.

One, using the GIT_SSH_COMMAND. From Git version 2.3.0, you can use the environment variable GIT_SSH_COMMAND like this:

  GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa" git clone git@github-corporate:company/project.git

Note that -i can sometimes be overridden by your config file, in which case, you should give SSH an empty config file, like this:

  GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -F /dev/null" git clone git@github-corporate:company/project.git

Second, via core.sshCommand. From Git version 2.10.0, you can configure this per repo or globally, so you don't have to set the environment variable anymore!

  git config core.sshCommand "ssh -i ~/.ssh/id_rsa -F /dev/null"
  git pull
  git push