前言
久闻 PGP 大名,印象中是使用非对称加密算法,且经常能在邮件列表的签名档中看到一长串的 PGP Public Key,却一直不知其原理及用法。最近把这块知识盲区扫除并记录成文。
PGP、OpenPGP 和 GPG
PGP 是 Pretty Good Privacy 的缩写,由 Philip R. Zimmermann 于 1991 年编写并上传到 USENET 而在全球流行,被广泛用于文本、电子邮件、文件甚至整个磁盘分区的签名、加密和解密。上传行为因涉嫌违反美国的加密软件出口限制而使 Zimmermann 陷入长达三年的犯罪调查。政府的调查终止之后,Zimmermann 创立了 PGP 公司,PGP 由此成为商业软件(PGP 公司在辗转多次之后最终被 Symantec 收购)。
OpenPGP 是由 PGP 衍生出的开源规范,而 GnuPG(简称 GPG)就是遵循 OpenPGP 规范的 GNU 实现。在不需要特别说明差异的情况下,三者统称为 PGP。
PGP 的安全性有多高呢?举个例子,记录片《Citizenfour》中 Edward Snowden 就是使用 PGP 与女记者 Laura Poitras 收发邮件的,以下来自电影截图:
PGP 加密机制
本部分主要参考《OpenGPG for Complete Beginners》一文。
计算机世界的加密主要有两种类型:「对称加密」和「非对称加密」。所谓「对称」或者「非对称」,指的是加密和解密使用的是否同一个密钥。
加解密使用同一个密钥的加密方法称为「对称加密」,主要应用于磁盘加密、网页会话 cookie 的加密等密钥不需要共享或可方便地在多人间共享而不泄漏密钥的场景。
而加解密使用不同的密钥的加密方法称为「非对称加密」,把同时生成的密钥对中的一个称为「公钥」可以对外公开,一个称为「私钥」须严加保管。非对称加密使用的算法保证了使用公钥加密的内容只能使用私钥解密,反之亦然。具体地说,使用公钥加密的内容即使使用公钥也无法解密,同样的,使用私钥加密的内容即使使用私钥也无法解密。另一方面,假如使用公钥能解密某一段密文,那么可以肯定此段密文是使用对应的私钥加密的,反之亦然。PGP 使用的就是非对称加密算法。
下面分别介绍非对称加密如何实现密码学中的保密性(confidentiality)、真实性(authenticity)和完整性(integrity)。
保密性 Confidentiality
非对称加密实现保密性(Confidentiality)的大致过程如下:你同时生成公钥和私钥,将公钥公之于众,将私钥严加保管。想要给你发邮件的人使用你的公钥对邮件加密之后发送给你。你收到加密的邮件之后,使用自己的私钥解密。
但非对称加密算法有个缺点,就是加解密的过程比较消耗计算资源,且某些算法生成的密文长度可能是明文长度的两倍。为解决这些问题,通常使用「混合加密」的方式,就是对称加密和非对称加密混合在一起使用。对方在给你发送邮件之前,不是直接使用你的公钥对邮件加密,而是:
- 生成一个唯一且随机的 nonce 值(number used once)
- 以 nonce 值为密码,使用「对称加密」算法对邮件加密(第一部分)
- 用你的公钥对 nonce 值加密(第二部分,非对称加密)
两部分内容同时发送给你。你收到之后,使用私钥对第二部分解密得到 nonce 值,再使用此 nonce 值解密第一部分得到邮件明文。
真实性 Authenticity
所谓真实性(Authenticity),就是如何确定你收到的邮件真的是由「发件人」所发送的呢?毕竟你的公钥是公开的、任何人都可获取到的。
前文提到过非对称加密的一个特征:假如使用公钥能解密某一段密文,那么可以肯定此段密文是使用对应的私钥加密的,反之亦然。利用这一特征,信息的发送者先使用「密码学哈希函数」对信息做哈希运算得到哈希值,再使用发送者的私钥对哈希值加密,得到的加密后的哈希值就是「签名(signature)」,先哈希再加密的过程称为给信息签名(sign)。签名与原信息一起发送给接收者。接收者需要做的是:
- 采用同样的「密码学哈希函数」对原信息部分做哈希运算
- 使用发送者的公钥对接收到的「签名」进行解密
- 比较上述两个结果是否一致。如果一致,就可以肯定此信息来自公钥的拥有者。
完整性 Integrity
上述实现真实性的同时,其实也实现了完整性(Integrity)。因为假如信息被篡改,那么哈希校验同样会失败。
Linux 内核源码每次发布都会附带一个同名的 .sign
文件即是对源码包的签名,既可用于认证此源码包由特定的维护者发布,同样也确保源码包在下载过程中无差错,具体验证方法详见下文「文档的签名与认证」部分。
keyserver
keyserver 就是存储公钥的服务器,你的公钥要对外公布,最简单的方法就是上传到 keyserver 上,同时包含了你的姓名及 Email 地址。你要给别人发加密邮件,你知道对方的姓名、Email 地址或 PGP Key ID 等信息,都可以在 keyserver 上搜索对方的公钥。
主流的 keyserver 之间通过特定协议定时同步,所以在对外发布公钥时,将公钥发布到一个 keyserver 即可。如何与 keyserver 交互请参见下文「与 keyserver 交互」部分。
信任网络 Web of Trust
Web of Trust 是 PGP 中一个非常重要的概念。如上所述,keyserver 是对公众开放的公钥服务器,任何人都可以上传公钥并声称自己是 Linda Ye,那么如何得知哪个公钥是真正的 Linda Ye 上传的呢?
和「真实性」一样,也是通过「签名」实现的。假如我对某一个声称自己是 Linda Ye 的公钥签名了,就表示:「我信任这个公钥,这就是属于 Linda Ye 的公钥!」越多人对此公钥签名,对应的公钥就越可信。keyserver 上的 PGP 用户相互之间对其认为可信的公钥进行签名,最后,众多公钥和签名的集合,构成了「信任网络(Web of Trust)」。
这里还有另外一个问题。我在网上认识了 Yoona Yuan 和 Frank Luo,他俩也把自己的公钥上传到 keyserver 了。我们三人之间都没有见过面,但我们相谈甚欢,此时我们应该如何确认 keyserver 上的各自公钥的真实性呢?毕竟 keyserver 也有被黑的可能,毕竟「在互联网上没有人知道你是一条狗」。
此时就有充分的理由开展线下聚会啦~也就是所谓的「公钥签名派对(Key signing party)」。参与派对的人们相互交换公钥的指纹信息(key fingerprint,每个公钥的唯一标识),甚至需要相互出示身份证、护照、驾照、出身证明什么的,以验明正身,场面热闹非凡(下图来自维基百科,图中并没有我、Yoona Yuan 或 Frank Luo)。
需要特别说明的是,带到线下聚会的公钥指纹必须是在各自的设备上生成的指纹,而不是 keyserver 上的指纹,否则你上传到 keyserver 的公钥被黑了的话,线下聚会验证的就是被黑的公钥了。线下相互交换并验证身份之后,就可在线上对对方提供的指纹对应的公钥进行签名了,具体方法详见下文「对公钥签名」部分。
子密钥 Subkey
假如你的私钥泄漏了或被黑了,就需要将 keyserver 上的公钥都废除并重新生成新的密钥对。但与此同时,之前的公钥对应的签名及信任关系也全部作废了,重新建立众多信任关系将是一场灾难。
PGP 使用「子密钥(subkey)」的概念解决此问题。在实际生成密钥对时,标识你的身份的是一个「主密钥对(master keypair)」,而实际用于加解密的是「子密钥对(subkey pair)」。假如你的某个子密钥对被黑了或者密码泄漏了,只需要将对应的子密钥废除并重新生成即可,主密钥对的签名和信任关系都不受影响。
PGP 的使用
明白了上述的原理性说明之后,实际操作起来就很简单了。
安装 GnuPG 及图形化的 Gnu Privacy Assistant 可参考以下两篇文章,本文仅介绍命令行操作方式。
安装 GnuPG
我使用的是 Ubuntu,安装 GnuPG 使用如下命令:
sudo apt-get install gnupg2
本地 key 管理
GnuPG 的命令都以 gpg
实现,其中生成新的密钥对有三个命令:--gen-key
、 --full-gen-key
和--quick-gen-key
,第一个命令将采用默认的密钥类型、密钥长度、密钥有效期,而直接从输入姓名和 Email 地址开始;第二个命令则以交互方式询问密钥相关配置;第三个命令则是将所有配置(除了 passphrase)都以命令行的方式传入。输入完姓名和 Email 地址之后,会弹出对话框要求输入 passphrase,这个就是用于保护私钥的密码了,建议使用密码管理器生成并妥善保存。最后需要做些随机操作以便生成随机数,此时可以移动鼠标上上网、刷刷推特听听歌。
生成之后,使用 --list-keys
查看本地所有的密钥:
$ gpg --list-keys
/home/yestyle/.gnupg/pubring.gpg
--------------------------------
pub rsa2048 2017-07-24 [SC]
D2934E56A93F437FEE9BC3BB1A3EC174674F551F
uid [ultimate] Philip Ye <[email protected]>
sub rsa2048 2017-07-24 [E]
pub rsa2048 2017-07-24 [SC]
445A417AE47BCDC582FD18B3E349E491E8513A14
uid [ultimate] Philip Ye <[email protected]>
sub rsa2048 2017-07-24 [E]
其中一长串的十六进制数就是公钥对应的指纹信息,在将公钥作为上传到 keyserver 之后,指纹信息的最末 8 个字符将作为其 Key ID。
在电子邮件中常见到的公钥是以 ASCII 形式出现的,而将某个公钥以 ASCII 形式导出,可在 --export
命令之前增加 --armor
选项,其中使用了 Key ID 作为特定公钥的标识:
$ gpg --armor --export 674F551F
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQENBFl1+EoBCADPJTl1BLoXrYV5+CZadF/P7DtFoHrY4TLz7C97cZcxYM4N1gLL
vmfjcC2iEnc20mcztU3kiTzZyY+gGeJ3nX4LEn0x6CQo8TBmpenvEsJGWlez3BoV
oWQqg4cNACEUhMfDcE30olH/3/2dzAqPqW04/xIKa5DE/XFkEQUP/mp7eFR4htRG
lVzRII1obKI+rCnnCcjMrupNwa6czC5cLb/frq+9bHwjQZlrTVwZNv+U2gVoW1S2
fDpu2nQ7xk8LfiZQJx61A7EUS2jG19QslhD3uSoJGNOf5Uci/PRLMqAzafm9WHKs
iWAl9IlZgwp47VMxkAFeELUdBg9HLfDWZnUrABEBAAG0HVBoaWxpcCBZZSA8eWVz
dHlsZUBnbWFpbC5jb20+iQE3BBMBCAAhBQJZdfhKAhsDBQsJCAcCBhUICQoLAgQW
AgMBAh4BAheAAAoJEBo+wXRnT1UfM2kH+gPR9raXY6hs0txZ/lTN8nYTA3p6RowO
Sx/mwymjousnPWacuAUqWAfY8QYybio+vqgt+f2IiwyHOkZjfZvWRavgUHG0wd1Y
1iq1diJVjBlDaALUrNb24HdjZuxG2enqHMzfavolTbOwz1nPHvjG0eNQP0JkwyBF
M/tdGVbRJPTk8iQYp+yp8UMUJ7OOenxAesu9eKEEW6gC10JUV/j1d7Im1Zc2pUsB
VQY1pjAAxtktdYMtrmX4ADi2yCWMXi64f8R79AI80WkVbqRKinXGb2HOqCXO1qVS
hzC/Lz9lXEOvc+DstM5mcIGLJkPE6Y3KWVFSmlrqcSd0do3wGpMjfrK5AQ0EWXX4
SgEIAKCVmhobfWxVslLtvoUruX/I1qw6UA5ELZAvIzJLamlKAXkGcqFebuoG8ay6
D2ysX6hnhjJ4TnyjQMyR8/K6L28rD4m22mj73l3yilodtKLyQI+HPuJnucozt5yc
qTEisTXT2Ny+Xm3Bc3eoTGnkFalJFtIuwkTYyEfvYakA65YSowPbyAptIsVC1sLE
mkBjJhod2s3imeZvzNW/srQeRSVBW1PMs7KUA7bQSeap4nQzQVDCRL5WTik9+1jz
5JHdCR2SHQefzSr9tYSscap/B1gxi1PRDDPP3zBnOMQ2zurZ6LdnbyNd5bFrKxRR
mVwcj+WeLZ6Z4JBjRQr7ETTJjiEAEQEAAYkBHwQYAQgACQUCWXX4SgIbDAAKCRAa
PsF0Z09VH8DBB/4wWsHc24/B8Dd3mTFX0zuawj7+7EcDlM5PBdDNx6Bz2DUOjYqI
AaZlz963yji0SG1cF7nzFnO8RvV4KVv/+jX3PYITURN0Q12Q6lV4Qy+st/pAh2BP
pVhF3BpmuIUIrAoLviYnlDhb9bcioiYb+f2pRRj/+wxcc5Ed5vTsn8TowEUMz+jm
lP321LBSwzbleutxRy+Gc/8XYA0eGBm6Hzo/SWjsbLzHRuILbnS7alSKClTMQsq5
a4jp8QO+/icqW3OqulIdKszQQHWio9a+q1xr2EYZ1U12Ihb4bSXZjOfRTC+wiIlj
FgAU5kIsJbLCMz+eQsFNgkvS3XYLxrsvJsGc
=6muK
-----END PGP PUBLIC KEY BLOCK-----
要对公钥做备份等,则使用对应的 --import
命令,方法类似,不再赘述。
与 keyserver 交互
生成的公钥可提交到公共的 keyserver 上,这样在自己的社交账号的个人资料中只需写上 Key ID,想要给你发送 PGP 加密邮件的人就可通过 Key ID 检索到你的公钥,再使用你的公钥将邮件内容加密后发送给你了。
将公钥发送到 keyserver 使用如下命令:
$ gpg --send-keys 674F551F
gpg: sending key 1A3EC174674F551F to hkp://keys.gnupg.net
可以看到,GnuPG 默认是将公钥上传到 keys.gnupg.net。上传成功后,通常第二天就可以自动同步到其它 keyserver 了。
而当你知道某位好友的 Key ID、Email 地址或姓名等信息,可以使用 --search-keys
命令搜索对应的公钥:
$ gpg --search-keys "Philip Ye"
gpg: data source: http://cryptonomicon.mit.edu:11371
(1) Philip Ye <[email protected]>
2048 bit RSA key E349E491E8513A14, created: 2017-07-24
(2) Philip Ye <[email protected]>
2048 bit RSA key 1A3EC174674F551F, created: 2017-07-24
Keys 1-2 of 2 for "Philip Ye". Enter number(s), N)ext, or Q)uit >
再根据提示选择导入某个公钥即可。
将公钥废除
假如某个公钥被黑或不再使用,需要将其废除,首先需要生成废除操作所需要的凭证,证明对应公钥确实归你所有,你有权对其废除:
$ gpg --gen-revoke 674F551F > revoke-674F551F.asc
再根据提示操作即可,生成的 revoke-674F551F.asc
同样需要妥善保管。当需要做废除操作时,先将上述废除凭证导入:
$ gpg --import revoke-674F551F.asc
gpg: key 1A3EC174674F551F: "Philip Ye <[email protected]>" 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: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
上述导入操作将把本地的公钥废除。要将 keyserver 上的公钥也一并废除,需要再次上传:
$ gpg --send-keys 674F551F
对公钥签名
根据前文介绍的 Web of Trust 相关方法验证了某个公钥的真实性之后,就可以使用对方提供的指纹信息下载公钥并对其签名。以 Micah Lee 的公钥为例,先从 keyserver 下载他的公钥:
$ gpg --recv-keys CD994F73
gpg: key 403C2657CD994F73: public key "Micah Lee <[email protected]>" imported
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: Total number processed: 1
gpg: imported: 1
再对他的公钥签名:
$ gpg --sign-key CD994F73
pub rsa4096/403C2657CD994F73
created: 2015-08-14 expires: 2018-06-19 usage: SC
trust: unknown validity: unknown
sub rsa4096/78B2EEB65D5F1356
created: 2015-08-14 expires: 2018-06-19 usage: E
[ unknown] (1). Micah Lee <[email protected]>
[ unknown] (2) Micah Lee <[email protected]>
[ unknown] (3) Micah Lee <[email protected]>
[ unknown] (4) Micah Lee <[email protected]>
[ unknown] (5) Micah Lee <[email protected]>
Really sign all text user IDs? (y/N) y
pub rsa4096/403C2657CD994F73
created: 2015-08-14 expires: 2018-06-19 usage: SC
trust: unknown validity: unknown
Primary key fingerprint: 927F 419D 7EC8 2C2F 149C 1BD1 403C 2657 CD99 4F73
Micah Lee <[email protected]>
Micah Lee <[email protected]>
Micah Lee <[email protected]>
Micah Lee <[email protected]>
Micah Lee <[email protected]>
This key is due to expire on 2018-06-19.
Are you sure that you want to sign this key with your
key "Philip Ye <[email protected]>" (1A3EC174674F551F)
Really sign? (y/N) y
最后将签过名的公钥重新上传到 keyserver:
$ gpg --send-keys CD994F73
gpg: sending key 403C2657CD994F73 to hkp://keys.gnupg.net
文档的加密与解密
文档的加密使用 --encrypt
命令并配合 --recipient
选项指定加密文档的接收者(也就是使用谁的公钥加密,当然也可以使用自己的公钥,--recipient
可缩写为 -r
),还可在加密的同时,使用自己的私钥对文档进行签名,表明此文档确实来自于你:
$ cat hello.txt
Hello, I am Philip Ye.
$ gpg --armor --sign --encrypt --recipient "Philip Ye" hello.txt
输入 passphrase 之后生成同名的 hello.txt.asc
即是签名并加密的文档了。上述命令可简写如下,且 -r
选项可以使用任意多次,即指定任意多个接收者:
$ gpg -ase -r "Philip Ye" -r "Linda Ye" -r "Yoona Yuan" -r "Frank Luo"
文档的解密则使用对应的 --decrypt
或 -d
命令(文章开头《Citizenfour》的电影截图就是这个命令):
$ gpg -d hello.txt.asc
gpg: encrypted with 2048-bit RSA key, ID B5D4E38A25575D82, created 2017-07-24
"Philip Ye <[email protected]>"
Hello, I am Philip Ye.
gpg: Signature made Wednesday, August 02, 2017 PM10:54:30 HKT
gpg: using RSA key 1A3EC174674F551F
gpg: Good signature from "Philip Ye <[email protected]>" [ultimate]
解密后的文档内容在中间部分输出。
文档的签名与认证
文档的认证以 Linux 内核源码包为例,参考 Linux kernel releases PGP signatures。
先下载源码包和对应的签名文件:
wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.12.4.tar.xz
wget https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-4.12.4.tar.sign
再将源码包解压(因为 .sign
文件是对 .tar
文件的签名而不是 .tar.xz
):
unxz linux-4.12.4.tar.xz
验证源码包签名,第一次验证可能出现 No public key 的错误:
$ gpg --verify linux-4.12.4.tar.sign
gpg: assuming signed data in 'linux-4.12.4.tar'
gpg: Signature made Fri 28 Jul 2017 06:10:56 AM CST using RSA key ID 38DBBDC86092693E
gpg: Can't check signature: No public key
需要先将提示的公钥 38DBBDC86092693E 从 keyserver 下载到本地:
$ gpg --recv-keys 38DBBDC86092693E
gpg: key 38DBBDC86092693E: public key "Greg Kroah-Hartman (Linux kernel stable release signing key) <[email protected]>" imported
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: Total number processed: 1
gpg: imported: 1
再次验证源码包的签名:
$ gpg --verify linux-4.12.4.tar.sign
gpg: assuming signed data in 'linux-4.12.4.tar'
gpg: Signature made Fri 28 Jul 2017 06:10:56 AM CST using RSA key ID 38DBBDC86092693E
gpg: Good signature from "Greg Kroah-Hartman (Linux kernel stable release signing key) <[email protected]>"
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: 647F 2865 4894 E3BD 4571 99BE 38DB BDC8 6092 693E
提示 Good signature 说明签名有效,但有一个 WARNING,是因为对应的公钥本身没有在你的信任网络中,同样对其公钥签名后可解决,不再赘述。
上述先下载公钥再验证文档的操作,可以使用 --auto-key-retrieve
合并为一个操作:
$ gpg --auto-key-retrieve --verify linux-4.12.4.tar.sign
gpg: assuming signed data in 'linux-4.12.4.tar'
gpg: Signature made Fri 28 Jul 2017 06:10:56 AM CST using RSA key ID 38DBBDC86092693E
gpg: key 38DBBDC86092693E: public key "Greg Kroah-Hartman (Linux kernel stable release signing key) <[email protected]>" imported
gpg: marginals needed: 3 completes needed: 1 trust model: pgp
gpg: depth: 0 valid: 1 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 1u
gpg: Total number processed: 1
gpg: imported: 1
gpg: Good signature from "Greg Kroah-Hartman (Linux kernel stable release signing key) <[email protected]>"
gpg: WARNING: This key is not certified with a trusted signature!
gpg: There is no indication that the signature belongs to the owner.
Primary key fingerprint: 647F 2865 4894 E3BD 4571 99BE 38DB BDC8 6092 693E
而生成文档签名可使用分离模式(即原文档和签名文件分离,签名文件仅包含签名本身),使用 --detach-sign
命令实现,同样依照惯例将签名以 ASCII 方式输出到文件中:
gpg --armor --output linux.4.12.4.tar.sign --detach-sign linux-4.12.4.tar
Gmail + Mailvelope
各种邮件客户端都有对应的插件等实现 PGP 功能的集成。在 PC 上我习惯使用 Gmail 网页版,配合 Chrome 扩展 Mailvelope(同时也有 Firefox 扩展),收发加密邮件、密钥搜索与管理等,都非常方便,推荐试用。
结尾
最后,分享电影《Citizenfour》中 Glenn Greenwald 对 Edward Snowden 的一段采访作为结尾。
Glenn Greenwald: If your self interest is to live in a world, much, there is a maximum privacy, doing something that could put you into prison in which your privacy is completely destroyed, sort of antithesis of that. How do you reach the point where that is worthwhile calculation for you?
Edward Snowden: I remember what the Internet was like before I was being watched. And there’s never been anything in the history of man is like it. I mean, you could, again, you can have children from one part of the world having an equal discussion. Where, you know, they were sort of granted, the same respect for their ideas in conversation with experts in the field from another part of the world, on any topic, anywhere, anytime, all of the time. And it was, it was free and unrestrained. That we’ve seen the chilling of that, the calling of that, the change of that model towards something which people self-police their own views. And they literally make jokes about ending up on the list if they donate to a political cause or if they say something in the discussion. And, and it’s become an expectation that they’re being watched. Many people love to talk have mentioned that they’re careful about what they type into search engines because they know that it’s being recorded. The limits, the boundaries of their intellectual exploration. And, I’m, I am more willing to risk imprisonment or any other negative outcome personally than I am willing to risk the curtailment of my intectual freedom. And that of those around me who might care for equally as I do for myself. And again, that’s not to say that I’m self sacrificing, because it gives me, I feel good in my human experience to know that I can contribute to the good of others.
以上。