A verified Git identity on macOS
Curiosity got me into this more than any need for security. GitHub will talk to you over HTTPS or SSH, and once I noticed the SSH option sitting there I wanted to know what it was like to use. One rabbit hole later, SSH was handling my authentication and a small green “Verified” badge had appeared next to each of my commits. It was never really about being safer, even if it is a little. It was about finishing the setup properly: doing the thing the way it looks like it was meant to be done. It turned out clean enough that I never went back, so this is the version I would hand a friend.
I will be honest that I cannot give you a hard technical reason to prefer SSH over HTTPS for everyday Git. They behave the same once they work. The real reasons are that I wanted to see how it went, it feels like the more professional way to do this, and at the end of it your commits can be signed. Make of that what you will.
I use two keys, one to authenticate and one to sign. A single key could do both, but SSH and GitHub treat the two jobs as separate things, and whoever drew that line presumably had reasons. Following their intent costs nothing here: it is a one-time setup, and it means the key that logs me in and the key that vouches for my commits can be replaced independently later.
Before you start
You need Homebrew and the GitHub CLI, signed in once. The CLI is what saves us from clicking around the website to upload keys:
brew install gh
gh auth login
Two keys, both Ed25519
Ed25519 is the elliptic-curve key type, and it is a small pleasure: the keys and signatures are tiny, signing is fast, and you get more security per byte than the older RSA keys. There is no real reason to reach for anything else now.
Make one key for authentication and one for signing:
ssh-keygen -t ed25519 -C "github auth" -f ~/.ssh/github_auth
ssh-keygen -t ed25519 -C "github signing" -f ~/.ssh/github_sign
The -C text is just a label baked into the key so you can recognise it later;
it can be anything. Give each key a passphrase when prompted. A passphrase is a
password that locks the key file, so a copied key is useless without it. Pick
one you can remember, or keep it in your password manager. You will rarely type
it in practice, because macOS is about to store it in the keychain for you. And
if you ever lose it, nothing is broken: an SSH key is disposable, so you delete
the old one on GitHub, generate a fresh one, and carry on. Anything you already
signed stays Verified: GitHub records that at push time, so rotating a key
leaves your past commits untouched.
Authenticate over SSH
Authentication here means your Mac proving it is allowed to push, without typing
a password every time. The auth key does that. Tell SSH when to use it, in
~/.ssh/config (create the file if it is not there):
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/github_auth
IdentitiesOnly yes
AddKeysToAgent yes
UseKeychain yes
IgnoreUnknown UseKeychain
Reading it line by line:
Host github.commeans these settings apply whenever you talk to GitHub.IdentityFilepoints at the auth key, so SSH does not go rummaging through every key you own.IdentitiesOnly yesholds it to exactly that, offering only this key instead of trying each one in turn (trying them all can trip GitHub’s rate limits).AddKeysToAgentandUseKeychainare the convenience I was after: macOS stores the passphrase in your keychain and loads the key for you, so SSH ends up as quiet as HTTPS. If this had been a chore, I would have gone back to HTTPS and SSH would have lost me.IgnoreUnknown UseKeychainis insurance.UseKeychainis an Apple-only keyword, and a machine whose SSH has never heard of it would otherwise refuse to read this whole file. This line tells it to quietly skip just that keyword. I only use macOS, but it costs nothing and lets the same config travel.
The indentation, by the way, is for you and not for SSH, which ignores leading whitespace entirely. Two spaces, four, or a tab all read the same to it. I use two.
Now store the passphrase in your keychain and hand the public half of the key to
GitHub. Only the .pub file ever leaves your machine; the private one, the file
without .pub, never does:
ssh-add --apple-use-keychain ~/.ssh/github_auth
gh ssh-key add ~/.ssh/github_auth.pub \
--type authentication \
--title "$(hostname -s) auth"
Check it worked:
ssh -T git@github.com
The first time, SSH asks whether to trust GitHub’s host key; type yes. Then
GitHub greets you by username and adds that it cannot give you a shell. That
looks like a rejection but is exactly right; pushing and pulling still work.
Sign your commits
Signing is the other half. A signature is a commit saying “this really came from me,” in a way others can check. When it checks out GitHub shows the green badge, and, more to the point, nobody can quietly push a commit under your name and have it look genuine.
Upload the signing key. GitHub keeps signing keys in a list of their own, just below the SSH auth keys on the same page:
gh ssh-key add ~/.ssh/github_sign.pub \
--type signing \
--title "$(hostname -s) signing"
Then tell Git to sign with it, in ~/.gitconfig:
[user]
name = Your Name
email = you@example.com
signingkey = ~/.ssh/github_sign.pub
[gpg]
format = ssh
[gpg "ssh"]
allowedSignersFile = ~/.ssh/allowed_signers
[commit]
gpgsign = true
[tag]
gpgsign = true
A few of those lines earn a word:
emailhas to be an address GitHub has verified for you. That is how it ties a commit back to your account.gpg.format = sshis the interesting one. Git can sign with GPG or SSH, and this picks SSH, so the key you already made does double duty: no second tool to install, no keyserver, nothing that expires. GitHub lists SSH keys above GPG keys in its own settings, which I chose to read as a hint.commit.gpgsign = truesigns every commit automatically, so it is never a thing you have to remember.allowedSignersFilelets your own Mac verify signatures, not just GitHub. Without it,git logcan show a signature but not whether it is any good.
Add your signing key to that allowed-signers file. Each line is an email, the
namespace it applies to, and the public key; namespaces="git" keeps the key
scoped to Git rather than anything else you might sign:
echo "you@example.com namespaces=\"git\" $(cat ~/.ssh/github_sign.pub)" \
>> ~/.ssh/allowed_signers
See that it worked
Make a commit and look at it:
git commit -m "First signed commit"
git log --show-signature -1
Locally, git log now vouches for the signature using that allowed-signers
file. On GitHub, the commit shows as Verified. That green badge is the part you
can see; the useful part is everything under it.
If a commit comes back Unverified instead, it is almost always the email: the
address in git config user.email has to match one your GitHub account has
verified. And only commits made after you turned signing on are signed; the
earlier ones stay as they were.
Clone over SSH
From here on, clone with the SSH address rather than the HTTPS one:
git clone git@github.com:you/repo.git
The config you wrote earlier quietly sends it through your auth key. Day to day, this is the bit that feels identical to HTTPS, which is exactly the point.
All of it, in one script
The same steps in one place, what I run on a new machine and what you can hand to a friend. It is an alternative to the walkthrough above, not an addition, so do one or the other. Fill in the two values at the top and run it; it pauses to ask for a passphrase as it makes each key.
#!/usr/bin/env bash
set -euo pipefail
name="Your Name"
email="you@example.com" # an address GitHub has verified for you
host="$(hostname -s)"
auth="$HOME/.ssh/github_auth"
sign="$HOME/.ssh/github_sign"
# two Ed25519 keys
ssh-keygen -t ed25519 -C "github auth" -f "$auth"
ssh-keygen -t ed25519 -C "github signing" -f "$sign"
# teach SSH about the auth key
cat >> "$HOME/.ssh/config" <<CONFIG
Host github.com
HostName github.com
User git
IdentityFile $auth
IdentitiesOnly yes
AddKeysToAgent yes
UseKeychain yes
IgnoreUnknown UseKeychain
CONFIG
ssh-add --apple-use-keychain "$auth"
# hand both keys to GitHub
gh ssh-key add "$auth.pub" --type authentication --title "$host auth"
gh ssh-key add "$sign.pub" --type signing --title "$host signing"
# sign every commit with the signing key
git config --global user.name "$name"
git config --global user.email "$email"
git config --global gpg.format ssh
git config --global user.signingkey "$sign.pub"
git config --global gpg.ssh.allowedSignersFile "$HOME/.ssh/allowed_signers"
git config --global commit.gpgsign true
git config --global tag.gpgsign true
# trust your own signature locally
echo "$email namespaces=\"git\" $(cat "$sign.pub")" \
>> "$HOME/.ssh/allowed_signers"
# confirm the connection
ssh -T git@github.com || true
Two keys, a little config, and your Git identity is both authenticated and signed. The “Verified” badge is just the part that shows.