Skip to content
Open
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
299 changes: 225 additions & 74 deletions practices/guides/commit-signing.md
Original file line number Diff line number Diff line change
@@ -1,92 +1,202 @@
# Git commit signing setup guide

- [Git commit signing setup guide](#git-commit-signing-setup-guide)
- [From Workstations](#from-workstations)
- [macOS](#macos)
- [Windows](#windows)
- [From Pipelines](#from-pipelines)
- [GitHub Actions](#github-actions)
- [AWS CodePipeline](#aws-codepipeline)
- [Troubleshooting](#troubleshooting)
Using GPG, SSH, or S/MIME, you can sign commits and tags locally. These commits and tags are marked as verified on GitHub so other people can be confident that the changes come from a trusted source.

## From Workstations
The instructions on this page focus on GPG and SSH.

### macOS
> You should only set up **one** of these options - **don't attempt to set up GPG and SSH commit signing**!

- Install the [Brew package manager](https://brew.sh)
See the full GitHub documentation [here](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification).

```bash
brew upgrade
brew install gnupg pinentry-mac
gpg --full-generate-key
```
## GPG commit signing

- Accept the defaults, Curve 25519 etc.
- Enter your GitHub account name as the Real Name
- Enter your GitHub account email as the Email Address
- Avoid adding a comment (this *may* prevent git from auto-selecting a key - see Troubleshooting section below)
- You can use the privacy *@users.noreply.github.com* email address listed in the GitHub profile: *Settings > Email*
- Define a passphrase for the key and keep it in your password manager
### From Workstations

```bash
gpg --armor --export ${my_email_address} | pbcopy
```
If you have already committed and need to retrospectively sign commits, follow the instructions below, then follow the [retrospective commit signing instructions](./retrospective-commit-signing.md).

- Public key is now in your clipboard - in your GitHub account add it to your profile via *Settings > SSH and GPG Keys> Add New GPG Key*
- Paste it in
#### macOS

```bash
git config --global user.email ${my_email_address} # same one used during key generation
git config --global user.name ${my_username}
git config --global commit.gpgsign true
sed -i '' '/^export GPG_TTY/d' ~/.zshrc
echo export GPG_TTY=\$\(tty\) >> ~/.zshrc
source ~/.zshrc
PINENTRY_BIN=$(whereis -q pinentry-mac)
sed -i '' '/^pinentry-program/d' ~/.gnupg/gpg-agent.conf
echo "pinentry-program ${PINENTRY_BIN}" >> ~/.gnupg/gpg-agent.conf
gpgconf --kill gpg-agent
```
1. Install `gnupg` & `pinentry-mac` with [Brew](https://brew.sh):

The first time you commit you will be prompted to add the GPG key passphrase to the macOS Keychain. Thereafter signing will happen seamlessly without prompts.
```bash
brew upgrade
brew install gnupg pinentry-mac
sed -i '' '/^export GPG_TTY/d' ~/.zshrc
echo export GPG_TTY=\$\(tty\) >> ~/.zshrc
source ~/.zshrc
PINENTRY_BIN=$(whereis -q pinentry-mac)
mkdir -p ~/.gnupg
touch ~/.gnupg/gpg-agent.conf
sed -i '' '/^pinentry-program/d' ~/.gnupg/gpg-agent.conf
echo "pinentry-program ${PINENTRY_BIN}" >> ~/.gnupg/gpg-agent.conf
gpgconf --kill gpg-agent
```

Most of the published solutions for this don't work because *brew* seems to have moved the default folder for binaries, plus many guides contain obsolete settings for *gpg-agent*.
1. Create a new GPG key:

### Windows
```bash
gpg --full-generate-key
```

- Install [Git for Windows](https://git-scm.com/download/win), which includes Bash and GnuPG
- Right-click on the Desktop > *Git Bash Here*
1. Pick `RSA and RSA`, or `RSA (sign only)` (there is no elliptic curve cryptography (ECC) support at the time of writing)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we suggest the ECDSA (over Curve25519) algorithm to be used as default? It's considered being more modern and more often used these days.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I run: gpg --full-generate-key

I get the following output:

╰─ gpg --full-generate-key
gpg (GnuPG) 2.4.8; Copyright (C) 2025 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Please select what kind of key you want:
   (1) RSA and RSA
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
   (9) ECC (sign and encrypt) *default*
  (10) ECC (sign only)
  (14) Existing key from card
Your selection? 9
Please select which elliptic curve you want:
   (1) Curve 25519 *default*
   (4) NIST P-384
   (6) Brainpool P-256
Your selection? 1
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated :)

1. `keysize` `4096` bits (the minimum accepted for GitHub)
1. Select a key expiry time (personal choice)
1. `Real name` Your GitHub handle
1. `Email address` Your GitHub account email [listed on your GitHub profile](https://github.com/settings/emails) (you can use the privacy *@users.noreply.github.com* email address): `Settings` -> `Emails` -> `Keep my email addresses private`)

```bash
gpg --full-generate-key
```
> If you go for the private email option, consider enabling `Block command line pushes that expose my email`.

- Pick *RSA and RSA*, or *RSA (sign only)* - there is no elliptic curve cryptography (ECC) support at the time of writing
- Set key size to 4096 bit, the minimum accepted for GitHub
- Enter your GitHub account name as the Real Name
- Enter your GitHub account email as the Email Address
- Avoid adding a comment (this *may* prevent git from auto-selecting a key - see Troubleshooting section below)
- You can use the privacy *@users.noreply.github.com* email address listed in the GitHub profile: *Settings > Email*
- Define a passphrase for the key and keep it in your password manager
1. Avoid adding a comment (this *may* prevent git from auto-selecting a key - see Troubleshooting section below)
1. Review your inputs and press enter `O` to confirm
1. Define a passphrase for the key

```bash
gpg --armor --export ${my_email_address} | clip
```
1. Test the key is visible and export the PGP PUBLIC KEY (to your clipboard):

- Public key is now in your clipboard - in your GitHub account add it to your profile via *Settings > SSH and GPG Keys> Add New GPG Key*
- Paste it in
```bash
gpg -k # This should list the new key
gpg --armor --export <my_email_address> | pbcopy
```

```bash
git config --global user.email ${my_email_address} # same one used during key generation
git config --global user.name ${my_username}
git config --global commit.gpgsign true
```
> Your PGP PUBLIC KEY is now in your clipboard!

1. [Add the public key to your GitHub account](https://github.com/settings/gpg/new) (`Settings` -> `SSH and GPG keys` -> `New GPG key`)

> Note the `Key ID` as you'll need this in the next step.

1. Set your local git config to use GPG signing:

```bash
git config --global user.email <my_email_address> # same one used during key generation
git config --global user.name <github_handle>
git config --global user.signingkey <key_id>
git config --global commit.gpgsign true
git config --global tag.gpgsign true
```

1. Test it works:

1. Create a temporary branch of your favourite repository.
1. Make an inconsequential whitespace change.
1. Commit the change.
1. You will be prompted for your GPG key passphrase - optionally select to add it to the macOS Keychain.
1. Check the latest commit shows a successful signing:

```bash
$ git log --show-signature -1
...
gpg: Good signature from "<github_handle> <<my_email_address>>" [ultimate]
...
```

#### Windows/WSL

1. Install (as administrator) [Git for Windows](https://git-scm.com/download/win) (which includes Bash and GnuPG)
1. Open `Git Bash`
1. Create a new GPG key:

```bash
gpg --full-generate-key
```

1. Pick `RSA and RSA`, or `RSA (sign only)` (there is no elliptic curve cryptography (ECC) support at the time of writing)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we suggest the ECDSA (over Curve25519) algorithm to be used as default? (same as on macOS version)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated :)

1. `keysize` = `4096` bits (the minimum accepted for GitHub)
1. Select a key expiry time (personal choice)
1. `Real name` = Your GitHub handle
1. `Email address` = Your GitHub account email [listed on your GitHub profile](https://github.com/settings/emails) (you can use the privacy *@users.noreply.github.com* email address): `Settings` -> `Emails` -> `Keep my email addresses private`)

> If you go for the private email option, consider enabling `Block command line pushes that expose my email`.

1. Avoid adding a comment (this *may* prevent git from auto-selecting a key - see Troubleshooting section below)
1. Review your inputs and press enter `O` to confirm
1. A new window called pinentry will appear prompting you to enter a passphrase.

1. Test the key is visible and export the PGP PUBLIC KEY (to your clipboard):

```bash
gpg -k # This should list the new key
gpg --armor --export <my_email_address> | clip
```

> Your PGP PUBLIC KEY is now in your clipboard!

1. [Add the public key to your GitHub account](https://github.com/settings/gpg/new) (`Settings` -> `SSH and GPG keys` -> `New GPG key`)

> Note the `Key ID` as you'll need this in the next step.

1. Set your local git config to use GPG signing:

When you commit you will be prompted to enter the GPG key passphrase into a Pinentry window.
```bash
git config --global user.email <my_email_address> # same one used during key generation
git config --global user.name <github_handle>
git config --global user.signingkey <key_id>
git config --global commit.gpgsign true
git config --global tag.gpgsign true
```

## From Pipelines
1. Now your key is created, make it available within Windows:

### GitHub Actions
1. Export the key:

```bash
gpg --output <GitHub handle>.pgp --export-secret-key <my_email_address>
```

1. Install (as administrator) [Gpg4win](https://www.gpg4win.org/) (which includes GnuPG and Kleopatra)

> **Ensure both `GnuPG` and `Kleopatra` are installed!**

1. Open Kleopatra -> `Import` -> Select the `<GitHub handle>.pgp` file created in the first step
1. In `cmd`, test the key is visible and set your local git config to use GPG signing:

```bash
gpg -k # This should list the new key
git config --global user.email <my_email_address> # same one used during key generation
git config --global user.name <github_handle>
git config --global user.signingkey <key_id>
git config --global commit.gpgsign true
git config --global tag.gpgsign true
```

1. Now make it available within WSL:

1. Within Ubuntu:

```bash
sudo ln -s /mnt/c/Program\ Files\ \(x86\)/GnuPG/bin/gpg.exe /usr/local/bin/gpg
sudo ln -s gpg /usr/local/bin/gpg2
```

1. Close and reopen your Ubuntu terminal

1. Test the key is visible and set your local git config to use GPG signing:

```bash
gpg -k # This should list the new key
git config --global user.email <my_email_address> # same one used during key generation
git config --global user.name <github_handle>
git config --global user.signingkey <key_id>
git config --global commit.gpgsign true
git config --global tag.gpgsign true
```

1. Test it works:

1. Create a temporary branch of your favourite repository.
1. Make an inconsequential whitespace change.
1. Commit the change.
1. You will be prompted for your GPG key passphrase.
1. Check the latest commit shows a successful signing:

```bash
$ git log --show-signature -1
...
gpg: Good signature from "<github_handle> <<my_email_address>>" [ultimate]
...
```

### From Pipelines

#### GitHub Actions

A GitHub Actions workflow will by default authenticate using a [GITHUB_TOKEN](https://docs.github.com/en/actions/security-guides/automatic-token-authentication) which is generated automatically.

Expand All @@ -97,7 +207,7 @@ The workflow would then use a Personal Access Token, stored with the GPG private
```yaml
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v5
with:
token: ${{ secrets.BOT_PAT }}
ref: main
Expand All @@ -121,7 +231,7 @@ git commit ${GITHUB_SIGNING_OPTION} -am "Automated commit from GitHub Actions: $
git push
```

### AWS CodePipeline
#### AWS CodePipeline

The cryptographic libraries in the default Amazon Linux 2 distro are very old, and do not support elliptic curve cryptography. When using pre-existing solution elements updating the build container is not always an option. This restricts the GPG key algorithm to RSA. You should use RSA-4096, which is the required minimum for GitHub.

Expand All @@ -138,7 +248,7 @@ if [[ ${BOT_SSH_KEY} != "None" ]]; then
echo "StrictHostKeyChecking yes" >> ~/.ssh/config
echo "UserKnownHostsFile=~/.ssh/known_hosts" >> ~/.ssh/config
echo "${BOT_SSH_KEY}" > ~/.ssh/ssh_key
echo -e "\n\n" >> ~/.ssh/ssh_key
echo -e "\n\n" >> ~/.ssh/ssh_key
chmod 600 ~/.ssh/ssh_key
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/ssh_key
Expand Down Expand Up @@ -174,10 +284,51 @@ git commit ${GITHUB_SIGNING_OPTION} -am "Automated commit from ${SCRIPT_URL}"
git push
```

## Troubleshooting
### Troubleshooting

Re-run your git command prefixed with `GIT_TRACE=1`.

A failure to sign a commit is usually because the name or email does not quite match those which were used to generate the GPG key, so git cannot auto-select a key. Ensure that these are indeed consistent. (If you added a comment when creating your GPG key, this *may* cause a mismatch: the comment will be visible when listing your GPG keys, e.g. `RealName (Comment) <EmailAddress>`.) You are able to [force a choice of signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key), though this should not be necessary.

## SSH commit signing
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to include the SSH flavour of commit signing? One downside is that SSH keys don’t support expiry, which reduces key lifecycle control. The preference would be to document a single recommended method in the framework for consistency.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd added this as it was in the other page migrated over, but I agree it's not the recommended, so have taken it out.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They don't support automated expiry, but you can expire them. There's an outstanding question as to what it is that we actually expect from signed commits - I think people might be expecting more from them than they can actually deliver, and in our context I'm not sure that SSH keys are meaningfully less good than GPG keys. They're certainly easier to configure.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See https://security.stackexchange.com/questions/14718/does-openpgp-key-expiration-add-to-security for a bit of discussion on this. The short version is that key expiry dates don't buy you very much, so if you want the practical effect of key expiry then you need to be actively revoking keys and that puts you into a manual process anyway. Plus you need to be aware that there will then be commits in the history which are signed with a now revoked key; if you aren't in control of why that key was revoked (which you're not, when someone leaves) then you can't distinguish between "expired with time before a problem happened" and "known to have been leaked in a hack, potentially used to maliciously sign historical commits".


1. If you do not already have SSH key access set up on your GitHub account, first [generate a new SSH key](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent). To create a new SSH key, you need to run the following command. This will generate a new SSH key of the type `ed25519` and associate it with your email address (replace `<my_email_address>` with your actual email address):

```shell
ssh-keygen -t ed25519 -C "<my_email_address>" -f "~/.ssh/github-signing-key"
```

> When you run this command, it will ask you to enter a passphrase. Choose a strong passphrase and make sure to remember it, as you will need to provide it when your key is loaded by the SSH agent.

1. Signing commits with an SSH key is not the default method, so you need to [configure Git](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key#telling-git-about-your-ssh-key) accordingly:

1. Run the following command to instruct Git to use the SSH signing key format, instead of the default GPG:

```shell
git config --global gpg.format ssh
```

1. Next, specify the private key for Git to use:

```shell
git config --global user.signingkey ~/.ssh/github-signing-key
```

1. Lastly, instruct Git to sign all of your commits:

```shell
git config --global commit.gpgsign true
```

1. [Add the SSH public key to your GitHub account](https://github.com/settings/ssh/new) (`Settings` -> `SSH and GPG keys` -> `New SSH key`)

1. `Key type` = `Signing Key`
1. Copy the contents of your public key file and paste it into the `Key` field.

Re-run your git command prefixed with GIT_TRACE=1
```shell
cat ~/.ssh/github-signing-key.pub
```

A failure to sign a commit is usually because the name or email does not quite match those which were used to generate the GPG key, so git cannot auto-select a key. Ensure that these are indeed consistent. (If you added a comment when creating your gpg key, this *may* cause a mismatch: the comment will be visible when listing your gpg keys, e.g. `RealName (Comment) <EmailAddress>`.) You are able to [force a choice of signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key), though this should not be necessary.
1. `Add SSH key`

If you have already committed and need to retrospectively sign this commit [please follow the instructions here](./retrospective-commit-signing.md).
1. To ensure your configuration works as expected, make a commit to a branch locally and push it to GitHub. When you view the commit history of the branch on GitHub, [your latest commit](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#about-commit-signature-verification) should now display a `Verified` tag, which indicates successful signing with your GPG or SSH key.