Sign commits with GPG keys

Learn to sign commits with GPG keys in this Secure development tutorial. Step-by-step instructions, troubleshooting tips, and next steps.

Focus: sign commits with gpg keys

Sponsored

You've mastered writing clean, tested code, but can anyone prove that commit was actually written by you? In open source and high-stakes production repos, a commit message claiming your name is just a string — easily forged with a simple git config change. Signing your commits with GPG keys closes that gap by cryptographically tying every commit to a key only you control, giving your team cryptographic evidence of authorship and protecting your supply chain from impersonation attacks.

The problem this lesson solves

Collaborative Git repositories operate on trust, but default Git commits are not authenticated. Anyone who can push to a repository can author a commit with any name and email — think of a PR from linus@linux.org that was never written by Linus. Attackers exploit this in supply chain attacks: they inject malicious code into a repo, then forge the author identity to make the change look legitimate. A compromised laptop can also be used to push commits under a teammate's identity, since no cryptographic proof of authorship exists.

Commit signing solves this by attaching a digital signature to each commit or tag. The signature is generated with your private GPG key and verified by anyone with your public key. If the commit content changes or the key doesn't match, verification fails — immediately alerting you to tampering or impersonation. It's the difference between a signed ID card and someone merely claiming your name to a guard.

Key points covered in this lesson:

  • Understand how GPG commit signing works and why it matters for secure development.
  • Apply it in a hands-on exercise — generate a key, configure Git, and verify a signed commit.
  • Connect it to what's next in your secure development track, like signing tags and integrating CI.

Why now? As your projects grow, the blast radius of a forged commit multiplies. Signing every commit early is cheap insurance that scales with your team.

Core concept / mental model

Think of a GPG key pair as a cryptographic passport:

  • Private key (kept secret on your machine) — used to stamp your commits with your digital signature.
  • Public key (shared with the world) — used by anyone to verify that a commit's signature came from your private key.

When you sign a commit, Git creates a hash of the commit contents (tree, parent, author, message, etc.) and your GPG software encrypts that hash with your private key. This signature is stored in the commit object. To verify, Git uses your public key to decrypt the signature and compares it against a freshly computed hash — if they match, the commit is authentic and unmodified.

A useful analogy: signing a commit is like wax-sealing a letter. The seal (signature) is unique to your ring (private key), and anyone can inspect the seal (using your public key) to confirm the letter wasn't opened and re-sealed by someone else.

Key definitions you'll encounter

  • GPG (GNU Privacy Guard) — an open implementation of the OpenPGP standard used to create and manage keys.
  • Key pair — a mathematically linked private and public key.
  • Signature — the output of encrypting a commit hash with your private key.
  • Verification — the process of using the public key to confirm the signature's validity.
  • Subkey — optional additional key bound to your primary key, often used for signing to improve security hygiene.

What a signed commit looks like

When you view a signed commit with git log --show-signature, you'll see a block like:

gpg: Signature made Tue Oct  1 12:00:00 2024 MST
Requires: RSA 4096
Primary key fingerprint: 1234 ABCD 1234 ABCD 1234  ABCD 1234 ABCD 1234 ABCD

If the signature is valid, Git displays gpg: Good signature; otherwise, Bad signature or Can't check signature is shown.

How it works step by step

The process of signing commits flows through three main stages:

  1. Generate a GPG key pair — one-time setup on your local machine.
  2. Configure Git to use that key — tell Git which key to sign with and that signing is enabled.
  3. Share your public key — provide it to platforms like GitHub/GitLab and your collaborators so they can verify your commits.

Stage 1: Generate your GPG key

Use gpg --full-generate-key and follow the prompts. Here's what each choice means:

  • Algorithm: RSA and RSA is the default — choose 4096 bits for long-term security.
  • Expiration: Set one (e.g., 2 years) to force periodic key rotation; you can extend it later.
  • Name and email: Must exactly match the email you use in your Git commits — otherwise verification fails.

Stage 2: Configure Git

Get your key's fingerprint and configure Git to use it:

# List your keys and copy the fingerprint of the one you want to use
gpg --list-secret-keys --keyid-format=long

# Output shows something like:
# sec   rsa4096/1F3A2B4C 2024-01-01 [SC]
#       1234ABCD1234ABCD1234ABCD1234ABCD1234ABCD
#       (fingerprint)

# Configure Git
GPG_KEY_ID=1234ABCD1234ABCD1234ABCD1234ABCD1234ABCD
git config --global user.signingkey "$GPG_KEY_ID"
git config --global commit.gpgsign true

Stage 3: Signing and verifying

Make a commit as usual — Git will prompt for your GPG passphrase (if set) and attach the signature. To verify a signed commit locally:

git log --show-signature -1

And to push your public key to GitHub:

gpg --armor --export "$GPG_KEY_ID"  # Copy the output
# Paste it in GitHub Settings > SSH and GPG keys > New GPG key

Now teammates and CI can mark your commits as "Verified" on GitHub.

Hands-on walkthrough

Let's apply this in a real project. You'll generate a key, sign a commit, and verify it end-to-end.

Step 1: Generate your GPG key

Run this interactive command and follow the prompts — choose RSA (4096 bits), set a 2-year expiration, and use the email you commit with:

gpg --full-generate-key

# Example interaction:
# Please select what kind of key you want: (1) RSA and RSA
# What keysize do you want? (4096)
# Key is valid for? (2y)
# Real name: Ada Lovelace
# Email address: ada@example.com

At the end, GPG prints a long line like gpg: key 1F3A2B4C marked as ultimately trusted. The 1F3A2B4C is the short key ID — but you'll use the full fingerprint for Git config.

Step 2: Configure Git with your key

Find your fingerprint and tell Git to sign with it automatically:

# Get the full fingerprint
gpg --list-secret-keys --keyid-format=long

# Example output:
# sec   rsa4096/1234ABCD1234ABCD 2024-01-01 [SC]
#      1234ABCD1234ABCD1234ABCD1234ABCD1234ABCD

git config --global user.signingkey 1234ABCD1234ABCD1234ABCD1234ABCD1234ABCD
git config --global commit.gpgsign true

Step 3: Create and sign a commit

In an existing repo, make a change and commit — notice Git automatically signs it:

echo "# Signed commit demo" > README.md
git add README.md
git commit -m "Add README"
# Output includes: [main 1a2b3c4] Add README
# 1 file changed, 1 insertion(+)  
# gpg: signing failed: Inappropriate ioctl for device  (if terminal issue — see troubleshooting)

If you get the ioctl error, run export GPG_TTY=$(tty) or install pinentry (see troubleshooting). Otherwise the commit is created with your signature.

Step 4: Verify the signature

Check that your commit is properly signed:

git log --show-signature -1

# Expected output (abbreviated):
# commit 1a2b3c4...
# Good signature from "Ada Lovelace <ada@example.com>" [ultimate]
#    Author: Ada Lovelace <ada@example.com>
#    Date:   Sun Jan 1 12:00:00 2024 -0700
#
#    Add README

The Good signature line confirms the commit is authentic. You can also see the verified badge on GitHub after pushing and adding your public key.

Step 5: Export your public key for GitHub

Share your public key so others can verify your signed commits:

gpg --armor --export 1234ABCD1234ABCD1234ABCD1234ABCD1234ABCD > my-pubkey.asc
cat my-pubkey.asc

Copy the block between -----BEGIN PGP PUBLIC KEY BLOCK----- and -----END PGP PUBLIC KEY BLOCK----- into your GitHub settings.

Now push a signed commit and see the Verified badge on the commit page!

Compare options / when to choose what

GPG isn't the only way to sign Git commits — but it's the most universal. Here's how it stacks up against alternatives:

Method Setup complexity Trust model Best for
GPG (OpenPGP) Medium — key management, passphrase Decentralized — you manage your private key Most projects; required by many workflows; works with GitHub/GitLab/Gitea
SSH signing Low — reuses existing SSH key Decentralized — but: not as widely recognized, no web-of-trust Teams already heavily use SSH; local/private repos where GPG is overkill
S/MIME (X.509) High — requires certificate authority Centralized — CAs issue and revoke certs Enterprise environments with existing PKI

When to choose what:

  • GPG is the default choice for public repos, open source, and compliance — platforms natively recognize it and display verified badges.
  • SSH signing is lighter if you only need integrity within your team and already manage SSH keys.
  • S/MIME is rare in Git — use only if your org mandates certificate-based signatures.

Wrapper tools like git-secret or GitHub's secret scanning don't replace signing but complement it by protecting your keys (e.g., pre-commit hooks that prevent committing private keys).

Troubleshooting & edge cases

Even with best practices, things go wrong. Here are the most common issues and their fixes:

Error: gpg: signing failed: Inappropriate ioctl for device

This happens when GPG can't open the terminal to prompt for your passphrase (common over SSH).

Fix: Export the TTY so GPG knows where to prompt:

export GPG_TTY=$(tty)

Add that line to your .bashrc/.zshrc to make it permanent. Alternatively, install a graphical pinentry program like pinentry-mac on macOS or pinentry-gtk on Linux.

gpg: key 1234... is not trusted

Your GPG key isn't marked as ultimately trusted because you didn't assign trust when generating it.

Fix: Edit the key's trust level:

gpg --edit-key 1234ABCD...
> trust
> 5 (ultimate trust)
> quit

The name/email in your Git config must also match the key's email — otherwise verification fails.

Commit says "Can't check signature: public key not found"

You're trying to verify a commit signed with a key you haven't imported.

Fix: Import the signer's public key:

gpg --import public-key.asc

Signed commit shows "Good signature" but GitHub says "Unverified"

Your public key isn't uploaded, or the email in the commit doesn't match any email on your GitHub account.

Fix: Add the key to GitHub and ensure the commit email is added to your account (Settings > Emails).

Edge cases to plan for

  • Expired keys: If your GPG key expires, new commits fail to sign. Renew the key with gpg --edit-key ... expire before it lapses.
  • Cross-platform passphrase prompts: Use a graphical pinentry on Windows/macOS; configure gpg-agent for headless CI.
  • CI/CD signing: If you sign in automated pipelines, never use your personal key — create a dedicated CI key with restricted permissions.

What you learned & what's next

You've just leveled up your secure development toolkit. Let's recap what you accomplished:

  • You can explain why GPG commit signing matters — it gives cryptographic proof of authorship and integrity to every commit.
  • You completed a hands-on exercise — you generated a GPG key, configured Git to sign automatically, committed with a signature, and verified the signature with git log --show-signature.
  • You know how to compare GPG with SSH and S/MIME signing, and you can troubleshoot common issues like the ioctl error and unverified badges.

Your next step in this track is to explore signing Git tags (git tag -s) — tags are often used for releases, and signing them prevents attackers from tampering with release points. You'll also learn how to use your signing key in CI/CD to verify commits and enforce policy, bringing the same cryptographic assurance to your deployment pipeline.

Remember: signing commits is not just a checkbox — it's a habit that turns a string of text into a verifiable claim of authorship. Start signing your commits today, and your future reviewers (and your supply chain) will thank you.

Practice recap

Create a new local Git repository, generate a GPG key if you don't have one, sign a few commits, and verify them with git log --show-signature. Then run git log --show-signature on a commit made without signing (e.g., temporarily disable commit.gpgsign) to see the difference in output.

Common mistakes

  • Using a different email in Git config than the one associated with your GPG key, causing verification to fail even with a valid signature.
  • Forgetting to export GPG_TTY in SSH or minimal-terminal environments, leading to the 'Inappropriate ioctl for device' error.
  • Uploading the private key instead of the public key to GitHub/GitLab — private keys must never leave your machine.
  • Setting commit.gpgsign to true globally but not having a key configured, causing every commit to fail.
  • Ignoring key expiration — if your key expires, you must renew it before you can continue signing commits.

Variations

  1. Use SSH signing (git config --global gpg.format ssh) if your team already uses SSH keys and you want a simpler setup.
  2. Use a dedicated subkey for signing, separate from your master key, to limit exposure if the signing key is compromised.
  3. Try using a hardware security key (YubiKey) to store your GPG private key for stronger protection.

Real-world use cases

  • Open source maintainers sign every release commit so contributors can verify the code hasn't been tampered with.
  • Enterprises enforce commit signing in CI to block unsigned commits from entering critical production branches.
  • Auditors use Git history signatures to prove who authored a change during compliance reviews.

Key takeaways

  • GPG signing attaches a cryptographic digital signature to each commit, making authorship verifiable.
  • You generate a key pair, configure Git with your key, and Git automatically signs commits when commit.gpgsign is enabled.
  • Verification uses your public key — share it on platforms like GitHub to get the 'Verified' badge.
  • Always keep your private key secret and back it up securely—losing it means you can no longer sign as you.
  • Troubleshoot common issues like the ioctl error and mismatched emails to avoid signing failures.
  • Consider SSH signing or hardware keys as alternatives when GPG feels heavy.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.