Verifying Commit and Tag Signatures
Signing commits and tags is only half the story — verifying those signatures is equally important. Verification confirms that a commit was genuinely authored by the key holder and that the commit object has not been tampered with since signing. This page covers how Git stores signatures, all the tools for verification, and how to enforce signature requirements in your workflow.
How Git Stores Signatures
When you sign a commit, Git embeds the signature inside the commit object itself as a gpgsig header. The signature covers the entire commit object — tree hash, parent hash, author, committer, timestamp, and message. Any change to any of these fields after signing will invalidate the signature.
Inspect the raw commit object to see the embedded signature
git cat-file -p HEAD
Raw commit object with embedded GPG signature
tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 parent 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 author Jane Smith <jane@example.com> 1673800365 -0500 committer Jane Smith <jane@example.com> 1673800365 -0500 gpgsig -----BEGIN PGP SIGNATURE----- iQEzBAABCAAdFiEEkpUkV5lUXv7FLx1XOqVcNDcVZ7IFAmPKa0UACgkQOqVcNDcV Z7I0twf/RKpI4WCb7qKzblEfOT7xQb5n+1A9... -----END PGP SIGNATURE----- feat: add user authentication module Implements JWT-based authentication with refresh tokens. Fixes #42
git verify-commit
Verify a specific commit
# Verify HEAD git verify-commit HEAD # Verify a specific commit hash git verify-commit 7d3e5f2a1b9c4d6e8f0a2b4c6d8e0f1a3b5c7d9e # Verify multiple commits git verify-commit HEAD HEAD~1 HEAD~2
Good signature output
gpg: Signature made Tue Jan 15 14:32:45 2023 EST gpg: using RSA key 3AA5C34371567BD2 gpg: Good signature from "Jane Smith <jane@example.com>" [ultimate]
Bad signature output (tampered commit)
gpg: Signature made Tue Jan 15 14:32:45 2023 EST gpg: using RSA key 3AA5C34371567BD2 gpg: BAD signature from "Jane Smith <jane@example.com>" [ultimate] error: could not verify commit '7d3e5f2a'
git verify-tag
Verify a tag signature
git verify-tag v1.0.0
Good tag signature output
object 7d3e5f2a1b9c4d6e8f0a2b4c6d8e0f1a3b5c7d9e type commit tag v1.0.0 tagger Jane Smith <jane@example.com> 1673800365 -0500 Release v1.0.0 gpg: Signature made Tue Jan 15 14:32:45 2023 EST gpg: using RSA key 3AA5C34371567BD2 gpg: Good signature from "Jane Smith <jane@example.com>" [ultimate]
git log --show-signature
Show signatures for all commits in log
# Full signature info for recent commits git log --show-signature -5 # One-line summary with signature status git log --format="%H %G? %GS" -10
Example --format output
7d3e5f2a1b9c4d6e8f0a G Jane Smith <jane@example.com> 3b1a9c8d2e4f6a7b8c9d G Jane Smith <jane@example.com> 1f2e3d4c5b6a7980abcd N 9a8b7c6d5e4f3a2b1c0d N 4d3e2f1a0b9c8d7e6f5a U Unknown Key ID
Signature Status Codes
Code | Meaning | Action |
|---|---|---|
G | Good — valid signature, key trusted | All clear |
B | Bad — signature does not match the commit object (tampered) | Investigate immediately |
U | Unknown — key not in your keyring, cannot verify | Import the signer's public key |
X | Expired — signature used an expired key | Contact signer to re-sign with current key |
Y | Expired OK — signature OK but key was already expired at time of signing | Acceptable if intentional |
R | Revoked — key was revoked after signing | Treat as suspicious; investigate |
E | Missing key — cannot check because key is unavailable | Import the public key |
N | No signature — commit was not signed at all | Expected if signing not enforced |
Scripting Signature Verification
Check all commits on current branch for valid signatures
# List any commits without a Good signature git log --format="%H %G?" main..HEAD | grep -v " G$" # Fail if any commit lacks a good signature (useful in CI) UNSIGNED=$(git log --format="%H %G?" main..HEAD | grep -v " G$" | wc -l) if [ "$UNSIGNED" -gt 0 ]; then echo "Error: Found commits without valid signatures" git log --format="%H %G? %an %s" main..HEAD | grep -v " G " exit 1 fi
Enforcing Signed Commits on GitHub
Go to Repository → Settings → Branches
Click Add branch protection rule or edit an existing rule for
mainEnable Require signed commits
With this enabled, GitHub rejects any push that includes unsigned commits
This also blocks merging PRs that contain unsigned commits
CI Verification Example
.github/workflows/verify-signatures.yml
name: Verify Commit Signatures
on: [pull_request]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Import team GPG keys
run: |
gpg --import team-keys/jane.asc
gpg --import team-keys/bob.asc
- name: Verify all commits in PR
run: |
git log --format="%H %G?" origin/main..HEAD | while read hash status; do
if [ "$status" != "G" ]; then
echo "Commit $hash has invalid or missing signature (status: $status)"
exit 1
fi
done
echo "All commits have valid signatures"