# 实用gpg教程




## gpg是什么？
gpg全称GNU Privacy Guard，也常缩写为GnuPG，简单来说是一个对通信内容进行加密的工具。
gpg支持多种加密算法，常用的组合是RSA+AES的非对称+对称加密的混合加密。

## 为什么需要gpg？  

### 为什么需要加密？

- 由于众所周知的原因,国内常用的通信软件的任何聊天记录都有可能出于某种原因被**明文**公开。

- QQ/微信的在线文件传输并未加密，旁听者或中间人可以轻易获取文件内容

这意味着，使用QQ/微信共享的身份证号/照片，密码，特殊上网方式等敏感信息随时存在被泄露的风险。

### 为什么需要使用非对称加密？

预共享密钥(PSK)（如事先约定的暗号，对方生日等）当然可以用于加密，并且对于**一般**强度的监听具有极高的效费比。
但是对于更严苛的环境（与接收者不熟、监听者可以很方便地得知PSK等），更加安全的加密方式才能保证消息的保密传输。

非对称加密将解密所需的密钥(本质可以看作是一串极长的密码)与加密所需的密钥分开：发送者使用接收者的**公钥**加密消息并发送，接收者使用公钥对应的**私钥**解密消息。现有计算机的计算能力无法由公钥推导出私钥，因此公钥可以随意公开以便他人加密并发送消息给接收者；而私钥由接收者保管，永不上传。公私钥分离使得监听者永远无法解密用私钥，自然也就无法解密消息。

### 不用gpg可以吗？

gpg作为开源世界常用的消息加密工具，提供方便的非对称加密+对称加密手段，且拥有成熟的生态。

- 多样传递方式：由非对称加密的安全性背书，加密后的文本可以通过**任意不安全的信道**发送，如微信，QQ，短信，~~大字报~~，而无需烦恼如何使用特殊的上网技巧（对比telegram,whatsapp等安全性较高但国内无法正常访问的通信软件）

- 开源工具：20年的“同行评审”，确保无后门

- 生态：世界上有许多gpg服务器供用户发布公钥，可以减少公钥传输过程中 中间人攻击的攻击面（后文详述）

- 信任网络（web of trust，后文详述）

## 使用方法

基本名词解释：

- 公钥：用于加密发送给接收者消息的一串数字，分享给发送者。

- 私钥：用于解密消息的一串数字，接收者自行保管，不对外分享。

- 加密：发送者使用接收者的公钥对消息进行变换。

- 解密：接收者使用自己的私钥对消息进行加密过程的逆变换。只有公钥对应的 私钥 可以解开加密信息。

- 签名：计算消息的checksum，使用发送者的私钥对checksum进行变换，并将变换后的checksum（即签名（名词））附在消息上。消息的任意微小变动都将导致checksum产生巨大变化，因此相同的checksum<=>相同的正文。

- 验证签名：使用发送者的公钥对签名进行逆变换获取附加在消息上的checksum。只有签名用私钥对应的 公钥 能完成这个逆变换过程，从而确认发送者身份；计算消息正文的checksum并检查计算结果是否与签名的checksum相符以确定消息未被更改。

使用GPG加密并传输的一般流程是：

0. 收发双方创建自己的公私钥对，并妥善保管私钥，**不传送私钥**给其他人

1. 发件人获取收件人的**公钥**；**发件人**使用**收件人**的**公钥**加密消息并发送给收件人；

2. **收件人**收到消息，使用自己的**私钥**解密消息

3. （可选）**收件人**使用**发件人的公钥**验证消息的发送者是否真的是发件人本人




以下介绍GUI和命令行的具体操作方法。


### **GUI**
*** 

QQ/微信显然没有预留接口供加解密使用……个人认为GUI对于 复制加密过的文本到聊天窗口 这样的操作更加友好

操作以gpg4win安装时自带的软件GPA(别吐槽这个名字)为例。

#### 1. 生成密钥对

打开GPA，选择Windows -> Keyring Manager打开gpg的“钥匙圈”管理器

在Key Manager中选中Keys -> New Key打开，填写名字，email和备注。如果希望密钥对于一定日期后自动过期，可勾选expires选项并选择日期。

![](create_key.png)

***强烈建议*** 为私钥设置密码！

#### 2. 发送公钥

成功创建密钥对后对创建好的条目右键->Export Keys，将**公钥**保存至任意位置。该公钥可以作为附件传送给潜在的发件人，或者直接复制其文本内容发送给他人。

![](export_keys.png)

**只有拥有你的公钥的人才可以向你发送（你能解密的）加密信息。**

也可以右键->Send Keys把公钥发送至**公用**公钥服务器 keys.gnupg.net，这样他人可以直接凭KeyID(红圈处)从公用keyserver下载你的公钥。

**虽然传送公钥不会导致密文泄露，但是出于保护个人信息（姓名与邮箱的组合）的目的，上传公钥至服务器前请慎重考虑。**

#### 3. 导入公钥

点击Key Manager的import键，选中对方发送的公钥文件即可导入对方的公钥。

如果对方已将公钥上传至公用服务器，也可点击菜单栏里的Server -> Retrieve keys，在弹出的对话框中填入对方的fingerprint，姓名或邮件即可导入对方的公钥。**请务必仔细核实下载到的公钥的各项信息，尤其是Fingerprint！**

核实完成后右键点选Sign Keys。在弹出的对话框中再次核对信息后输入自己私钥的密码来为对方的公钥签名。签名后的公钥才会被当做可信的公钥。

(2019.06.02注：GPA 0.10.0版本存在无法从服务器下载公钥的bug，可以使用gpg4win自带的另一个GUI工具Kleopatra或下文描述的命令行方式下载公钥)

#### 4. 加密消息

关闭Key Manager返回GPA的Clipboard窗口。在窗口中输入任意消息，点击Encrypt。

![](encrypt.png)

在上部列表中选择收件人（本例中为Remilia），按OK即替换明文为加密后的文本。

![](encrypt_done.png)

如欲对加密消息进行签名（**推荐**，这样收件人可以确认消息的发送者的确是你），请勾选Sign并在下方列表中选择**自己**（本例中sakuya为发件人）。 收件人和签名身份均可按住Ctrl键多选；**每个**收件人都可以**独立**地解开加密后的信息。

复制这段文本到任意通讯软件的聊天框即可发送加密后的消息。（记得关掉QQ的斜杠表情转换。。。）

**注意：加密后的文本你本人无法解开，长文本请做好备份**

#### 5. 解密消息

清空Clipboard中的任何内容，复制加密后的消息至Clipboard内，点击Decrypt。GPA会自动匹配你所拥有的私钥用于解密。 如果你拥有能够解密这段文本的私钥，GPA会自动用其解密消息，或提示你输入私钥的密码。（gpg-agent会临时记住解密后的私钥，一段时间内使用该私钥解密消息无需输入密码）

![](decrypt.png)

解密后的消息会替换Clipboard窗口中的加密文本。


#### 6. 加/解密文件

对于不便复制粘贴的内容（如图片，音乐等二进制文件），GPG也提供了加密方法。

加解密文件的方法与加解密文本的方法大同小异；点击Files按钮打开文件管理器，打开欲加/解密后的文件，点击Encrypt/Decrypt即可。加密后的文件名默认为`原文件名.原扩展名.gpg`

唯一的区别在于，Encrypt界面多出了Armor的选项。在无法可靠发送附件的环境（如微信传文件），勾选该选项可以将加密后的文件转换为可复制粘贴的纯文本格式，后缀名也会变为`.asc`。

~~（当然加密后长度会超过单条消息长度上限的大文件肯定不能这么干）~~

左：未勾选Armor，是个“乱码”，无法通过QQ/微信发送；右：Armor，只含可见ASCII字符

![](armor.png)

#### 7. 签名/验证签名 

有时候我们只需证明文件在传输过程中未被更改而无需加密文件；有时候我们希望分发文件给许多未知的收件人（即无法获得收件人的公钥）。这种情况下我们只需（只能）对文件进行签名而无需加密。

收件人检验文件签名的操作无需收件人的公/私钥。

于GPA File Manager打开欲签名的文件，点击sign并选择你自己的名字来以你的名义对该文件签名。

(以下以`004.jpg`为例进行签名)

![](sign.png)


Detached signature选项会创建独立的签名文件（`004.jpg.asc`）（推荐，这样没有gpg的收件人也可以打开文件）

Sign and compress选项会将签名与被签名文件打包成一个文件(`004.jpg.gpg`)

对于GPA，验证签名时需把签名和被签名文件(`004.jpg`)放置进同一目录，确保签名的文件名是`004.jpg.asc`或`004.jpg.sig`（命令行无此限制），在GPA File Manager中打开.asc或.sig文档，点击Verify即验证签名。

弹出的对话框中会提示签名校验状态：

- `Good Signature`：意味着被签名文件未被更改，且gpg确定签名者的公钥是发件人本人的公钥

- `Bad Signature`：意味着被签名文件被更改，请联系发件人重发

- `Key NOT valid`：被签名文件与签名校验相符，但是gpg不确定签名用公钥是否为发件人本人（例：在上述导入公钥的过程中没有完成签名操作）。请可靠地与发件人核对公钥Fingerprint。

- `Unknown Key` 未导入发件人公钥，请下载并导入发件人的公钥


### **命令行**
***

~~Google/bing is your friend。。。Read the friendly manual。。。man page讲得比我好多了。。。~~

国际惯例， 命令里中括号`[]`括起的部分为可选选项。
每一步的目的可参考GUI部分，就不复制粘贴了。。。

#### 1. 密钥对生成

（简化的私钥生成）
```
gpg --gen-key
```

（提供定制选项的生成）
```
gpg --full-generate-key
```

~~提示你输入Real Name的时候别傻乎乎地真填真实姓名啊~~


#### 2. 导出/发送公钥

```
gpg --armor --output mypubkey.gpg --export your.name@yourdomain.com
```

`mypubkey.gpg`为生成的公钥文件；`your.name@yourdomain.com`是你创建密钥时填写的邮箱。

##### 2.1 上传至公共服务器：

运行

```
gpg --list-secret-keys
```
列举你拥有的所有公/私钥对。

示例输出：

```
sec   rsa2048 2019-06-02 [SC]
      0C4A9E9CE059692F4D40FC962077CA3472D6B779
uid           [ultimate] Remilia (??) <remilia@koumakan.com>
ssb   rsa2048 2019-06-02 [E]
```

找到你想上传的公钥，复制sec下的一行（即key id，一般复制最后八位即可，下文以`72D6B779`为例）
运行

（发送至默认服务器）

```
gpg --send-keys 72D6B779
```

（发送至指定服务器）

```
gpg --keyserver pgp.xxx.edu --send-keys 72D6B779
```


#### 3. 导入公钥

```
gpg --import pubkey.gpg
```

请仔细核对打印出的信息。

##### 3.1 从服务器下载公钥

```
gpg --recv-keys fingerprint_of_key
```
替换`fingerprint_of_key`为收件人提供的公钥；也可通过：

```
gpg --search-key whoareyou@whererufrom.com
```
查找收件人的公钥ID。

下载时也可指定`--keyserver pgp.xxx.edu`从指定服务器搜索/下载。

~~一般要搜好长时间所以还是用fingerprint吧~~

##### 3.2 为下载到的公钥签名
```
gpg [--local-user fingerprint_of_key] --sign-keys fingerprint_of_key 
```
`--local-user`选项指定使用哪个私钥来签名。不指定的话会使用默认的私钥(`~/.gnupg/gpg.conf中指定`)签名。

#### 4. 加密文本/文件

从stdin读取文本并打印至stdout：

文本内容结束后按回车，Ctrl-Z(非windows平台Ctrl-D)，回车以结束文本输入。

```
gpg [-a] [-s] --encrypt -r recipient1@example.com [-r recipient2@example.com]
```

解释：

`-a` 开关启用前文提到的Armor，即输出纯文本；不想加密至Armor可删除该开关

`-s` 对消息签名

`-r` 指定(多个)收件人


```
gpg [-a] [-s] --encrypt -o output.file -r recipient1@example.com -r recipient2@example.com file_to_encrypt.file
```

`-o` 选项指定加密后文件的保存位置。传入短横dash(`-`)作为该选项的值将打印加密后的文件至stdout； 不指定该选项将输出文件为`file_to_encrypt.file.gpg`

#### 5. 解密文本/文件

从stdin读取加密文本并打印至stdout：
```
gpg --decrypt
```

```
gpg [-o decrypted_file.file] --decrypt  encrypted_file.file.gpg
```

不指定`-o`将打印解密后的文件至stdout。

#### 6. 签名/验证签名

```
gpg [-o signature_file] --sign input_file.extension
```
-o 选项指定输出签名的文件名

```
gpg --verify file.extension.sig [other.name.file]
```

不指定待校验文件(`other.name.file`)的话会默认校验`file.extension`，如果`file.extension`不存在会返回`No data`

校验结果分三种：

-  `Good signature from XXXX`, 后无任何警告信息： 签名者公钥被信任，且文件未被更改

-  `Good signature from XXXX`, 后接`WARNING: This key is not certified with a trusted signature!`: 文件未被更改，但是签名者的公钥不被本机信任。请联系发件人核对公钥信息。

-  `BAD signature from XXXX`：文件被更改，请联系发件人重传

- `Can't check signature: No public key`：未导入发件人公钥，请下载发件人公钥并导入gpg


## 增强安全性

gpg当然不是绝对安全的，也会受到各种攻击的影响。下文介绍常见的用于攻击gpg的方式。


### 中间人攻击(Man-in-the-middle attack, MITM)

此攻击应用范围广，在QQ/微信/短信等环境中易于操作，因此需**重点防范**。

假设收发双方分别叫Alice和Bob，两人开始交换公钥(P_A,P_B)。
这时Charlie拦截了两人之间的全部流量，把P_A和P_B替换成自己的公钥P_C(姓名和邮箱填写为Alice和Bob的信息)发送给了Alice和Bob。
这样，Alice和Bob都以为P_C才是对方的公钥并用P_C加密了消息。

Charlie继续截取所有的密文，用自己的私钥S_C解密，读到了消息明文，再用之前截获的P_A和P_B重新加密，发送给Alice和Bob。Alice和Bob依然可以使用自己的私钥解开(Charlie重新加密过的)消息，因此误以为Charlie才是对方。

解决方案：

- 面对面交换公钥——除非Charlie会易容术

- (仅针对QQ/微信等即时通信软件): 按照（明文）约好的特殊书写方式，手写公钥指纹，拍照发送给对方核对。攻击者**不大可能**短时间内伪造出一张符合要求的图片来。

- 从支持加密的keyserver(hkps://打头)下载公钥：中间人者**不大可能**拥有破译TLS流量的能力（但中间人可以通过**网络降级攻击**强迫用户使用未加密连接）

- 信任网络：让可信任的第三者在对方的公钥上签名（后文详述）——除非Charlie能骗到所有你信任的人，否则假冒的Bob/Alice公钥会被立刻发现


### Sign-then-encrypt / Encrypt-then-sign的缺陷

此攻击法应用范围较窄，在QQ/微信等带额外身份校验的环境中不易于操作，仅作了解

#### Sign-then-encrypt

缺点在于收件人绝对知道明文作者是谁，却不知道是谁加密并传送给他的。举个例子：

1. Alice想要约Bob晚上在公园见面，于是她写下了“晚上9点公园见”，用私钥签名，用Bob的公钥加密后发送给了Bob。

2. Bob收到消息，决定整一整Charlie: 
Bob使用Charlie的公钥加密了Alice的消息和签名，打印在纸上，丢进了Charlie的宿舍。

3. Charlie收到了消息，使用自己的私钥解密，得到了“今晚9点公园见”的消息和Alice的签名。因为消息有Alice的签名，Charlie信以为真。

4. 现在Bob只要发一封消息给Alice重新约个地点，Charlie就可以去喂蚊子和鸽子了。

#### Encrypt-then-sign

任何人都可以冒充消息作者：

1. Alice加密了消息“Bob欠我15元不还”后签了名发送给David。

2. Charlie拦截了这段消息，移除了Alice的签名，并用自己的私钥签了名发给David。

3. 行侠仗义的David去揍了Charlie一顿，拿到了15元。因为消息上有Charlie的签名，所以David把钱交给了Charlie， BOOM

实践中通常使用Sign-encrypt-sign的方法来同时确保发件人身份和加密人身份，或者简单粗暴地，在消息明文中填写收件人信息后sign-then-encrypt（Charlie也就能知道Alice其实是约Bob去公园了）。



## 扩展：信任网络 (web of trust)

两名沙雕网友相隔万里、从未见过、网速巨慢（也就有可能被伪造一张手写公钥指纹出来），要怎么才能确认交换到的公钥就是对方的呢？

同样基于公/私钥体系、网页浏览常用的HTTPS体系使用权威证书(Certificate Authority, CA)审核并签名的方式来确保服务器发给用户的公钥未被第三方更改（第三方攻击者使用的证书无法获得CA的签名，也就不会被浏览器信任并会触发浏览器的警告）。

GPG，PGP及其他OpenPGP兼容加密系统则采用了去中心化的公钥签名体系：

单个用户在**可靠地**获取了**可靠的用户**的公钥后，可以对这个用户的公钥签名、上传至公钥服务器并指示GPG**信任该用户签名过的公钥**。 这样的信任可以不断传递，也就是：
```
我相信我信任的公钥签名过的公钥签名过的公钥。。。。。。
```
打个比方，A给B签过名，B给C签过，C给D签过……Y给Z签过。
这样，当A拿到**真正的**Z的公钥的时候，GPG会顺着信任的链条寻找下去并认证Z的证书是可信的。
(攻击者署名为Z的证书显然没法拿到签名，因此攻击者加密/签名的消息会被A标记为不可信消息)

每个用户既是每条信任链条的起点，也会是其他用户信任链条中的中间节点，由此构成一张信任的网络，是名 "web of trust"。

并且根据六度空间理论，世界上任何互不相识的两人只需要很少的中间人就能够建立起联系，因此并不需要很多个中间节点就可以完成公钥的验证，验证速度也就很快。在现有GPG web of trust中任意互相信任的两名用户间的距离约为6.2[1]。

如果链条中间某个用户不靠谱（私钥被盗，喜欢酒后签名，……）该怎么办？

当然是选择不信任TA啊（

在GPA的Keyring Manager中，右键点击一个公钥可打开Set Owner Trust选项来为公钥在信任网络中的作用做记号。Owner Trust分为五个等级：

1. Unknown 未知：默认信任等级，实际作用等同于Never

2. Never 从不：不相信这枚公钥（对应的私钥）签名的公钥，除非其他可信的人也为那枚公钥签名过。

3. Marginal 部分可信：无其他高可信度用户，且小于3枚有效Marginal公钥签名过的公钥不会被信任；有其他高可信度用户签名或大于等于3枚有效Marginal公钥签名过的公钥会被信任。

4. Full 完全信任：被有效的、Full等级公钥签名过的公钥会被信任。

5. Ultimate 无条件信任： 即使该公钥因过期等原因无效化了，也信任**此公钥**及其签名过的公钥。

需要注意的是，Ultimate之外的Owner Trust并不改变该公钥的有效性，只影响对该公钥签名过的公钥的判断。

仅设置Owner Trust为Full并不能使gpg信任该公钥；只有Ultimate签名过（或Ultimate签名过的公钥签名过的）公钥才是有效的。

公钥本身只会因吊销、过期、无人签名等原因失效。

![](owner_trust.png)

### 说了这么多，优点是什么？

与CA体系相比，WoT最重要的当然是去中心化：

- 单个用户失效不影响公钥认证
    - 对比：一个CA的私钥失窃后，所有被该CA签名过的公钥都需要更换CA；全世界的操作系统都需要更新以删除失窃CA的公钥（证书）。例：[DigiNotar私钥失窃](https://en.wikipedia.org/wiki/DigiNotar)
    - ~~对比：沃通（律师函警告）~~

- 收敛速度快：
    - 任意两个强连通分量(Strongly Connected Component)中只要各出一人互相信任即可合并两个SCC，两个SCC中每个成员都互相信任

- 低参与成本
    -  无需花钱购买CA证书（Let's Encrypt: ん？）
        - （安利FSF的免费CA Lets' Encrypt）

### 缺点也是有的

- 新用户加入WoT的速度较慢：
    - 获得已有用户的信任需要较长时间(因为必须可靠地确认身份)
    - ~~身份认证常用活动Key Signing Party对社恐极不友好~~


## 总结

使用gpg可以在不安全的通信环境中（QQ，微信，短信等），加密传输消息并保证消息不被第三方获取。

传递消息时，发送者获取接收者的公钥，认证公钥是否属于接收者，并使用接收者的公钥加密消息。接收者使用自己公钥对应的私钥解密消息获得消息正文。

对消息签名可以确保消息的确来自发送者，且消息在传输过程中未被第三方改动。

## 参考文献

1. Analysis of the strong set in the web of trust, [https://pgp.cs.uu.nl/plot/](https://pgp.cs.uu.nl/plot/)


# （待填坑）

## Kleopatra
