Using git with multiple profiles and GPG+SSH keys
Tags: git, gpg, ssh, howto
Published: 2021-04-03 19:08:55

---
title: Using git with multiple profiles and GPG+SSH keys
published: true
date: 2021-02-14 00:00:00 UTC
tags: git, gpg, ssh, howto
canonical_url: https://markentier.tech/posts/2021/02/github-with-multiple-profiles-gpg-ssh-keys/
---

I work across 2 computers and 3 OS, all with nearly same development setup. For work I need some special care for my git config, since I want/need access to personal repositories.

The slightly more complex version control system configuration is due to the following requirements and expectations:

- **I want to use my personal (and main) email address for my personal GitHub projects.**
- I want to use my **work email** address for the **workplace stuff.**
- I want separate GPG keys for each email address.
- I want to sign my commits with the individual GPG keys for each of the addresses.
- I want separate SSH keys for each context (work, personal; but also for each computer).
- I want to use the individual SSH keys based on the project I work on.
- I want all of it happening as automatically as possible.
- I want to forget that it is there.

I know, I know, this looks like a huge list of requirements. But not only is this my personal preference, partially also my workplace has some strictler rules, especially when it comes to security.

So, if you cannot get rid of the first point and do need also access to your personal persona on GitHub (or any other git based hosting platform for that matter), this article might help you to achieve it.

By the way, this approach should work for both single and multiple GitHub accounts. Since GitHub makes it easy to keep using a single account while also being a member of an Enterprise organization, I haven't bothered testing it with a true multi-account configuration, but since you will use individual SSH and GPG keys for either way your computer won't really know the difference.

## SSH configuration

While `git` does support the HTTPS transport, in most cases you will use the more preferred way of talking _git+ssh_ instead. So let's tackle this lower level first:

### SSH Keys

If you working with GitHub you want to generate keys with the latest and greatest recommended algorithms:

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

If you work on other platforms, please check first, which algorithms are supported there.

In decreasing order of preference and security:

```
# prefer this if possible:
ssh-keygen -t ed25519

# this is still quite okay:
ssh-keygen -t ecdsa -b 521

# this only as a last resort option, should work everywhere:
ssh-keygen -t rsa -b 4096

# avoid this, please;
# also GitHub does not allow new keys with DSA anymore!
ssh-keygen -t dsa
```

More details about key generations at: [https://www.ssh.com/ssh/keygen/](https://www.ssh.com/ssh/keygen/)

### `~/.ssh/config`

```
### github

### -- PERSONAL/MAIN ACCOUNT --

Host github.com
  Hostname github.com
  User <YOUR GITHUB USERNAME>
  IdentityFile ~/.ssh/<YOUR PERSONAL SSH KEY>

### -- WORK PERSONA/ACCOUNT --

Host github.com-work
  Hostname github.com
  User <YOUR GITHUB USERNAME>
  IdentityFile ~/.ssh/<YOUR WORK SSH KEY>

### general

Host *
  AddKeysToAgent yes
  IdentitiesOnly yes
  PreferredAuthentications publickey
  Compression yes
```

**Important note:** Do not change the order of the configuration.

> For each parameter, the first obtained value will be used. The configuration files contain sections separated by ''Host'' specifications, and that section is only applied for hosts that match one of the patterns given in the specification. The matched host name is the one given on the command line.
> 
> Since the first obtained value for each parameter is used, more host-specific declarations should be given near the beginning of the file, and general defaults at the end. <footer><cite><a href="https://linux.die.net/man/5/ssh_config">ssh_config(5) manual</a></cite></footer>

Since we will use two different hosts, we must repeat the `Hostname` line, but also want to specify `User` and `IdentityFile` specifically. If you use your ssh config only for git and also have only a single user name, you could move that config line into the generic section in the bottom. Though I still prefer the explicit repetition for each specific Host block.

At this point you might wonder »How is _github.com-work_ supposed to function?« Hold that thought, we will come back to it later.

## GPG key management

You want to use GPG for signing commits. Your workplace might not require it (yet), but if you have any level of trust issues, or just want to be sure that a commit was made by you and be able to prove it, use commit signing.

Interestingly GitHub does not recommend you to use a more modern algorithm and [requires you to pick RSA.](https://docs.github.com/en/github/authenticating-to-github/generating-a-new-gpg-key) A bit sad, but RSA with 4096 bits seems to be still fine for this purpose.

After you have created your keys, you should grab the IDs for them:

```
gpg --list-secret-keys --keyid-format LONG

# I use shorter IDs and git doesn't seem to struggle
gpg --list-secret-keys --keyid-format SHORT
```

Example output:

```
/home/asaaki/.gnupg/pubring.kbx
-------------------------------
sec rsa4096/D73D7242 2021-02-14 [SC]
      AE93078BDC15BF6A84767DBBA3CBBB61D73D7242
uid [ultimate] TEST KEY <test-key@example.com>
ssb rsa4096/57047776 2021-02-14 [E]
```

The ID is the part after the `rsa4096/` from the `sec` line: `D73D7242`

_In the LONG variant it just used more characters from the whole fingerprint (which you can completely see in the line between sec and uid)._

## git shenanigans

The `git` command line interface (CLI) improved a lot over the years. I remember that I used a custom `GI_SSH=…` wrapper in my shell environments to make this whole host/key mapping possible, but thanks to the power of git configs this is a thing of the past.

### `~/.gitconfig`

```
[init]
  defaultBranch = main

[commit]
  gpgsign = true

# other sections cut for brevity; add the following to the bottom:

[user]
  name = Your Name
  useConfigOnly = true

# optional (if you sometimes work outside of your regular directories)
[include]
  path = ~/.gitconfig.personal

[includeIf "gitdir:~/Development/Personal/"]
  path = ~/.gitconfig.personal

[includeIf "gitdir:~/Development/Work/"]
  path = ~/.gitconfig.work
```

### `~/.gitconfig.personal`

```
[user]
  email = your.personal@email.here
  signingkey = <PERSONAL GPG SIGNING KEY ID>
```

### `~/.gitconfig.work`

```
[user]
  email = your.work@email.here
  signingkey = <WORK GPG SIGNING KEY ID>

[url "git@github.com-work"]
  insteadOf = git@github.com
```

### Explanation

`include` and `includeIf` are the key components to separate out specific configurations based on where you are on your system.

`include` is always pulled in, this is useful if you really want to separate out parts of your main `.gitconfig`.

If you only work in specific directories like `~/Development/Personal/` and `~/Development/Work/` — and this means where you also need to commit and push — then you could remove the general `[include]` section entirely. I keep it around, because I might have checked out a repository somewhere else and don't want to move it for a commit.

There's probably a better way to organize this, but the above has served me well for quite some time now.

I recently added the global `[user]` section for setting `useConfigOnly = true` and the name, since I only have one. `useConfigOnly` prevents git from guessing name and email and forces it to read it from the configuration files. If the email would be missing, git will complain the next time you try to commit or push. And you will know that something is broken in the configuration.

**Important note:** The trailing slashes (`/`) on the `[includeIf …]` lines are very important. If you forget them, then git would try to match only this very specific folder and ignore it for any folder within it. More details about **conditional includes** in the [git documentation](https://git-scm.com/docs/git-config#_conditional_includes). _(I totally missed that you can use them even for branches, too.)_

The `signingkey` values should be set appropriately based on the IDs you have noted from the [previous section](https://markentier.tech/posts/2021/02/github-with-multiple-profiles-gpg-ssh-keys/#gpg-key-management) of this article. Now when you commit anything git will use the correct key based on where the repository directory lives.

To automatically enforce the commit signing, use `commit.gpgsign` set to `true`.

Remember the question you had earlier about the weird looking Host configuration in SSH? The `[url "git@github.com-work"]` section is the counterpart making it work, because git will do the translation when you are in your company's repos.

What will happen is the following:

- You are in a **work** related repository.
- You want to _fetch/pull_ the latest changes or _push_ your local state to the origin.
- Since git will load the **work** config, it replaces the regular URLs having `git@github.com` in it with the value of the `[url …]`, here `git@github.com-work`.
- git reaches one level down and uses ssh for the communication.
- SSH sees a `github.com-work` host and tries to find all matching configurations for that, including the exact one we have [defined above](https://markentier.tech/posts/2021/02/github-with-multiple-profiles-gpg-ssh-keys/#ssh-config).
- SSH will ignore our personal configuration, because the regular `github.com` does not match anymore.
- SSH picks up your **work** identity file for authentication with GitHub's server.
- SSH will also use the actual `Hostname` instead of `Host` (translating it back again).
- Everyone is happy. \o/

* * *

_What's with this `[init]` block you haven't mentioned before?_

Since Git 2.28 you can set a name for the default branch when creating a new repository. Be a good citizen and avoid offensive and negatively connotated terms. The wider developer community seems to like "main" as a good replacement for the previous default name. You can read more about [renaming existing default brances](https://github.com/github/renaming) on GitHub.

### Bonus

To quickly check which config is used execute the following command in a repository:

```
git remote -v
```

The URL should provide you a quick hint if the correct profile is used.

Alternatively you can also run:

```
git config --show-origin --get user.email
```

This will also print where the final value was retrieved from.

* * *

Git came a long way and I'm very glad and happy that we can have such setup without a lot of manual tinkering and workarounds. Gone are all my custom scripts, snippets, and `.envrc`'s for that purpose, which were also not completely platform agnostic.