前言

Github设置有一项叫“SSH 与 GPG 公钥”,平常我们看一些教程只需要使用SSH 公钥,好像用不到GPG 公钥,直到我看到这篇有趣的帖子——👨‍💻 震惊!竟然有人在 GitHub 上冒充我的身份!

Git 的 commit 是可以任意修改的,你可以将某个坏 commit 嫁祸给别人,甚至将某个坏仓库的 commit 批量嫁祸给毫不知情的人。
使用一个只有我们自己手中拥有的 GPG 私钥对我们的 commit 进行签名,可以让 GitHub 确认我们本次 commit 是真实且是本人操作的。这样,别有用心的他人就无法以我们的身份创建「被签名」的 commit。在 GitHub 上使用的 GPG 密钥和我们的 SSH 密钥并不一样,后者 SSH key 唯一存在的原因是为了向 GitHub 证明身份,用于向我们拥有权限的仓库中进行 commit,而前者 GPG key 则是为了「证明我拥有本次 commit 的著作权」,也只有用 GPG 私钥签名的 commit 在 GitHub 上才会显示如下图的 Verified 绿色钦定小标标。

今天我们就来了解一下GPG是什么,以及怎么用。

什么是GPG

1991年,菲利普·齐墨尔曼(Philip R. Zimmermann)发明了PGP加密算法,第一个版本的 PGP 程式与其中使用的加密算法 BassOmatic 都是由齐默尔曼发展出来。但 PGP 是商业软件,不能自由使用。所以,自由软件基金会决定开发一款自由软件以替代 PGP。这就是GPG的由来。

PGP 与 GPG 的关系

PGP

  • Pretty Good Privacy,是一个被设计用来加密信息,保护隐私的软件。
  • OpenPGP 是与最初 PGP 工具兼容的 IETF 标准
  • 现在提到“PGP”, 基本上是说 OpenPGP 标准。

GPG

  • 即GnuPG。
  • GnuPG (“Gnu Privacy Guard”)是实现了 OpenPGP 标准的自由软件。
  • GnuPG 的命令行工具称为 “gpg”

GPG KEY 使用场景

  • 使用 GPG 密钥来签名你的 git commits
  • 使用公钥验证第三方软件的签名
  • 使用 gpg 公钥来加密你的邮件
  • 使用公钥认证来实现授权登陆 (Public Key Authentication)

Linux下使用GPG签名/加密

安装 GPG

Linux下GPG有两种安装方式。可以下载源码,自己编译安装。

./configure
make
make install

也可以安装编译好的二进制包

# Debian / Ubuntu 环境
sudo apt-get install gnupg

# Fedora 环境
yum install gnupg

# Arch 环境
sudo pacman -S gnupg

完成后键入以下命令

gpg --help

如果屏幕显示GPG的帮助,就表示安装成功。

生成密钥

gpg –-gen-key

回车以后会跳出

gpg (GnuPG) 2.2.41-unknown; Copyright (C) 2022 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.

gpg: directory '/c/Users/Administrator/.gnupg' created
gpg: keybox '/c/Users/Administrator/.gnupg/pubring.kbx' created
Note: Use "gpg --full-generate-key" for a full featured key generation dialog.

GnuPG needs to construct a user ID to identify your key.

Real name:

使用“gpg --full-generate-key”生成GPG密钥,有详细的配置参数。

gpg (GnuPG) 2.2.41-unknown; Copyright (C) 2022 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 (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
  (14) Existing key from card
Your selection?

默认选择第一个选项,表示加密和签名都使用RSA算法。
然后,系统就会问你密钥的长度。

RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072)

接着,设定密钥的有效期。

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)

个人使用建议选择第一个选项,即永不过期,回车即可。接下来,系统让你确认。

Key does not expire at all
Is this correct? (y/N)

输入y,系统要求提供个人信息。

GnuPG needs to construct a user ID to identify your key.

Real name:
Email address:
Comment:

"Comment"这一栏可以空着。
然后你的用户ID就生成了。

You selected this USER-ID:
    "xiaxi626 <aijiang1220966821@hotmail.com>"

Change (N)ame, (C)omment, (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.

几分钟以后,系统提示密钥已经生成了。

gpg: /c/Users/Administrator/.gnupg/trustdb.gpg: trustdb created
gpg: directory '/c/Users/Administrator/.gnupg/openpgp-revocs.d' created
gpg: revocation certificate stored as '/c/Users/Administrator/.gnupg/openpgp-revocs.d/2DF491A4DB677422C13A5398102ED6A053101F78.rev'
public and secret key created and signed.

请注意上面的字符串"2DF491A4DB677422C13A5398102ED6A053101F78",这是"用户ID"的Hash字符串,可以用来替代"用户ID"。
这时,最好再生成一张"撤销证书",以备以后密钥作废时,可以请求外部的公钥服务器撤销你的公钥。

gpg --gen-revoke [用户ID]

上面的"用户ID"部分,可以填入你的邮件地址或者Hash字符串(以下同)。
系统提示。

Create a revocation certificate for this key? (y/N)

密钥管理

查看公钥

list-keys参数列出系统中已有的密钥。

gpg --list-key

网上教程的显示结果:

  /home/ruanyf/.gnupg/pubring.gpg
  -------------------------------
  pub 4096R/EDDD6D76 2013-07-11
  uid Ruan YiFeng <yifeng.ruan@gmail.com>
  sub 4096R/3FA69BE4 2013-07-11

第一行显示公钥文件名(pubring.gpg),第二行显示公钥特征(4096位,Hash字符串和生成时间),第三行显示"用户ID",第四行显示私钥特征。
本机输入gpg --list-key 显示:

gpg: checking the trustdb
gpg: marginals needed: 3  completes needed: 1  trust model: pgp
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
/c/Users/Administrator/.gnupg/pubring.kbx
-----------------------------------------
pub   rsa3072 2023-08-25 [SC]
      2DF491A4DB677422C13A5398102ED6A053101F78
uid           [ultimate] xiaxi626 <aijiang1220966821@hotmail.com>
sub   rsa3072 2023-08-25 [E]

pubring.kbx是密钥块资源。
如果你要从密钥列表中删除某个密钥,可以使用delete-key参数。

gpg --delete-key [用户ID]

删除公钥前会提示

gpg: there is a secret key for public key "[公钥]"!
gpg: use option "--delete-secret-keys" to delete it first.

查看私钥

list-secret-keys参数列出系统中已有的私钥。

gpg --list-secret-keys

显示结果如下:

/c/Users/Administrator/.gnupg/pubring.kbx
-----------------------------------------
sec#  rsa3072 2023-08-25 [SC]
      2DF491A4DB677422C13A5398102ED6A053101F78
uid           [ultimate] xiaxi626 <aijiang1220966821@hotmail.com>
ssb   rsa3072 2023-08-25 [E]

输出密钥

公钥文件(.gnupg/pubring.gpg)以二进制形式储存,armor参数可以将其转换为ASCII码显示。

gpg --armor --output public-key.txt --export [用户ID]

or

gpg -a --export [用户ID] > public-key.asc

"用户ID"指定哪个用户的公钥,output参数指定输出文件名(public-key.txt)。

类似地,export-secret-keys参数可以转换私钥。

gpg --armor --output private-key.txt --export-secret-keys

or

gpg -a --export-secret-keys [用户ID] > private-key.asc

上传公钥

公钥服务器是网络上专门储存用户公钥的服务器。send-keys参数可以将公钥上传到服务器。

gpg --send-keys [用户ID] --keyserver hkp://subkeys.pgp.net

使用上面的命令,你的公钥就被传到了服务器subkeys.pgp.net,然后通过交换机制,所有的公钥服务器最终都会包含你的公钥。
由于公钥服务器没有检查机制,任何人都可以用你的名义上传公钥,所以没有办法保证服务器上的公钥的可靠性。通常,你可以在网站上公布一个公钥指纹,让其他人核对下载到的公钥是否为真。fingerprint参数生成公钥指纹。

gpg --fingerprint [用户ID]

输入密钥

除了生成自己的密钥,还需要将他人的公钥或者你的其他密钥输入系统。这时可以使用import参数。

gpg --import [密钥文件]

为了获得他人的公钥,可以让对方直接发给你,或者到公钥服务器上寻找。

gpg --keyserver hkp://subkeys.pgp.net --search-keys [用户ID]

正如前面提到的,我们无法保证服务器上的公钥是否可靠,下载后还需要用其他机制验证.

加密和解密

加密

假定有一个文本文件demo.txt,怎样对它加密呢?
encrypt参数用于加密。

gpg --recipient [用户ID] --output demo.en.txt --encrypt demo.txt

recipient参数指定接收者的公钥,output参数指定加密后的文件名,encrypt参数指定源文件。运行上面的命令后,demo.en.txt就是已加密的文件,可以把它发给对方。

gpg -ea -r [用户ID] filename

即会生成filename.asc的加密文件。

解密

对方收到加密文件以后,就用自己的私钥解密。

gpg --decrypt demo.en.txt --output demo.de.txt

decrypt参数指定需要解密的文件,output参数指定解密后生成的文件。运行上面的命令,demo.de.txt就是解密后的文件。

GPG允许省略decrypt参数。

gpg demo.en.txt

运行上面的命令以后,解密后的文件内容直接显示在标准输出。

gpg -o filename -d filename.asc

运行上面的命令以后,输入私钥密码。
即可把filename.asc的加密文件解密成filename文件。

签名

对文件签名

有时,我们不需要加密文件,只需要对文件签名,表示这个文件确实是我本人发出的。sign参数用来签名。

gpg --sign demo.txt

运行上面的命令后,当前目录下生成demo.txt.gpg文件,这就是签名后的文件。这个文件默认采用二进制储存,如果想生成ASCII码的签名文件,可以使用clearsign参数。

gpg --clearsign demo.txt

运行上面的命令后 ,当前目录下生成demo.txt.asc文件,后缀名asc表示该文件是ASCII码形式的。

如果想生成单独的签名文件,与文件内容分开存放,可以使用detach-sign参数。

gpg --detach-sign demo.txt

运行上面的命令后,当前目录下生成一个单独的签名文件demo.txt.sig。该文件是二进制形式的,如果想采用ASCII码形式,要加上armor参数。

gpg --armor --detach-sign demo.txt

签名+加密

上一节的参数,都是只签名不加密。如果想同时签名和加密,可以使用下面的命令。

gpg --local-user [发信者ID] --recipient [接收者ID] --armor --sign --encrypt demo.txt

local-user参数指定用发信者的私钥签名,recipient参数指定用接收者的公钥加密,armor参数表示采用ASCII码形式显示,sign参数表示需要签名,encrypt参数表示指定源文件。

验证签名

我们收到别人签名后的文件,需要用对方的公钥验证签名是否为真。verify参数用来验证。

gpg --verify demo.txt.asc demo.txt

举例来说,openvpn网站就提供每一个下载包的gpg签名文件。你可以根据它的说明,验证这些下载包是否为真。

在 Gitee 上使用 GPG key 来签名 commit

  1. Kleopatra创建OpenPGP密钥对,输入用户名和邮箱,注意邮箱必须与 Gitee 提交邮箱一致;
  2. 导出公钥和私钥文件;
  3. 打开公钥文件,复制内容到Gitee gpg_keys,复制页面GPG密钥的指纹字符串;
  4. 输入【查看公钥、私钥命令 | 输入密钥命令】,输入密钥到.gnupg文件夹;
  5. 输入查看公钥或查看私钥命令,得到"用户ID"的Hash字符串,和Gitee gpg_keys页面GPG公钥指纹字符串是一样的;
  6. 配置 Git
git config --global user.signingkey [Gitee网页GPG公钥指纹/本地"用户ID"的Hash字符串]
  1. 输入git config --global --list检查git config是否配置成功,输入git config --global --edit可修改配置;

  2. 添加到 Gitee 账户,查看GPG 公钥验证状态,GPG 邮箱为当前用户已激活邮箱验证才能通过;

    • 删除 仅移除 GPG 公钥,验证通过的 Commit 签名状态保持不变
    • 注销 移除 GPG 公钥并且将已验证的 Commit 签名状态修改为未验证
  3. 使用 GPG 签名进行提交

git commit -S -m "YOUR COMMIT MESSAGE"
git log --show-signature # 查看签名状态
  1. 提交的显示结果
git push origin master
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 4 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 564 bytes | 564.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Powered by GITEE.COM [GNK-6.4]
To gitee.com:xiaxi626/gpg-test.git
   8013057..53ca0f6  master -> master

  1. 签名状态,Commit出现Verified | This commit was signed with the committer's verified signature,成功。

    • Commit 验证通过的条件为:commit 提交邮箱与 commit GPG 签名所使用的公钥邮箱一致且 GPG 公钥验证通过。
  2. 查看 GPG 公钥

    • 输入 https://gitee.com/\<username>.gpg
    • 选择用户个人资料右上角的设置页面进入安全设置 - GPG 公钥
    • Gitee 平台 GPG 公钥:https://gitee.com/gitee.gpg

在 Github 上使用 GPG key 来签名 commit

Github 配置 GPG

配置 GPG 公钥到仓库

Github Setting -> SSH and GPG keys -> New GPG Key 导入即可

本地代码仓库启用GPG Sign

通过gpg --list-keys查看pub GPG key ID,后设置git签名时用的key

全局设置

# 配置已经生成的GPG Key ID
git config --global user.signingkey <pub GPG key ID>
# 配置启用GPG签名
git config --global commit.gpgsign true

指定仓库设置,需要进入代码目录:

# 配置已经生成的GPG Key ID
git config --local user.signingkey <pub GPG key ID>
# 配置启用GPG签名
git config --local commit.gpgsign true

重启 gpg-agent

第一次配置,必须重启,否则签名会失败,命令如下:

gpgconf –kill gpg-agent

上述步骤示例

Administrator@AUTOBVT-Q90417J MINGW64 ~/Desktop
$ git config --global user.signingkey 69A20512441F53BA4F13F93F74EA6A7E693AEF20

Administrator@AUTOBVT-Q90417J MINGW64 ~/Desktop
$ git config --global commit.gpgsign true

Administrator@AUTOBVT-Q90417J MINGW64 ~/Desktop
$ gpgconf –kill gpg-agent
gpg:OpenPGP:/usr/bin/gpg
gpgsm:S/MIME:/usr/bin/gpgsm
gpg-agent:Private Keys:/usr/bin/gpg-agent
scdaemon:Smartcards:/usr/lib/gnupg/scdaemon
dirmngr:Network:/usr/bin/dirmngr
pinentry:Passphrase Entry:/usr/bin/pinentry

Windows 上的 git-bash 上默认的 /usr/bin 目录在:C:\Program Files\Git\usr\bin\。

关闭GPG签名

所有仓库:

git config --global commit.gpgsign false
git config --global --unset commit.gpgsign

本地仓库:

git config --local commit.gpgsign false
git config --local --unset commit.gpgsign

git 使用

提交

git commit -am "feature: something"
git push origin develop

然后我们可以在 git 中看到 Verified 的标识。

如果不设置git config --global commit.gpgsign true,提交的时候加上一个 -S 参数就可以为提交签名:

git commit -S -m `your commit message`

提交tag时签名

git tag -s ...

查看日志

git log --show-signature -1

使用 Kleopatra 来签名/加密文件

Gpg4win(GNU Privacy Guard for Windows)是一个加密软件,用于对文件和电子邮件进行签名和加密。

它能够生成OpenPGP密钥对、签名/验证、加密/解密,还可以建立S/MIME认证请求。

OpenPGP 证书高级设置

1、密钥类型

  • RSA
  • DSA
  • ECDSA/EdDSA(默认)

2、证书用途

  • 签名
  • 证书 仅加密(默认)
  • 验证
  • 有效期结束于

新建密钥对后,界面会显示一条证书信息,包含名称、电子邮件、用户编号(认证的/已吊销)、有效期、密钥ID。
你可以双击证书来添加用户ID、认证用户IDs、吊销证书、吊销用户ID等。

签名/加密文件

  1. 新建OpenPGP秘钥对
  2. 输入名称和电子邮件
  3. 需要进行高级设置可以点击高级设置进行设置,默认密钥类型为ECDSA,可以根据需要设置过期时间
  4. 勾选“需要密码句保护生成的密钥”,输入密码句
  5. 密码复杂度低时会提示你重新输入密码,如果密码要求不是太高,直接点击Take this one anyway,如果需要返回重新设置,点击Enter new passphrase
  6. 秘钥对创建成功,右键“备份私钥”,生成密钥对的副本,输入密码,导出成功
  7. 导出公钥,右键该加密证书,导出公钥,选择公钥的导出目录,导出
  8. 将需要的加密的文件拖入到kleopatra中,点击签名/加密
  9. 选择加密证书以及加密文件存储位置,输入密码,加密完成

解密/验证文件

  1. 首先需要得到对方的加密公钥和私钥文件
  2. 先导入公钥文件(.asc),为本地创建一个证书
  3. 创建完成后,认证证书公钥,只认证自己,输入刚刚创建的本地证书密码
  4. 继续导入,选择私钥文件(.gpg)导入,查看证书详情,证书颜色已经变深,可以进行解密操作了
  5. 将对方发送的加密文件(.gpg)拖入到kleopatra中进行解密,点击 Save All 保存,得到解密后的文件

想要对方加密文件给你,你需要提供公钥给对方,对方用你的公钥进行加密,发送给你加密文件后,你需用你的私钥进行解密。

可以进行解密操作后,双击签名/加密输出的文件和点击解密/校验效果是一样的。

关于提交签名验证

使用 GPG、SSH、 或 S/MIME,可以在本地对标记和提交进行签名。 这些标记或提交在 GitHub 上标示为已验证,便于其他人信任更改来自可信的来源。

您可以在本地签署提交和标签,让其他人对您所做更改的源充满信心。 如果提交或标记具有可加密验证的 GPG、SSH、 或 S/MIME 签名,GitHub 会将提交或标记标示为“已验证”或“部分验证”。

存储库提交列表中提交的屏幕截图。 “已验证”以橙色轮廓突出显示。

对于大多数个人用户,GPG 或 SSH 会是对提交进行签名的最佳选择。 在较大型组织的环境中通常需要 S/MIME 签名。 SSH 签名是最容易生成的。 甚至可以将现有身份验证密钥上传到 GitHub 以用作签名密钥。 生成 GPG 签名密钥比生成 SSH 密钥更复杂,但 GPG 具有 SSH 所没有的功能。 GPG 密钥可以在不再使用时过期或撤销。 GitHub 将已使用此类密钥进行签名的提交显示为“已验证”,除非密钥标记为已泄露。 SSH 密钥没有此功能。

GPG 提交签名验证

GitHub 使用 OpenPGP 库来确认本地签名的提交和标记,是否根据你在 GitHub.com 上添加到帐户的公钥进行加密验证。

SSH 提交签名验证

可以使用 SSH 通过自己生成的 SSH 密钥对提交进行签名。 有关详细信息,请查看 user.SigningkeyGit 参考文档。 如果已使用 SSH 密钥向 GitHub 进行了身份验证,还可以再次上传该相同密钥以用作签名密钥。 可以添加到帐户的签名密钥数没有限制。

GitHub 使用 ssh_data(一种开放源代码 Ruby 库)来确认本地签名的提交和标记是否根据在 GitHub.com 上添加到帐户的公钥进行加密验证。

参考

👨‍💻 震惊!竟然有人在 GitHub 上冒充我的身份!
关于提交签名验证 - GitHub 文档
GPG入门教程 - 阮一峰的网络日志
Linux下GPG的使用
推荐一款好用的文件加密传输软件——Kleopatra(含详细使用文档)
Kleopatra文件的加密解密遇到部分问题_kleopatra解密_少猿的博客
使用 GPG Key 来构建签名、加密及认证体系 - 知乎
简明 GPG 概念 - 知乎
2021年,用更现代的方法使用PGP(上) - 知乎
PGP——密码技术的完美组合 - 简书
如何在 Gitee 上使用 GPG | Gitee 产品文档
使用 GPG 签名 Git Commit-谢先斌的博客
修改/重置 git 的全局配置 | 血衫非弧の一存