前言
GPG, 或 GnuPG (GNU Privacy Guard) 是一个遵照 OpenPGP 协议的用于加密、数字签名以及认证的软件。它与 PGP (Pretty Good Privacy) 的区别是它是开源的,而 PGP 则是 Symantec 公司的专有软件。
在数字世界,我们经常需要进行邮件加密、数字签名或者登陆认证等操作,GPG 就是这样一个既可以方便我们管理公私钥,又可以随时满足我们需求的密钥管理工具。
如果你使用 Linux 发行版,gpg 工具是自带的 (/usr/bin/gpg
),你可以执行 gpg -h
来查看帮助信息。本文介绍如何使用 GPG 来构建我们的签名、加密以及认证系统。
第一部分:GPG KEY 使用场景
为了有个直观的理解,我先列举几个使用场景:
1. 使用 GPG 密钥来签名你的 git commits
向 github 仓库提交代码时,如果你的 commit 经过已授权的 GPG Key 签名,那么会显示为绿色的 Verified 状态。
如果你使用了未授权的密钥进行签名,则会显示
或者你的密钥已经授权,但你进行 commit 时使用了非认证的 email(冒充别人或别人冒充你)
对于要求多重签名却又缺少其中某个签名的:
类似的,发布 release,或者添加 tag,通常都应该使用签名以便使得授权主体更加明确。
需要使用签名的场景非常多,可以说覆盖开发和管理的个个角落,有了 GPG 来帮助我们管理自己的密钥,面对各种场景就会得心应手。
2. 使用公钥验证第三方软件的签名
软件作者在软件发布时,Release 页面通常会同时提供程序文件和签名文件。通常,签名文件是软件作者使用私钥对软件的摘要(digest)进行签名而得来。我们可以在拿到作者的公钥后对签名进行验证。如果验证失败,那么说明下载的软件已经被篡改。这种情况通常发生在有人下载作者的软件之后,修改软件并注入木马,然后重新发布到假的镜像站点;如果你没有对签名进行验证,就会存在风险。
公钥或签名文件通常会以 .sig
或 .asc
为后缀。
gpg --import veracrypt_pgp_public_key.asc
gpg --verify veracrypt-1.25.9.sig veracrypt-1.25.9.deb
3. 使用 gpg 公钥来加密你的邮件
当我们苦苦寻找安全性更高,支持加密的邮箱时,有了 GPG Key,我们可以自己加密邮件了。下面演示使用 GPG KEY 对 GMAIL 邮件加密。
1) 发送加密邮件
2) 收到加密邮件
可借助插件自动使用私钥进行解密,解密后文本如下:
3) 未解密的邮件
如果不自动解密,则会显示 ASC 格式的密文 (ASC 格式是 base64 略作修改而来,gpg 可通过 --armor
参数把二进制内容进行字符化)。
比如下图,我在手机上打开 Gmail 邮件 则会显示为密文,这是因为我手机上没有导入我的私钥,所以无法解密。
4) 使用公钥认证来实现授权登陆 (Public Key Authentication)
Publick Key Authentication (公钥认证)是服务器最常见的应用,比如 SSH 登陆,公钥认证是比密码登陆要安全得多的方式。具体的方式就是把需要登陆的用户的公钥提交到服务器上 ~/.ssh/authorized_keys
,服务器上的 sshd 程序使用公钥加密一个 shared key 发给用户,用户用私钥解密后得到 shared key,实现认证;并且双方在后续的通信中使用这把对称密钥进行加密通信。
如果你在 ~/.ssh/
目录中生成了太多的 key pairs,管理这些 keys 肯定会让你头疼,而如果你使用 gpg 来管理这些密钥的话,就会方便很多。gpg 生成的密钥格式与 ssh-keygen
所生成的密钥格式不相同,但是可以使用 --export-ssh-keys
命令选项来到处 ssh 格式的公钥,并借助 gpg-agent
加载私钥,从而使得 ssh 可以使用 gpg 的密钥建立加密连接。使用 gpg keys 来替代原来的 ssh keys 最大的好处就是管理更加方便和简单。而且,如果你使用了支持 gpg key 的硬件(比如 Yubikey
),你的安全性也更加高了。
第二部分:GPG Keys 核心概念
下面介绍 GPG 中的核心概念以及 GPG 的公钥加密体系的原理
1. Key 密钥,每个 Key 都包含两部分:Private Key 和 Public Key。
2. Fingerprint 指纹,指纹是 Public Key 的散列值(默认使用 MD5 算法),gpg 中的 fingerprint 与 SSH 的计算方法略有不同,SSH 中 fingerprint 是直接对 base64 的结果再计算 MD5 得来,而 gpg 则对合并后的 modulus
和 exponent
等参数的二进制进行 MD5 计算而来。
3. KeyID,GPG 中使用 fingerprint 来作为 KeyID,用来标识一个 Key。总长为 32-bit。
- Long ID 用 fingerprint 的后 16 位字符 (HEX)来表示
- Short ID 则用 fringerprint 的后 8 位字符 (HEX) 来表示
如果不加 --keyid-format
,则默认显示的是 LONG ID。
4. UserID,GPG 中也使用 UserID 来标识一个或多个 Key。由于 Key 必须绑定至少一个 UserID(也就是 Email 地址),所以 UserID 也可以用来标识 Key。 区别是:同一个 UserID(即 Email 地址) 可以绑定多个 Keys。
5. Keyring,密钥环,是指存储密钥的数据库。默认情况下,所有的本地密钥都保存在 ~/.gnupg/pubring.kbx
中
另外,我们可以把 gpg 命令的常用配置信息写入 ~/.gnupg/gpg.conf
,执行 gpg
命令的时候会自动读取该文件。
6. Key Server,专门用于存放 Public key 的服务器。gnugp 提供了一个免费的 key server https://keys.openpgp.org/
,当我们执行 gpg --send-keys [keyid]
命令来发布一个 public key 的时候,它会自动发送到这个 key server 中去。另外,当我们执行 gpg --search-keys [keyid]
或 gpg --recv-keys [keyid]
的时候,也都是会对默认的 key server 操作。
7. Keygrip,是一个 20 字节长度的,与 protocol 无关的的值,通过对 key 的 modulus 参数计算 md5 得来。比如,对于使用 RSA 算法的 key,具体实现是把 modules 作为 unsigned integer,并去除二进制高位的0,然后计算 sha1 得来。
Keygrip 相比 fingerprint 的特别之处是,它可以唯一标识一个 Key,因为它只跟你的密钥参数(如 modulus)相关,因此在 GPG 和 SSH 中,相同的 keygrip 都表示一个相同的 key。当我们执行 gpg --gen-key
命令时,会 keygrip 文件会自动生成在目录 ~/.gnupg/private-keys-v1.d/
下,并以 keygrip-id.key
命名。
我们可以用 gpg -k --with-keygrip
命令来查看 Key 对应的 keygrip:
keygrip 一个用处是,如果我们只想删除 Master Key,当我们执行 gpg --delete-secret-keys [master-keyid]
,他会继续追问我们是否删除 subkey,否则会删除失败。这是由于 KeyID/Fingerprint 会关联 Master Key 及所有的 subkey,所以如果你只想单独把 Master Key 删除,那么可以使用它的 keygrip:
gpg-connect-agent "DELETE_KEY 5EACE229E5EA90792805777B9163400FE3D189D4" /bye
8. Master Key and Subkey
当我们使用 gpg --gen-key
命令生成 GPG Keys 时,默认会创建一个 Master key 和一个 Subkey。
Master Key 和 Subkey 是什么关系?
本质上,Master key 和 Subkey 都是独立生成的,两者在生成时彼此之间并无依赖关系;但是在生成之后,GPG 用一个叫 Binding Signature 的东西把两种进行关联。简单来说就是 Master key 对 Subkey 进行签名,声明自己对 Subkey 的 Owner 关系;同时 Subkey 也对 Master Key 进行签名,声明自己对 Master Key 的 Member 关系。
RFC 4880 中对此有解释如下:
我们可以使用 --check-sigs
选项来查看 Key 中的签名:
使用 --list-sigs
来列出所有签名
MasterKey 和 Subkey 互相对彼此签名,--check-sig [keyid]
显示的是 Master key 所拥有的签名数量,由于 Master key 会对自己签名,再加上 subkey 也会对他签名,所以最下面显示它有 2 个 goog signatures。如果我们只生成 Master Key,那么它会只有一个签名,就是自签名。
9. GPG Message Format
如上所述,当我们执行 gpg --list-keys
或 gpg --list-secret-keys
命令查看 Key 信息时,显示结果包含了 fingerprint, date, expiration, usage, signature 等等许多信息,完整的 GPG Keys 格式是定义在 OpenPGP 协议中的,每个组成部分的描述都可以在 RFC 4880 中找到。 RFC 4880 把 Key 格式中每部分叫做一个 packet,每个 packet 分配一个 tag,这些规则都定义在 OpenPGP 协议中,RFC 4880 中的对应描述如下:
比如,我们想要查看 fingerprint 使用的散列算法,我们可以这样
使用的是 digest algo 10,我们可以在 RFC 4880 9.4 Hash Algorithm 一章查到如下信息:
可以看到,ID 10 对应的就是 SHA512 算法。关于算法的选择,我们是可以通过参数设置的,比如常见的设置的方法是在 ~/.gnupu/gpg.conf
中添加如下内容:
personal-digest-preference SHA512 SHA384 SHA256 SHA224
personal-cipher-preference AES256 AES192 AES CAST5 CAMELLIA192 BLOWFISH TWOFISH
CAMELLIA128 3DES
personal-compress-preferences ZLIB BZIP2 ZIP
cert-digest-algo SHA512
digest-algo SHA256
如果你想要看更多或更加 human readable 的 detail,你可以安装 pgpdump
工具来查看。
10. GPG Keys 的四种用途:
- Certify
- Sign
- Encryption
- Authentication
在第一部分已经根据 GPG KEY 的用途介绍了多种使用场景,所以这部分内容并不难复杂,这里再稍微赘述一下以方便对文章后续内容的理解:
Certify or Certification 翻译成“证书授权,发证书”,本质就是 Sign 操作,也就是用私钥对目标进行签名(其实就是加密操作,之所以不能算是真正的加密是因为只要是公钥就可以解密,而公钥人人都可以获得,所以就失去了“加密”的内涵,人人都知道的东西肯定不叫秘密了)。但是签名的对象通常有两种,一种是对数据进行签名,而另一种则是对 Key 密钥进行签名。我们把对数据进行签名叫做 Sign ,而对密钥 (Public Key) 进行签名叫做 Certify。对于前者 gpg 提供的命令选项是 --sign (or --clearsign)
,对于后者,gpg 提供的命令选项是 --sign-key
。
信任链 Trust Chain
当 A 用其私钥对 B 的公钥进行签名之后,B 就相当于找了一个人给他背书,这样,当 B 把公钥给第三个人 C 的时候,C 就更倾向于相信 B 的公钥确实是他的,当越多人对 B 的公钥进行签名,B 的公钥的受信任程度就越高。相比于 PKI 体系中的依靠权威证书机构所构建的信任体系,这是一种分布式的信任链。比特币的制造原理也是基于类似这样的分布式信任,当某个矿工打包一个区块并用自己的私钥对这个区块进行签名,发布到网络中之后,越来越多的矿工也对这个区块进行签名,这个区块就越加变得可信,最终达到难以摧毁的信任程度,也就是信任强化(Trust Hardening)。
Encryption 是只用公钥进行加密,在非对称密钥体系中,公钥加密的内容只能通过对应的私钥才能解密,但是由于加密速度非常慢,所以公钥加密通常用在传输体积较小的数据上,比如邮件就是最适合的应用场景。另外一个场合加密通信的握手阶段进行密钥交换。
另外,gpg 也支持对称密钥加密,比如使用下列命令就可以用对称密钥加密一个文件:
gpg --symmetric --cipher-algo AES256 hello.txt
会弹出对话框提示你输入 passphrase
,输入之后就会生成加密文件,操作非常简单,这里不赘述。
Authentication 是认证,我们常见的认证方式就是密码登陆,但是由于密码存在暴力破解的问题,所以并不是一种非常安全的方式,如果你购买过公网服务器就知道,只要一上线,/var/log/auth.log 中就有无数的密码尝试登陆记录。所以,通常更加安全的方式就是使用公钥认证,它的加密强度更高,目前无法破解。公钥认证的原理跟使用公钥加密一样,服务器持有用户的公钥,只要用户持有对应的私钥(可以解密服务器下发的加密后的数据),就可以验证用户身份。最常见的比如 SSH 登陆,另外 Kubernetes 也是用客户端证书来校验用户身份,把证书中所授权的 Subject 映射为集群中的 Authorized User,Organization 则为组 Group。证书与公钥的主要区别在于前者绑定了用户身份及域名信息(Subject, SAN, etc.),在 GPG 的消息格式中,Public Key 也同样绑定了用户身份(User ID)。
上面介绍了 Key 的四种功能,只有 Master Key 有 Certify 的功能。也就是说,只有 Master Key 可以对其它 Key 进行签名,subkey 是无法这么做的。
第三部分:构建 GPG Keys
下面介绍如何使用 gpg 工具来构建 GPG keys。
gpg 是一个 Linux 下自带的一个命令行工具(多数发行版都自带这个工具),如果你是使用 macOS 或 Windows,那么你可以分别下载 GPG Suite 和 Gpg4win)。官网就提供了多种平台的下载链接:gnupg.org/download/ 。
根据前面介绍的 KEY 用途和功能的不同,我们需要构建如下的 Keys
为什么要构建这么多 Key?
主要是基于网络安全中的最小权限和职责分离原则。
Master Key 作为类似于 PKI 体系中的 Root CA 的功能,永不过期,只有在需要签发新的 subkey 和 吊销 subkey 的时候才出来干活,平时放置在一个离线的安全存储中(比如硬件密钥盘 Yubikey)。而各个具有不同功能的 subkey 则作为真正的打工人,负责平时每日的干活,有效期短,过期后需要使用 Master key 签发新的。
1. 创建 Master keys
Master Key 应该只用于 Certify,使用安全系数最高的 Ed25519 椭圆曲线算法,并且永远不过期。
命令末尾会提示你是否输入一个 passphrase 密码来保护密钥的访问,建议输入一个保护密码。如果嫌密码不好记忆,可以暂时不设密码,因为后续还可以手动再添加。另外,关于密码的管理,可以参考我另一篇文章《如何管理你的密码》。
参数解释:
--expert
是启用专家模式,让你对参数有更多的选择权。--full-gen-key
比--gen-key
提供更加丰富的选项。
上面两个选项都是交互性的,我们也可以使用 --quick-gen-key
选项来一键生成,命令格式是:
--quick-generate-key user-id [algo [usage [expire]]]
示例:
gpg --expert --quick-gen-key "Elon Musk(Test for lab) emusk@spacex.com" ed25519 cert 0
上述命令依然会弹出交互对话框询问你是否需要设置 passphrase,我们可以用选项 --batch --passphrase ""
来彻底一键化
gpg --expert --batch --passphrase "" --quick-gen-key "Elon Musk(Test for lab) emusk@spacex.com" ed25519 cert 0
注: 如果没有添加 --expert
选项,将无法选择 ed25519
算法。
2. 编辑 Mster key,为其添加 subkey
创建 3 个 subkey,分别赋予 Sign Only, Encrypt Only 和 Authenticate Only 功能,有效期均为 1 year。
需要注意的是:上面添加 Authenticate Only subkey 时,选择的是 toggle capability 方法,默认情况下 Sign, Encrypt 是启用的,Authentication 是禁用的,用二进制表示就是 110;因此,我们要仅启用 Authentication 的话,就需要变成 001。
3. 导出并删除 Master Key
添加完所有需要的 subkey 之后,Master Key 的主要工作就完成了,平时的 Signing, Encryption, Authentication 等功能都由 subkey 去完成。这时候我们应该把 Master Key 保存到一个离线的安全地方,以避免因为设备被盗取、破坏或丢失等因素导致 Master Key 丢失。
什么时候需要用到 Master Key?
- when you sign someone else's key or revoke an existing signature,
- when you add a new UID or mark an existing UID as primary,
- when you create a new subkey,
- when you revoke an existing UID or subkey,
- when you change the preferences (e.g., with setpref) on a UID,
- when you change the expiration date on your primary key or any of its subkey, or
- when you revoke or generate a revocation certificate for the complete key.
接下来我们需要执行
- 导出 Master Key 的私钥,保存到离线 U 盘中。
- 创建 Master Key 的吊销证书,防止私钥丢失后无法吊销,保存到另一个 U 盘中。
- 在 Keyring 中删除 Master Key 的私钥。
验证 Master Key 私钥已经被删除
当私钥被删除后,会出现 sec#
,否则显示为 sec
。
4. 导出 subkey 的私钥和公钥
我们可能在多台机器上工作,没必要在每台机器上都建立一个 GPG KEY 系统;所以我们把 subkey 的私钥导出到我们各个工作时需要用到的机器上。导出 subkey 的公钥为文件主要是方便我们分发给需要的人,比如你需要别人给你的公钥签名以增加信任,或者别人需要用你的公钥加密邮件发给你,等等。
5. 操作实验
(1) Encrypting
--recipient
指的就是接收方,你用接收方的公钥加密文件并发给对方,对方持有私钥,所以才能解密。
(2) Decrypting
(3) Signing keys
当需要给别人的 public key 签名时,我们需要先导入 Master Key 的私钥(前面提到我们已经删除 Master Key)。
选项 --default-key
的意思是指定 signer,也就是我们用来给别人签名的 Master Key。如果没有指定,那么 gpg 会使用 keyring 中默认的 signer,通常是 keyring 中的第一个 Key。我们可以在 ~/.gnupg/gpg.conf
中修改:
(4) Signing data
gpg 提供 --sign
和 --clearsign
对数据进行签名,两个选项都会把签名与原数据文件打包成一个整体,--sign 还会对数据进行压缩,后者不压缩。
上述两个选项把签名和数据写在一起,所以对使用方来说很不方便,所以实际用途不大,更常见的还是把签名文件和数据文件分开来。gpg 提供 --detach-sign
选项来实现:
(5) Revoking
Revoke 分两种:Revoke Master Key 和 Revoke Subkey
注意:只有 Master Key 才能 Revoke 自己和 subkey,所以操作之前记得先把 Master Key 私钥导入。
1) Revoke Master Key
主要分三步:
- 生成 Revoke Key
实际上这一步可以省略,还记得在执行
gpg --gen-key
的时候,会默认生成一个 revoke key 在~/.gnupg/openpgp-revocs.d/
目录吗? - 把 Revoke key 导入 Keyring
- 把 Revoke key 分发出去(上传到 key server 或传给别人)
2. Revoke subkey
通过编辑 Master Key 实现:
完成操作之后,不要忘记使用 --send-keys [keyid]
命令选项把 key 更新到 Key Server,以便别人的值你的 subkey 已经被吊销。
第四部分:使用 GPG Keys
看到这里,应该已经熟悉了 GPG 工具的大部分操作方法,个人的 GPG KEY 也已经建立得相对完善了,那就回到开头第一部分介绍的应用场景,简单介绍一下如何实现。
有了上面的理论基础,下面的步骤和操作都将变得简单:
1. 使用 GPG Key 对 git commit 进行签名
步骤如下:
- 导出具有 Sign 功能的 subkey 的公钥
- 把公钥导入到 Github 设置页面
- 设置本地的 git client,以便它使用 keyring 中的私钥对 commit 签名。
2. 使用 GPG Key 加密邮件
如果使用 Gmail,我们可以安装一个浏览器插件 Flowcrypt,它支持 Chrome, Firefox, Brave 三个浏览器,同时还提供 Androd 和 iOS App。这个插件可以自动使用我们 GPG Key 对 Gmail 邮件(不支持其它邮箱)加密。
操作方法非常简单,安装插件时会提示你创建或者导入 GPG Key,由于我们已经建立了自己的 GPG Keys,所以我们直接导入有 Encryption 功能的 subkey 的私钥。
完成设置后,Flowcrypt 会把我们的公钥上传到它的 Key Server 中,这样其它也安装了 Flowcrypt 插件的人也可以获得我们的公钥,也就能给我们发加密邮件了。
打开邮箱后,我们会收到 Flowcrypt 用我们的公钥加密的一封邮件,我们打开后立即是解密状态,这是因为 Flowcrypt 用我们的私钥自动解密了。
需要记住的是,我们目前还不能给别人发加密邮件,因为我们没有别人的公钥。
所以,如果我们想要给别人发加密邮件,就需要有他/她的一个公钥。目前 Flowcrypt 虽然有自己的 Key Server,但是并不支持手动上传公钥,因此要求对方也安装 Flowcrypt,这样他/她的公钥也在 Flowcrypt 的 Key Server 上,这边就能自动检索到。
下面演示一下如何给别人发加密邮件:
地址栏里的 recipient 就是你的发送对象,如果对方也安装了 Flowcrypt 并载入过私钥,那么这里就会显示绿色,说明你可以发加密邮件给对方。
另外,如果你的邮件客户端需要支持 GPG Key,否则收到加密邮件后无法自动解密。上面的 Chrome 浏览器是就是 Gmail 的客户端,我们安装了 Flowcrypt 所以它可以帮我们自动解密,如果我们换成用其它客户端(比如 Outlook 等)打开邮件,则会显示密文,如下:
在 Thunderbird 中导入对应的 GPG 私钥,
又可以自动解密了。
3. 使用 GPG Key 做 SSH 认证
做法很简单,步骤如下:
-1) 导出 SSH 格式的公钥,并上传到服务器
gpg --export-ssh-keys 64810DE8 > ~/.ssh/gpg_subkey.pubssh-copy-id -i ~/.ssh/gpg_subkey.pub server:./ssh servercat gpg_subkey.pub >> ~/.ssh/authorized_keys
- 2. 关掉 ssh-agent,启动 gpg-agent
echo enable-ssh-support >> $HOME/.gnupg/gpg-agent.conf
cat >> ~/.bashrc << EOFunset SSH_AGENT_PIDif [ "${gnupg_SSH_AUTH_SOCK_by:-0}" -ne $$ ]; then export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"fiexport GPG_TTY=$(tty)EOF
gpg-connect-agent updatestartuptty /bye >/dev/null
- 3. 测试登陆 SSH
ssh -T git@github.com
Reference:
datatracker.ietf.org/doc/html/rf…
blog.djoproject.net/2020/05/03/…
wiki.archlinux.org/index.php/G…
unix.stackexchange.com/questions/3…
security.stackexchange.com/questions/1…
davesteele.github.io/gpg/2014/09…
www.digitalneanderthal.com/post/gpg/
fedoraproject.org/wiki/Creati…
security.stackexchange.com/questions/1…
如果你觉得我的文章对你有帮助,欢迎留言或者关注我的专栏。
微信公众号:“知辉”
搜索“deliverit”