Pretty Good Privacy
GPG 是什么
提 GPG 之前需要提一个软件叫 PGP。PGP 是“Pretty Good Privacy” 的缩写,中文直译为“完美隐私”,名字言简意赅,上来就把软件的用途拍用户脸上。然而 PGP 不是自由软件,所以自由软件基金会决定开发一个替代 PGP 的自由软件,于是有了 GPG(GnuPG)。
GPG 可以提供对信息、文件的签名和验证,或者是加密和解密,主要用于不安全网络上的信息传输。为此 GPG 需要一个密钥环,GPG 使用私钥和公钥分别完成签名和加密,对应地验证和解密由公钥和私钥完成。
主钥、从钥、公钥、私钥
然而 GPG 密钥环并不只有一对公钥和私钥,如果称公钥和其对应的私钥为一个密钥对的话,那么一个 GPG 密钥环可以拥有很多个密钥对,每一个密钥对都由一个钥匙号(key ID)标识,被称为钥匙。其中有一个钥匙拥有签名其他钥匙的功能(可以在密钥环中创建钥匙),这个钥匙被称为主钥,其他的钥匙则被称为从钥。
下面列出了我在使用的一个密钥环,首先是公钥。
1 | $ gpg --list-keys |
然后是私钥。
1 | $ gpg --list-secret-keys |
GPG 列出的每个密钥环第一行一定是主钥,其余的则为从钥,可以看到上面的密钥环中只有一个主钥和一个从钥。每个密钥后面有许多信息描述它的属性,例如 sec rsa4096/B66CC194 2016-04-15 [SC]
代表这是一个主钥的私钥,加密算法为 rsa,长度 4096 位,主钥的钥匙号为 B66CC194
,创建于 2016 年 4 月 15 日,功能为 SC
。
不难看出一个 GPG 密钥环一共有四种类型的密钥,如果按照上面指令的样例输出来看的话则如下表。
属性 | 代表 | 含义 |
---|---|---|
sec | SECret key | 主钥的私钥 |
pub | PUBlic key | 主钥的公钥 |
ssb | Secret SuBkey | 从钥的私钥 |
sub | public SUBkey | 从钥的公钥 |
至于这些钥匙的作用可以查看它们的功能,常用的功能有三种。
功能 | 代表 | 含义 |
---|---|---|
S | Signing | 签名和验证信息 |
E | Encryption | 加密和解密信息 |
C | Certification | 签名和验证钥匙 |
注意功能是针对一个钥匙而言的,由其中的公钥和私钥共同完成。其中加密和解密分别由钥匙的公钥和私钥完成,签名和验证则分别由私钥和公钥完成。一般地,GPG 密钥环中钥匙的公钥需要公布到网络上,也就意味着:
- 所有人都能用你公布的公钥加密信息,加密后的信息只有持有私钥的你才能够解密。
- 你可以使用自己持有的私钥签名信息,所有人都能够用你公布的公钥验证签名的合法性。
默认地,GPG 生成的密钥环,主钥用于签名和验证,从钥用于加密和解密。
签名
生成密钥
首先需要生成一个 GPG 密钥环,GPG 在生成密钥的时候会使用一个根据你的操作生成的随机数,所以你可以在 GPG 生成密钥的时候多做一些操作,例如点鼠标、敲键盘、复制文件等等。你可以利用 dd
指令在生成密钥的期间做一些读写操作以让随机数字发生器获得足够的熵数。
1 | $ sudo dd if=/dev/random of=/dev/null bs=4M |
然后可以生成 GPG 密钥环,推荐使用 --full-gen-key
选项来启用所有的功能。
1 | $ gpg --full-gen-key |
其中需要注意的事情有以下几项:
- 密钥种类:形如
method1 and method2
的选项是生成主钥和一个从钥,默认可以用于签名和加密,形如method
的选项只生成主钥,默认只能用于签名。 - 密钥长度:越长越安全,同时加密解密的时间花费越多,选择一个你认为合理的长度。
- 有效期限:你需要选择一个你认为合理的有效期限,钥匙到期后,签名将失效,GPG 服务器也会删除其公钥,所以一般来说你需要使用一个永不过期的钥匙。
- 钥匙密码:一定要为你的钥匙设置一个足够强壮的密码!
上传公钥到服务器
现在你可以将你的公钥上传到任意 GPG 服务器上了,这可以方便他人导入公钥以验证你的签名。通过服务器的交换机制,全球所有的 GPG 服务器都会得到你的公钥。你可以列出你现在所拥有的公钥。
1 | $ gpg --list-keys |
可以看到两个公钥,分别属于主钥 B66CC194
和从钥 F96E3CB7
。上传时指定主钥的钥匙号(key ID)即可,GPG 会将密钥环中的公钥上传到指定的服务器。
1 | $ gpg --keyserver keys.gnupg.net --send-keys <key ID> |
上传公钥到 GitHub
GitHub 刚刚发布了支持 GPG 签名的消息,所以你可以选择使用 GitHub 托管你的仓库。首先你需要以文本形式导出你主钥的公钥。
1 | $ gpg -a -o gnupg.pub --export <key ID> |
然后打开你的 GitHub 密钥管理界面,根据文件 gnupg.pub
为你的 GitHub 账户配置用于验证签名的公钥。
注意:这一步不是必须的,你不一定要使用 GitHub,或许你更喜欢使用其他的商业产品,或者自己搭建一个 Git 服务器。Git 本身就是支持 GPG 签名的,GitHub 对 GPG 的支持仅是把验证结果在网页上显示出来(使用你上传的公钥)。
导出指纹
然而不幸的是,任何人都可以冒充你的名义上传公钥到 GPG 服务器,所以对方搜到以你的名义发布的公钥,不一定真的是你发布的。为了避免这个问题,你需要公布主钥的指纹。GPG 导入公钥后需要手动设置信任度。这时候对方就可以通过对比计算得到的主钥指纹和你提供的主钥指纹,来确定导入的主钥的合法性。
你可以像下面一样导出指纹。
1 | $ gpg --fingerprint <key ID> | perl -nE '$.-2 or s/^\h+// and print' | tee fingerprint |
然后将 fingerprint
文件提交到你的项目仓库中,或者公布在网络的其他位置。
- 你只需(只能)导出主钥的指纹,对方也只需要验证主钥的指纹,因为主钥的公钥可以验证从钥。
- 你可以通过
--export-ownertrust
和--import-ownertrust
来直接导出和导入信任度,但是不推荐这样做。
签名提交和标签
首先你需要为 Git 设置一个用于签名的私钥,通常来说所有的个人项目都用一个私钥进行签名,所以建议设置为全局配置。
1 | $ git config --global user.signingkey <key ID> |
然后就可以使用这个私钥来签名提交。
1 | $ git commit -S |
或者签名标签了。
1 | $ git tag -s <tag> |
如果你想全局默认使用 GPG 签名提交,可以全局将 commit.gpgsign
设置为 true
。
1 | $ git config --global commit.gpgsign true |
关于私钥
任何情况向下都不要把私钥泄露给除了你之外的任何人。如果需要向对方发送加密信息,请让对方提供指纹,导入对方的公钥进行加密,而不要用自己的公钥加密后再把自己的私钥发送过去。
验证
获得公钥
你可以根据你得到的信息在任何 GPG 服务器上查找对应的公钥,典型的例如查看指纹(后 8 位数字为钥匙号),然后根据得到的钥匙号到服务器上查找钥匙。
1 | $ gpg --keyserver keys.gnupg.net --search-keys <key ID> |
选择对应的编号,会自动下载并导入该公钥。你也可以根据用户名和邮箱进行查找。
设置信任
导入后的公钥需要设置信任度才能使用该公钥进行验证,你可以通过类似下面的指令编辑该公钥的信息。
1 | $ gpg --edit-key <key ID> |
你所看到的应该是一个文本交互界面,下面是一个样例。
1 | gpg (GnuPG) 2.1.11; Copyright (C) 2016 Free Software Foundation, Inc. |
你可以键入 fpr
来打印这个主钥的指纹,和你得到的主钥指纹进行对比,如果一致则键入 trust
来设置主钥的信任度。如果主钥被设置为绝对可信的(ultimately),GPG 会根据主钥的公钥验证从钥的签名,最终完成信任建立。最后键入 quit
退出。
验证签名
现在你可以用导入的公钥来验证你 git clone
下来的仓库的提交和标签了,同样你需要首先告诉 Git 应该使用哪个公钥对这个仓库进行验证。一般来说不同作者的项目公钥都不同,建议不要将用于验证的公钥设置为全局。
1 | $ git config user.signingkey <key ID> |
然后可以像下面这样验证一个提交。
1 | $ git verify-commit <commit ID> |
或者验证一个标签。
1 | $ git tag -v <tag> |
扩展阅读
签名和验证
动作 | 指令 |
---|---|
二进制方式签名文件 | gpg -u <key ID> -s <file> |
纯文本方式签名文件 | gpg -u <key ID> --clearsign <file> |
签名文件并独立存放签名 | gpg -u <key ID> --detach-sign <file> |
验证文件 | gpg --verify-files <file> |
通过独立的签名文件验证文件 | gpg --verify-files <file_sig> <file> |
如果不指定
-u
选项,会使用第一个密钥环中主钥的私钥进行签名。
加密和解密
动作 | 指令 |
---|---|
二进制方式加密文件 | gpg -r <key ID> -e <file> |
纯文本方式加密文件 | gpg -r <key ID> -a -e <file> |
解密文件 | gpg <file> |
- 这里你需要指定从钥的钥匙号,如果
-r
选项被省略,GPG 会交互式的请求一个钥匙号。- 如果你想在加密的同时签名文件,在加密指令中额外指定一个
-s
选项。