0%

如何给Git Commit GPG签名?

这是最终效果

起因

其实在很早之前 Github 就已经充分支持 GPG 密钥了,而在我之前使用 Github 的两年时间内,竟对此一无所知,实在有些“没见过世面”。直至近日,在一次偶然查看仓库的commit历史中,发现某些commit有一个不同寻常的绿色标记(Verified),不仅美观而且看上去舒心,如图所示:

点击这个标记,得知这一次commit是经过签名验证的(signed with a verified signature),因此,我便开始研究如何利用GPG对自己的每次commit进行签名验证。

什么是GPG

GnuPG is a complete and free implementation of the OpenPGP standard as defined by RFC4880 (also known as PGP). GnuPG allows you to encrypt and sign your data and communications; it features a versatile key management system, along with access modules for all kinds of public key directories. GnuPG, also known as GPG, is a command line tool with features for easy integration with other applications. A wealth of frontend applications and libraries are available. GnuPG also provides support for S/MIME and Secure Shell (ssh).

Since its introduction in 1997, GnuPG is Free Software (meaning that it respects your freedom). It can be freely used, modified and distributed under the terms of the GNU General Public License .

The current version of GnuPG is 2.4.5. See the download page for other maintained versions.

Gpg4win is a Windows version of GnuPG featuring a context menu tool, a crypto manager, and an Outlook plugin to send and receive standard PGP/MIME mails. The current version of Gpg4win is 4.3.1.

以上是从GPG网站上摘取的部分简介,总的来说,GPG的功能十分丰富,然而我这次主要是用它来对Git中的commit进行签名验证,所以需要做的事情也不算太复杂:

  1. 生成自己的GPG密钥
  2. 关联GPG公钥与Github账户
  3. 设置利用GPG私钥对commit进行签名
  4. 可选步骤:信任Github的GPG密钥

过程

安装GPG

由于我的目的是在Git中使用GPG,而Windows版本的Git发行包中,已经包含了可用的GPG命令行。判断方法也很简单,打开Git Bash,输入gpg --version,可以看到类似的GPG版本信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ gpg --version
gpg (GnuPG) 2.4.4-unknown
libgcrypt 1.9.4-unknown
Copyright (C) 2024 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: /c/Users/fjqz177/.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

生成自己的GPG密钥

打开Git Bash,运行gpg --full-generate-key,根据提示,输入相应的个人信息(需要注意的是邮箱必须要使用在Github中验证过的邮箱)、自定义密钥参数、设置私钥密码等等,即可生成自己的GPG密钥。(补充说明,使用gpg --gen-key亦可生成密钥,但是会略去自定义密钥参数的步骤,对于一般场合的使用问题不大。)

输出结果的末尾大致如下:

1
2
3
4
pub   ed25519 2024-04-08 [SC] [expires: 2027-04-08]
3679AE2AC28CD97D026F5D64BE77A57CFB552D88
uid test <test@test.com>
sub cv25519 2024-04-08 [E] [expires: 2027-04-08]

需要记下的,是上述输出信息中的密钥ID:
3679AE2AC28CD97D026F5D64BE77A57CFB552D88 或者BE77A57CFB552D88,后者是前者的简短形式,是密钥ID的倒数16个字符。

当然,如果没有及时将其记下也不要紧,可以运行gpg --list-keys,列出本地存储的所有GPG密钥信息,大致如下:

1
2
3
4
5
6
$ gpg --list-keys
# 省略一大部分
pub ed25519 2024-04-08 [SC] [expires: 2027-04-08]
3679AE2AC28CD97D026F5D64BE77A57CFB552D88
uid [ultimate] test <test@test.com>
sub cv25519 2024-04-08 [E] [expires: 2027-04-08]

稍微解读一下这些结果:

  • pub其后的是该密钥的公钥特征
    1. Ed25519EdDSA签名方案,但使用SHA-512 / 256Curve25519;它是一条安全的椭圆形曲线,比DSAECDSAEdDSA提供更好的安全性,并且具有更好的性能。
    1. 密钥创建时间
    1. 用途是SigningCertificating
    1. 过期时间(gpg --gen-key默认3年之后过期)
    1. 密钥的ID。
  • uid其后的是生成密钥时所输入的个人信息。
  • sub其后的则是该密钥的子密钥特征,格式和公钥部分大致相同(E表示用途是Encrypting)。

关联GPG公钥与Github账户

还记得在上一步中记下的密钥ID吗?现在,我们需要根据这个ID来导出对应GPG密钥的公钥字符串。继续在Git Bash中,运行命令gpg --armor --export {key_id}:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ gpg --armor --export 3679AE2AC28CD97D026F5D64BE77A57CFB552D88
-----BEGIN PGP PUBLIC KEY BLOCK-----

mDMEZhQBlBYJKwYBBAHaRw8BAQdA1OYDdzmuH7SBWxI7HOqsxn6NxSAWDu7N6h4o
c2hM5Jq0FHRlc3QgPHRlc3RAdGVzdC5jb20+iJkEExYKAEEWIQQ2ea4qwozZfQJv
XWS+d6V8+1UtiAUCZhQBlAIbAwUJBaOagAULCQgHAgIiAgYVCgkICwIEFgIDAQIe
BwIXgAAKCRC+d6V8+1UtiKpwAP91C5ymjro8IGLSTlmVi/ujI17CDIlE9O4oPT4q
NVeAOQD7BZOP7wGYgmhXyWR1GOAxoSyKXCEwUp0nbCPwT4o0vgm4OARmFAGUEgor
BgEEAZdVAQUBAQdAJwvKNZFa+jEo1MZHTolvrrVjBpf9TU7O4YrkQcfmhAUDAQgH
iH4EGBYKACYWIQQ2ea4qwozZfQJvXWS+d6V8+1UtiAUCZhQBlAIbDAUJBaOagAAK
CRC+d6V8+1UtiNeHAQCOtH6wTDCcBEE4Dfehf2OdPNVZDo3HRmuK5IfZlbtXuwD+
Ljm1lcAF3re1vs7GRn7UA2UKMVpnWuC0WnWFVXQNtwA=
=KGi3
-----END PGP PUBLIC KEY BLOCK-----

然后,在Github的SSH and GPG keys中,新增一个GPG key,内容从-----BEGIN PGP PUBLIC KEY BLOCK-----开始到-----END PGP PUBLIC KEY BLOCK-----结尾,要完整地粘贴上去。

再次提醒,GPG密钥中个人信息的邮箱部分,必须使用在Github中验证过的邮箱,否则添加GPG key会提示未经验证。

利用GPG私钥对Git commit进行签名

首先,需要让Git知道签名所用的GPG密钥ID:

1
git config --global user.signingkey {key_id}

然后,在每次commit的时候,加上-S参数,表示这次提交需要用GPG密钥进行签名:

1
git commit -S -m "..."

如果觉得每次都需要手动加上-S有些麻烦,可以设置Git为每次commit自动要求签名:

1
git config --global commit.gpgsign true

但不论是否需要手动加上-S,commit时皆会弹出对话框,需要输入该密钥的密码,以确保是密钥拥有者本人操作,如图所示:

输入正确密码后,本次commit便被签名验证,push到Github远程仓库后,即可显示出Verified绿色标记,点击Verified绿色标记,即可查看相关信息(由于test <test@test.com>密钥的邮箱未经验证,所以此处实际用的是我本人的密钥进行签名):

可选步骤:信任Github的GPG密钥

事实上,在完成上述步骤后,已经可以基本完全正常地同时使用Github和GPG了,那为什么还需要这一步骤呢?很简单,不妨用git log --show-signature试试查看本地的某个Git仓库的commit记录和签名信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ git log --show-signature
# 省略一大部分
commit ec37d4af120a69dafa077052cfdf4f5e33fa1ef3 (HEAD -> master)
gpg: Signature made 2024年04月 4日 12:52:29
gpg: using RSA key 1BA074F113915706D141348CDC3DB5873563E6B2
gpg: Good signature from "fjqz177 <fjqz177@139.com>" [ultimate]
Author: fjqz177 <fjqz177@139.com>
Date: Sun April 7 12:52:29 2024 +0800

test GPG

commit 6937d638d950362f73bfbf28bc4a39d1700bf26b
gpg: Signature made 2024年04月 4日 11:58:46
gpg: using RSA key B5690EEEBB952194
gpg: Can't check signature: No public key
Author: fjqz177 <20233656+fjqz177@users.noreply.github.com>
Date: Sun April 7 11:58:46 2024 +0800

Initial commit

可以发现,虽然所有的commit在Github中查看都是Verified,但是有一些比较特殊:在Github网页端进行的操作,比如创建仓库。这些commit并没有用我们之前生成的密钥进行签名,而是由Github代为签名了。这样的结果就是,我们本地无法确认这些签名的真实性。

为了解决这个问题,我们需要导入并信任Github所用的GPG密钥

由于我这里已经导入过了,所以显示的内容可能会不一样。

先是导入:

1
2
3
4
5
6
$ curl https://github.com/web-flow.gpg | gpg --import
# 省略一大部分
gpg: key 4AEE18F83AFDEB23: "GitHub (web-flow commit signing) <noreply@github.com>" not changed
gpg: key B5690EEEBB952194: "GitHub <noreply@github.com>" not changed
gpg: Total number processed: 2
gpg: unchanged: 2

由于GitHub (web-flow commit signing) <noreply@github.com>密钥已经过期,所以我们只需要给GitHub <noreply@github.com>进行签名即可

然后是信任(用自己的密钥为其签名验证,需要输入密码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ gpg --sign-key B5690EEEBB952194

pub rsa4096/B5690EEEBB952194
created: 2024-01-16 expires: never usage: SC
trust: unknown validity: full
[ full ] (1). GitHub <noreply@github.com>


pub rsa4096/B5690EEEBB952194
created: 2024-01-16 expires: never usage: SC
trust: unknown validity: full
Primary key fingerprint: 9684 79A1 AFF9 27E3 7D1A 566B B569 0EEE BB95 2194

GitHub <noreply@github.com>

Are you sure that you want to sign this key with your
key "fjqz177 <fjqz177@139.com>" (BE77A57CFB552D88)

Really sign? (y/N) y

至此,再用git log --show-signature查看本地仓库的commit签名信息,则会发现所有的commit签名都已得到验证:

结束

经过这一番操作,Github和GPG圆满结合在了一起,而我也得到了我想要的Verified标记。不过,GPG的功能远非止于此,它还可以用来对文件、邮件等进行加密,还可以进行身份验证等等,都有待我去学习研究。