Contents

Signing Git Commits & Tags with GPG

Using GPG or S/MIME, you can sign tags or commits locally. Those tags/commits are then marked as trusted on GitHub, so others can be confident the changes came from a trusted source.

Copyright notice: This is an original article by xwi88, licensed under CC BY-NC 4.0. Commercial use is prohibited; please cite the source when reposting. Follow at https://github.com/xwi88

Enable vigilant mode

With vigilant mode on, your own unsigned commits are flagged Unverified. This alerts you and others to potential authenticity issues. The author and committer of a Git commit are easily spoofed — someone could push a commit that claims to be yours but isn’t.

Path: Settings->SSH and GPG keys>Vigilant mode->select Flag unsigned commits as unverified

GPG Command

man gpg

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
gpg (GnuPG) 2.2.34
libgcrypt 1.9.4
Copyright (C) 2022 g10 Code GmbH
License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: ~/.gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2

Syntax: gpg [options] [files]
Sign, check, encrypt or decrypt
Default operation depends on the input data

Commands:

 -s, --sign                         make a signature
     --clear-sign                   make a clear text signature
 -b, --detach-sign                  make a detached signature
 -e, --encrypt                      encrypt data
 -c, --symmetric                    encryption only with symmetric cipher
 -d, --decrypt                      decrypt data (default)
     --verify                       verify a signature
 -k, --list-keys                    list keys
     --list-signatures              list keys and signatures
     --check-signatures             list and check key signatures
     --fingerprint                  list keys and fingerprints
 -K, --list-secret-keys             list secret keys
     --generate-key                 generate a new key pair
     --quick-generate-key           quickly generate a new key pair
     --quick-add-uid                quickly add a new user-id
     --quick-revoke-uid             quickly revoke a user-id
     --quick-set-expire             quickly set a new expiration date
     --full-generate-key            full featured key pair generation
     --generate-revocation          generate a revocation certificate
     --delete-keys                  remove keys from the public keyring
     --delete-secret-keys           remove keys from the secret keyring
     --quick-sign-key               quickly sign a key
     --quick-lsign-key              quickly sign a key locally
     --quick-revoke-sig             quickly revoke a key signature
     --sign-key                     sign a key
     --lsign-key                    sign a key locally
     --edit-key                     sign or edit a key
     --change-passphrase            change a passphrase
     --export                       export keys
     --send-keys                    export keys to a keyserver
     --receive-keys                 import keys from a keyserver
     --search-keys                  search for keys on a keyserver
     --refresh-keys                 update all keys from a keyserver
     --import                       import/merge keys
     --card-status                  print the card status
     --edit-card                    change data on a card
     --change-pin                   change a card's PIN
     --update-trustdb               update the trust database
     --print-md                     print message digests
     --server                       run in server mode
     --tofu-policy VALUE            set the TOFU policy for a key

Options controlling the diagnostic output:
 -v, --verbose                      verbose
 -q, --quiet                        be somewhat more quiet
     --options FILE                 read options from FILE
     --log-file FILE                write server mode logs to FILE

Options controlling the configuration:
     --default-key NAME             use NAME as default secret key
     --encrypt-to NAME              encrypt to user ID NAME as well
     --group SPEC                   set up email aliases
     --openpgp                      use strict OpenPGP behavior
 -n, --dry-run                      do not make any changes
 -i, --interactive                  prompt before overwriting

Options controlling the output:
 -a, --armor                        create ascii armored output
 -o, --output FILE                  write output to FILE
     --textmode                     use canonical text mode
 -z N                               set compress level to N (0 disables)

Options controlling key import and export:
     --auto-key-locate MECHANISMS   use MECHANISMS to locate keys by mail address
     --disable-dirmngr              disable all access to the dirmngr

Options to specify keys:
 -r, --recipient USER-ID            encrypt for USER-ID
 -u, --local-user USER-ID           use USER-ID to sign or decrypt

(See the man page for a complete listing of all commands and options)

Examples:

 -se -r Bob [file]          sign and encrypt for user Bob
 --clear-sign [file]        make a clear text signature
 --detach-sign [file]       make a detached signature
 --list-keys [names]        show keys
 --fingerprint [names]      show fingerprints

Please report bugs to <https://bugs.gnupg.org>.

gpg shell command

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
gpg> help
quit        quit this menu
save        save and quit
help        show this help
fpr         show key fingerprint
grip        show the keygrip
list        list key and user IDs
uid         select user ID N
key         select subkey N
check       check signatures
sign        sign selected user IDs [* see below for related commands]
lsign       sign selected user IDs locally
tsign       sign selected user IDs with a trust signature
nrsign      sign selected user IDs with a non-revocable signature
deluid      delete selected user IDs
delkey      delete selected subkeys
delsig      delete signatures from the selected user IDs
pref        list preferences (expert)
showpref    list preferences (verbose)
trust       change the ownertrust
revsig      revoke signatures on the selected user IDs
enable      enable key
disable     disable key
showphoto   show selected photo IDs
clean       compact unusable user IDs and remove unusable signatures from key
minimize    compact unusable user IDs and remove all signatures from key

* The 'sign' command may be prefixed with an 'l' for local signatures (lsign),
  a 't' for trust signatures (tsign), an 'nr' for non-revocable signatures
  (nrsign), or any combination thereof (ltsign, tnrsign, etc.).

GPG key generation & config

Note
GPG does not come installed by default on macOS or Windows. To install GPG command line tools, see GnuPG’s Download page.

Existing GPG keys

gpg --list-secret-keys --keyid-format=long

Example
1
2
3
4
5
~/.gnupg/pubring.kbx
-----------------------------------
sec   rsa4096/325ACD1FD3B6AA80 2022-03-07 [SC] [expires: 2024-03-06]
      1F11E9A019E23C53C11C8D4C325ACD1FD3B6AA80
uid                 [ultimate] xwi88 <278810732@qq.com>
  • the GPG key ID: 325ACD1FD3B6AA80

Generate a new GPG key

If an old GPG key already exists, you don’t need to generate a new one!

gpg --default-new-key-algo rsa4096 --gen-key

Tip

Algorithms github supports for generating gpg keys

  • RSA
  • ElGamal
  • DSA
  • ECDH
  • ECDSA
  • EdDSA
Example

gpg --default-new-key-algo rsa4096 --gen-key

  • Real name: tmp_gpg
  • Email address: 278810732@qq.com

You selected this USER-ID: “tmp_gpg 278810732@qq.com

  • Change (N)ame, (E)mail, or (O)kay/(Q)uit? O

We need to generate a lot of random bytes. It is a good idea to perform some other action (type on the keyboard, move the mouse, utilize the disks) during the prime generation; this gives the random number generator a better chance to gain enough entropy.

Note that this key cannot be used for encryption. You may want to use the command “–edit-key” to generate a subkey for this purpose.

1
2
3
pub   rsa4096 2022-03-09 [SC] [expires: 2024-03-08]
      F09FC9FB34FA457ED2F7090AFE47519758053257
uid                      tmp_gpg <278810732@qq.com>

Latest GPG keys

gpg --list-secret-keys --keyid-format=long

1
2
3
4
5
6
7
8
-----------------------------------
sec   rsa4096/325ACD1FD3B6AA80 2022-03-07 [SC] [expires: 2024-03-06]
      1F11E9A019E23C53C11C8D4C325ACD1FD3B6AA80
uid                 [ultimate] xwi88 <278810732@qq.com>

sec   rsa4096/FE47519758053257 2022-03-09 [SC] [expires: 2024-03-08]
      F09FC9FB34FA457ED2F7090AFE47519758053257
uid                 [ultimate] tmp_gpg <278810732@qq.com>

Export a GPG key

  • gpg --armor --export FE47519758053257
  • or gpg -a -o --export FE47519758053257
1
2
3
4
5
-----BEGIN PGP PUBLIC KEY BLOCK-----

xxxxxxxxxxxxxxx

-----END PGP PUBLIC KEY BLOCK-----

Export a gpg public key to a specific destination:

  • gpg --export --armor [uid] > gpgkey.pub.asc export to file uid: keyID/name/email
  • gpg --keyserver [keyserverAddress] --send-keys keyIDs export to a specific key server
  • gpg --send-keys keyIDs export to the default key server

Output when exporting to the default key server

1
output: gpg: sending key 90684042688CB9BE to hkps://keyserver.ubuntu.com

Find a GPG key

keyIDs may be: name, keyID, email

  • gpg --keyserver keyserverAddress --search-keys keyIDs
  • gpg --search-keys keyIDs search the default key server
Example

gpg --search-keys xwi88

1
2
3
4
gpg: data source: https://162.213.33.8:443
(1)	xwi88 <278810732@qq.com>
	  4096 bit RSA key 90684042688CB9BE, created: 2022-03-09
Keys 1-1 of 1 for "xwi88".  Enter number(s), N)ext, or Q)uit > q

gpg --search-keys 90684042688CB9BE

1
2
3
4
gpg: data source: https://162.213.33.8:443
(1)	xwi88 <278810732@qq.com>
	  4096 bit RSA key 90684042688CB9BE, created: 2022-03-09
Keys 1-1 of 1 for "90684042688CB9BE".  Enter number(s), N)ext, or Q)uit > q

gpg --search-keys 278810732@qq.com

1
2
3
4
gpg: data source: https://162.213.33.8:443
(1)	xwi88 <278810732@qq.com>
	  4096 bit RSA key 90684042688CB9BE, created: 2022-03-09
Keys 1-1 of 1 for "278810732@qq.com".  Enter number(s), N)ext, or Q)uit > y

Import a GPG key

Key-server import — without an address it defaults to hkps://keyserver.ubuntu.com (your address may differ; this is local output)

  • gpg --import [GPG public key] import from file
  • gpg --keyserver [keyserverAddress] --recv-keys keyIDs import from key server
  • gpg --recv-keys keyIDs import from the default key server
  • gpg --refresh-keys update all keys from the key server
Example

gpg --recv-keys 90684042688CB9BE

1
2
3
gpg: key 90684042688CB9BE: "xwi88 <278810732@qq.com>" not changed
gpg: Total number processed: 1
gpg:              unchanged: 1

Secret-key backup, export & import

Mainly for: sharing across your own machines

Warning
  • Never upload a secret key to a key server
  • Back up the secret-key file securely
  • Secret-key file permission: 600
  • Public-key file permission: 644

Export a secret key

gpg -a -o test_secKey.asc --export-secret-keys keyID

Example

gpg -K view local secret keys

gpg -a -o test_secKey.asc --export-secret-keys 1F11E9A019E23C53C11C8D4C325ACD1FD3B6AA80 export the secret key

Import a secret key

gpg --import secKeyFile

Example

gpg --import test_secKey.asc import a secret key — same operation as importing a public key

1
2
3
gpg: key 325ACD1FD3B6AA80: "xwi88 <278810732@qq.com>" not changed
gpg: Total number processed: 1
gpg:              unchanged: 1

gpg -K view local secret keys

Key revocation

After generating a new key pair, if other key pairs are no longer used, publish a revocation certificate immediately to declare the old public key invalid and prevent malicious use.

gpg --output test_revoke.asc --gen-revoke keyID

gpg --import test_revoke.asc import the revocation certificate

gpg --send-keys keyID send the revocation certificate to the server, declaring the original GPG Key revoked

Example

gpg --output test_revoke.asc --gen-revoke 325ACD1FD3B6AA80

or gpg --output test_revoke.asc --generate-revocation 325ACD1FD3B6AA80

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
sec  rsa4096/325ACD1FD3B6AA80 2022-03-07 xwi88 <278810732@qq.com>

Create a revocation certificate for this key? (y/N) y
Please select the reason for the revocation:
  0 = No reason specified
  1 = Key has been compromised
  2 = Key is superseded
  3 = Key is no longer used
  Q = Cancel
(Probably you want to select 1 here)
Your decision? 3
Enter an optional description; end it with an empty line:
>
Reason for revocation: Key is no longer used
(No description given)
Is this okay? (y/N) y
ASCII armored output forced.
Revocation certificate created.

Please move it to a medium which you can hide away; if Mallory gets
access to this certificate he can use it to make your key unusable.
It is smart to print this certificate and store it away, just in case
your media become unreadable.  But have some caution:  The print system of
your machine might store the data and make it available to others!

gpg --import test_revoke.asc

1
2
3
4
5
6
gpg: key 325ACD1FD3B6AA80: "xwi88 <278810732@qq.com>" revocation certificate imported
gpg: Total number processed: 1
gpg:    new key revocations: 1
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   3  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 3u
gpg: next trustdb check due at 2024-03-06

gpg --send-keys 325ACD1FD3B6AA80

gpg --search-keys 325ACD1FD3B6AA80

1
2
3
4

gpg: data source: https://162.213.33.8:443
gpg: key "325ACD1FD3B6AA80" not found on keyserver
gpg: keyserver search failed: Not found

Delete keys

  • gpg --delete-secret-keys keyID delete this first
  • gpg --delete-keys keyID

Restart the GPG Agent

GPG restarts it on demand

gpgconf --kill gpg-agent

Configure git to use GPG

Set the GPG key

  • Copy the GPG key you want to use, beginning with —–BEGIN PGP PUBLIC KEY BLOCK—– and ending with —–END PGP PUBLIC KEY BLOCK—–

  • Paste it into github: Settings->SSH and GPG keys->GPG keys. If the key already exists, ignore; otherwise create a new entry and paste.

Configure the GPG key used for signing

Mind whether you want this global; with a global config, an individual project can still override it! Replace –global with local

1
2
3
4
5
# global config
git config --global user.signingkey 325ACD1FD3B6AA80

# configure your Git client to sign commits by default for a local repository, in Git versions 2.0.0 and above
git config --global commit.gpgsign true

Create a signed commit

git commit -S -m "your commit message"

If you’ve set commits to be signed by default, you can also just:

git commit -m "your commit message"

Warning

If your GPG key uses a GPG key passphrase, you’ll need to enter your passphrase when committing.

You can store the GPG key passphrase to avoid entering it on every sign:

  • Mac users, GPG Suite can store your GPG key passphrase in Mac OS Keychain
  • Windows users, Gpg4win
  • Manually configure gpg-agent to store it

Sign a tag

1
2
3
4
5
# sign a tag
git tag -s my_tag

# verify your signed tag
git tag -v my_tag

View commit signatures

git log --show-signature

Tip

Format the log to show signatures — define your own log view:

git log --color --graph --pretty=format:'%C(cyan)%G?%Creset %Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset | [%GK trust:%GT] %C(yellow)%GS%Creset' --abbrev-commit

1
2
3
4
5
6
7
* G fb56816 - fixed rsync dir (2 days ago) <xwi88> | [325ACD1FD3B6AA80 trust:ultimate] xwi88 <278810732@qq.com>
* G 63d6ec2 - fixed rsync deploy (2 days ago) <xwi88> | [325ACD1FD3B6AA80 trust:ultimate] xwi88 <278810732@qq.com>
* N 1ac5368 - workflow add remote deploy (2 days ago) <xwi88> | [ trust:undefined]
* E 289ae51 - add domain ICP info (2 days ago) <xwi88> | [EEA29F407613E698 trust:]
* E f0e44bd - switch comment store repo:x (2 days ago) <xwi88> | [EEA29F407613E698 trust:]
* E fbd5778 - fixed giscus issue (3 days ago) <xwi88> | [EEA29F407613E698 trust:]
* E 5fa8d41 - replace utterances by giscus (3 days ago) <xwi88> | [EEA29F407613E698 trust:]
  • G: for a good (valid) signature
  • B: for a bad signature
  • U: for a good signature with unknown validity
  • X: for a good signature that has expired
  • Y: for a good signature made by an expired key
  • R: for a good signature made by a revoked key
  • E: if the signature cannot be checked (e.g. missing key)
  • N: for no signature

Local signature-verification issues

When viewing commit signatures via git log --show-signature, you may see:

1
2
3
4
commit e39e0920b68648b0751b7f91fffdd07080391945 (HEAD -> main, origin/main, origin/HEAD)
gpg: Signature made Thu Mar 10 00:19:37 2022 CST
gpg:                using RSA key 7E7F28C4EFFD7721E0133ED490684042688CB9BE
gpg: Can't check signature: No public key

You can also verify a specific commit’s signature with git verify-commit e39e0920b68648b0751b7f91fffdd07080391945

This happens because your local repo doesn’t have the gpg public key used for those commits. Fix it by importing the committer’s gpg public keys.

Importing missing gpg keys & verifying

To get full signature verification, we import the missing public keys. Suggested import sources:

  • https://github.com/<username>.gpg requires you to have uploaded GPG keys to github
  • hkps://keyserver.ubuntu.com requires you to have uploaded keys

Certify the imported public key

  • gpg --list-keys --keyid-format=long find the key you want to certify
  • gpg --lsign-key [GPG key ID] local sign
  • gpg --edit-key [GPG key ID] local sign — pick one of this or the above
Example

Import the missing public key from github gpg --keyserver https://github.com/xwi88.gpg --recv-keys 7E7F28C4EFFD7721E0133ED490684042688CB9BE

Certify the key gpg --lsign-key 7E7F28C4EFFD7721E0133ED490684042688CB9BE

Check the certified key gpg --list-keys --keyid-format=long

1
2
3
pub   rsa4096/90684042688CB9BE 2022-03-09 [SC] [expires: 2024-03-08]
      7E7F28C4EFFD7721E0133ED490684042688CB9BE
uid                 [  full  ] xwi88 <278810732@qq.com>

Verify our commit again git verify-commit e39e0920b68648b0751b7f91fffdd07080391945

1
2
3
gpg: Signature made Thu Mar 10 00:19:37 2022 CST
gpg:                using RSA key 7E7F28C4EFFD7721E0133ED490684042688CB9BE
gpg: Good signature from "xwi88 <278810732@qq.com>" [full]

Importing Github’s GPG public key

github public GPG key for web-flow

  1. curl https://github.com/web-flow.gpg | gpg --import import github public gpg key
  2. gpg --edit-key noreply@github.com trust quit trust and save, you choose: 4
  3. gpg --lsign-key noreply@github.com sign selected user IDs locally

GPG key update & renewal

gpg --edit-key [GPG key ID]

Example
1
2
3
sec   rsa4096 2022-03-09 [SC] [expires: 2024-03-08]
      CE70FE5A7EB462DDA68EE86913431F2AC47C4AE0
uid           [ultimate] tmp_gpg_local <278810732@qq.com>

This GPG key is regenerated, for demo purposes only

gpg --edit-key CE70FE5A7EB462DDA68EE86913431F2AC47C4AE0

Key output:

1
2
3
4
sec  rsa4096/73758EF02856F877
     created: 2022-03-09  expires: 2024-03-08  usage: SC
     trust: ultimate      validity: ultimate
[ultimate] (1). tmp_gpg_local <278810732@qq.com>

Enter expire to update the expiry

gpg> expire

1
2
3
4
5
6
7
Changing expiration time for the primary key.
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) 180d

Key expires at Mon Sep 5 21:48:04 2022 CST

Is this correct? (y/N) y

1
2
3
4
sec  rsa4096/13431F2AC47C4AE0
     created: 2022-03-09  expires: 2022-09-05  usage: SC
     trust: ultimate      validity: ultimate
[ultimate] (1). tmp_gpg_local <278810732@qq.com>

gpg> trust

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
sec  rsa4096/13431F2AC47C4AE0
     created: 2022-03-09  expires: 2022-09-05  usage: SC
     trust: ultimate      validity: ultimate
[ultimate] (1). tmp_gpg_local <278810732@qq.com>

Please decide how far you trust this user to correctly verify other users' keys
(by looking at passports, checking fingerprints from different sources, etc.)

  1 = I don't know or won't say
  2 = I do NOT trust
  3 = I trust marginally
  4 = I trust fully
  5 = I trust ultimately
  m = back to the main menu

Your decision? 5

Do you really want to set this key to ultimate trust? (y/N) y

1
2
3
4
sec  rsa4096/13431F2AC47C4AE0
     created: 2022-03-09  expires: 2022-09-05  usage: SC
     trust: ultimate      validity: ultimate
[ultimate] (1). tmp_gpg_local <278810732@qq.com>

gpg> save

gpg --list-secret-keys --keyid-format=long verify the expiry updated

1
2
3
4
5
6
7
sec   rsa4096/90684042688CB9BE 2022-03-09 [SC] [expires: 2024-03-08]
      7E7F28C4EFFD7721E0133ED490684042688CB9BE
uid                 [ultimate] xwi88 <278810732@qq.com>

sec   rsa4096/13431F2AC47C4AE0 2022-03-09 [SC] [expires: 2022-09-05]
      CE70FE5A7EB462DDA68EE86913431F2AC47C4AE0
uid                 [ultimate] tmp_gpg_local <278810732@qq.com>

Rebinding the GPG sign key

Don’t change this unless you need to.

Warning
  • Don’t update the following unless needed

  • For a single project, you may replace –global with local

  • git config --global commit.gpgSign true
  • git config --global user.signingKey 13431F2AC47C4AE0

If anything changes, sync the GPG key in your git repo too.

Deleting GPG keys

  • gpg --delete-secret-key [uid]
  • gpg --delete-secret-key [uid1] [uid2]
Warning

Don’t delete unless necessary — if it’s just expiry, do a renewal instead.

uid for tmp_gpg may be any of the following; prefer the GPG key ID:

  • tmp_gpg
  • 13431F2AC47C4AE0
  • CE70FE5A7EB462DDA68EE86913431F2AC47C4AE0
Example

gpg --delete-secret-key 13431F2AC47C4AE0

1
sec  rsa4096/13431F2AC47C4AE0 2022-03-09 tmp_gpg_local <278810732@qq.com>

Delete this key from the keyring? (y/N) y

This is a secret key! - really delete? (y/N) y

Verify deletion: gpg --list-secret-keys

1
2
3
sec   rsa4096 2022-03-09 [SC] [expires: 2024-03-08]
      7E7F28C4EFFD7721E0133ED490684042688CB9BE
uid           [ultimate] xwi88 <278810732@qq.com>

References