PGP 简介

2016-10-21 linux security command

GnuPGP 是一个基于公钥加密体系的加密软件,功能强大,有很快的速度,而且源码是免费的。

这里简单介绍下 PGP 相关内容。

简介

Pretty Good Privacy, PGP 加密由一系列散列、数据压缩、对称密钥加密,以及公钥加密的算法组合而成,遵守 OpenPGP 数据加密标准 RFC 4880 ,每个步骤支持几种算法,可以选择一个使用。

注意 PGP 是一个商用软件,对应的开源软件为 GnuPGP ,两者都遵守 OpenPGP 标准。

pgp diagram

通常,使用如上的加密和解密过程大致为。

  1. 用户产生需要加密的信息。
  2. OpenPGP 发送方产生一串随机数,作为对称加密密钥,这一随机数只对该信息或该会话有效。
  3. 使用接受者的公钥加密上述的随机数 (密钥),放置到需要发送消息的开头。
  4. 通过上述产生的随机数加密需要加密的信息,通常会先对信息进行压缩。
  5. 接收方则会先通过私钥解密随机数,然后解密信息。

当然,也可以先产生一个数字签名,并将数字签名添加到信息中,然后同样会通过随机串加密。在 OpenPGP 中也可以对信息进行压缩,通常压缩是在获得数字签名后且在压缩之前。

The Public-Key Cryptography Standards(PKCS) 是由美国 RSA 数据安全公司及其合作伙伴制定的一组公钥密码学标准,包括证书申请、证书更新、证书作废表发布、扩展证书内容以及数字签名、数字信封的格式等方面的一系列相关协议。

信任模型

PGP 的好处在于它的信任模型 Web Trust Model,一种非常贴近生活中人与人的信任模型。在信息世界里面,人与人之间的信任纽带有两种方式被建立。

集中式

如 CA 结构,大家都信任 CA ,于是 Peter 是不是 Peter ,是根据 CA 是否认识这个 Peter ,我们现在通常所说的的数字证书认证,就是这种集中认证的模式。

分布式

假设没有CA,只信任自己和朋友,每个人都是在一个朋友圈子中,朋友圈子以外的人,则需要一个中间人作为介绍人,这就是现实的生活。

我有一个朋友叫 Andy,且我有另一个朋友叫 Bob,如果 Bob 想认识 Andy,于是他想通过我来认识 Andy,方法很简单,鉴于我认识他们两者,通过我为 Bob 的公钥签名,于是 Andy 可以直接向 Bob 证明他是我的朋友。

PGP 采用的后者是这种方式,每个人都以自己为中心,然后是朋友圈,然后是朋友的朋友。

GnuPGP

公钥算法的关键点在于如何传播公钥,如果有恶意用户传递了一把虚假公钥,此时,如果别人不知就里,用这把公钥加密信息,持有该虚假钥匙的侵入者就可以解密而读到信息。而如果侵入者再将解密的讯息加以修改,并以真正的公开钥匙加密,然后传送出去,这种进攻无法被发现。

PGP 的解决方法是对公钥进行签名,也即生成指纹 (fingerprint),只有公钥会有指纹。每个公钥均绑定唯一的用户身份 (包括用户名、注释、邮箱),当然一个人的公钥可以由别人来签名,签名 (指纹) 是用来测试该公钥是否是由其声明的用户发出的

至于有多信任这些签名,完全取决于 GPG 用户,当你信任给这把钥匙签名的人时,你认为这把钥匙是可信的,并确信这把钥匙确实属于拥有相应用户身份的人。只有当你信任签名者的公开钥匙时,你才能信任这个签名。要想绝对确信一把钥匙是正确和真实的,你就得在给予绝对信任之前,通过可靠渠道比较钥匙的 “指纹”。

使用

~/.gnupg 目录下,保存了一些必须的文件,其中密钥以加密形式存储在磁盘上,包括了两个文件,一个存储公钥 (pubring.gpg)、一个存储私钥 (secring.gpg)。

GPG 会通过 username、comment、email 生成一个 UID(hash value),在使用时可以用其中的一种方式。

1. 生成密钥对 (公钥+私钥)

生成时需要选择 A) 加密算法、密钥长度 (长度越长解密时间越长,不过也会更安全)、密钥的有效时间;B) 区分该密钥对的用户名、e-mail、注释 (这些信息是可以修改的);C) 一个保护该私钥的验证密码。

$ gpg --gen-key
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)
Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048)
Requested keysize is 2048 bits
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: foobar
Email address: foobar@ooops.com
Comment: just for test
You selected this USER-ID:
    "foobar (just for test) <foobar@ooops.com>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
You need a Passphrase to protect your secret key.         # 输入密码

gpg: key FCC7A473 marked as ultimately trusted            # 用户的hash值,可以用来替代userid
public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   1  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 1u
pub   2048R/FCC7A473 2016-07-06
      Key fingerprint = ACB2 B707 B2D8 A548 55A8  5E24 A5CC BA5C FCC7 A473   # 可以用来电话验证^_^
uid                  foobar (just for test) <foobar@ooops.com>               # 三元素组成userid
sub   2048R/3B44DA00 2016-07-06

生成的公钥 (pubring.gpg)、私钥 (secring.gpg) 保存在 ~/.gnupg/ 目录下。

2. 撤销公钥

当密钥对生成之后,应该立即做一个公钥回收证书,当忘记了私钥的口令或者私钥丢失或被盗窃,可以发布这个证书来声明以前的公钥不再有效。

$ gpg --output revoke.asc --gen-revoke "user-name/e-mail/user-id"           # 生成回收证书,以备不时之需
$ gpg --import revoke.asc                              # 导入回收证书
$ gpg --keyserver keys.gnugp.net --send "8D253E8A"     # 发送回收证书到服务器,声明原GPG Key作废

当然如果要这么做,一定要先有私钥,否则任何人都能取消你的公钥。

3. 传播公钥

导出公钥之后,就可以通过一些可靠的方式传播公钥。可以将公钥通过 e-mail 发送给好友,或者上传到指定的服务器(如果不指定使用默认的 keys.gnupg.net ,其它的还有 keyserver.ubuntu.com

$ gpg --keyserver keys.gnugp.net --send-keys "8D253E8A"                     # 上传到服务器
$ gpg --fingerprint                                                         # 打印公钥指纹
$ gpg --keyserver keys.gnugp.net --search-keys "user-name/e-mail/user-all"  # 在服务器上查找

由于公钥服务器没有检查机制,任何人都可以用你的名义上传公钥,所以没有办法保证服务器上的公钥的可靠性。通常,可以在网站上公布一个公钥指纹,让其他人核对下载到的公钥是否为真。

4. 查看密钥

$ gpg --list-keys                       # 输出现有公钥,包括UID、公私钥的HASH值,等同于gpg -k
$ gpg --list-sigs                       # 同时显示签名,实际也就是上述的HASH值
$ gpg --fingerprint                     # 输出公钥的指纹
$ gpg --list-secret-keys                # 列出私钥,PS. 列出私钥的指纹和签名根本就没用,等同于gpg -K

通过 gpg -k 列出所有公钥,其中第一行表示保存的文件名 (pubring.gpg);第二行为公钥的特征 (加密方式:如 2048R 表示 2048-bit RSAuser-idhash 值,生成时间);第三行表示用户 ID ;第四行表示私钥特征。

5. 导出公钥

将指定用户的公钥以 ASCII 的格式导出。

$ gpg --armor --export "user-name/e-mail/user-id"                           # 导出ASCII公钥到终端
$ gpg --armor --output public-key.gpg --export "user-name/e-mail/user-id"   # 导出ASCII公钥到文件,或者-o
$ gpg --armor --export-secret-keys                                          # 导出私钥
$ gpg --refresh-keys                                                        # 从服务器定期更新公钥

其中 --armor 表示将内容转换成可见的 ASCII 码输出,否则是二进制不方便阅读。

6. 导入公钥

除了生成自己的密钥,还需要别人的公钥,用于生成加密信息或者验证数字签名等。为此,就需要将他人的公钥或者你的其他密钥输入系统,这时可以使用 import 参数。

$ gpg --import public-key.gpg                                                       # 导入ASCII公钥到文件
$ gpg --keyserver hkp://subkeys.pgp.net --search-keys "user-name/e-mail/user-all"   # 从服务器查看然后选择
$ gpg --keyserver subkeys.gpg.net --recv-keys 8D253E8A                              # 直接导入

由于任何人都可以导入公钥,我们无法保证服务器上的公钥是否可靠,下载后还需要用其他机制验证,如数字签字。

7. 删除密钥

我们可以通过如下命令删除密钥。

$ gpg --delete-keys "user-name/e-mail/user-id"                    # 从公钥钥匙环里删除密钥
$ gpg --delete-secret-keys "user-name/e-mail/user-id"             # 从私钥钥匙环里删除密钥
$ gpg --delete-secret-and-public-key "user-name/e-mail/user-id"   # 同时删除公钥私钥

8. 修改密钥

用此命令你可以修改钥匙的失效日期,加进一个指纹,对钥匙签名等等。 尽管显得太清楚而不用提,这里还是要说,要做以上事情你得用你的通行句。 敲入通行句后,你会见到命令行。

$ gpg --edit-key "user-name/e-mail/user-id"

9. 加密/解密

在安装和按照需要设置好所有事以后,我们就可以开始加密和解密了。

$ gpg --recipient "8D253E8A" --output demo.en.txt --encrypt demo.txt   # 用指定用户公钥加密

$ gpg --output demo.de.txt --decrypt demo.en.txt                       # 解密,可以不指定用户名
$ gpg demo.en.txt                                                      # 解密,最简单方式

--recipient 参数指定接收者的公钥,--output 指定加密后的文件名,--encrypt 指定源文件。

10. 签名

有时只需要对文件签名,表示这个文件确实是我本人发出的。

$ gpg --sign demo.txt                   # 生成二进制签名demo.txt.gpg,同时包含文本内容
$ gpg --clearsign demo.txt              # 生成ASCII的签名文件demo.txt.asc,同时包含文本内容
$ gpg --detach-sign demo.txt            # 只生成二进制签名demo.txt.sig
$ gpg --armor --detach-sign demo.txt    # 只生成ASCII签名demo.txt.asc
$ gpg --verify demo.txt.asc demo.txt    # 验证解密的文件与签名是否相符

$ gpg --armor --detach-sign -u UID demo.txt    # 可以指定用户

11. 签名+加密

此时签名和加密同时保存在同一个文件中。

$ gpg --local-user "user-name/e-mail/user-id" --recipient "8D253E8A" --armor --sign --encrypt demo.txt
$ gpg demo.txt.asc                      # 解密

公钥的管理

正如前言所提到的,这个系统有一个最大的薄弱点,那就是公钥的真实性问题。可以通过直接向公钥的提供者当面或者电话验证指纹,从而确定公钥的真实性问题。

当确定了公钥的真实性之后可以对公钥签名,表示你绝对确信这把钥匙是真实的,有了这个保证,用户就可以开始放心用这把钥匙加密了。

要对一把钥匙签名,用 gpg --edit-key UID ,然后用 sign 命令。

GPG 会根据现有的签名和对 “主人的信任度” 来决定钥匙的真实性,包括了四个值。

1 = 我不知道
2 = 我不信任(这把钥匙)
3 = 我勉强信任
4 = 我完全信任

对于用户不信任的签名,就会将废弃这个签名不用。注意这些信任信息不是存在储存钥匙的文件里,而是存在另一个文件里。

参考

关于如何使用 subkey Creating a new GPG key with subkeys 一个不错的介绍,可以参考 本地文档;另一篇参考的是最佳实践,可以参考 OpenPGP Best Practices ,或者 本地文档

一个官方的中文文档 GnuPG 袖珍 HOWTO ,或者英文版 The GNU Privacy Handbook