Total Pageviews

Saturday 25 April 2020

OpenPGP数字签名的最佳实践

译者:MDrights
在上一篇博文,我们讨论了 OpenPGP 加密的最佳实践。另一方面,数字签名也是 OpenPGP 一个重要的部分,高级和入门级用户都会用到它。在本文里我们来梳理在使用 openPGP 签名时的最佳实践。

什么是数字签名

数字签名一种数学计算过程用来确保以下的目的:
  • 验证发送者:消息/文件的发送者确定就是他/她
  • 完整性:消息在传送过程中没有被篡改
  • 不可抵赖:发送者无法否认某某消息是她/他发的
这技术能验证发送者并给到接收者很大的信心来信任消息的来源。
一图说清数字签名和验证的原理
签名始于对原始的消息进行简单的密码学哈希转换,再用发送者的私钥加密之,然后跟原始消息一起发送出去。
接收者接收到附带这个加密了的哈希的原始消息,把加密的哈希用发送者的公钥解密,然后把这串哈希跟原始消息的哈希进行比对。
好了你现在知道是怎么回事了!如果两串哈希值一致,就等于验证了(对方的)数字签名。

OpenPGP 数字签名最佳实践

下面就是一些你在实际给消息做数字签名时需要追求的最佳实践。

密钥创建和数字签名

  • 使用一个强劲的密钥用于数字签名。
要用4096位(或至少2048位)的私钥。Mailfence 默认生成 4096 位的RSA密钥。- 使用子密钥(sub-key)用于签名
在一台单独的机器上保护好你的密钥对可能并不容易,但你的设备总有可能被偷或丢失。加密和签名是两种不同的操作,因此也需要区别出两种不同的密钥管理方式。你可能需要让你的签名密钥有很长的有效期,这样过去的朋友仍然能验证你的签名。但对于你的加密密钥,你会希望尽快换成一对新的并能轻松地撤回/过期旧的密钥(当你旧密钥丢了时)。此外,最好生成一对密钥作为“主密钥”,保存到外部存储(比如,U盘),再用这对“主密钥”生成一对子密钥(sub-key)放在本地机器日常使用。
这里有篇好文章讲解了如何一步一步使用GnuPG工具创建子密钥。然后你可以把子密钥导入到 Mailfence 密钥仓库里并无缝地在你所有设备上使用。
注意:把主私钥保存在外部介质上会让GPG密钥验证变得有点麻烦,因为每次对别人的密钥做验证时都需要把主密钥导入到本地环境。此外,如果给子密钥设置了过期日期(推荐如此)的话,那子密钥必须在过期日前续期,要么就重新签发子密钥。还有,使用了你的公钥的人(比如,要验证一些你签名了的东西)会收到关于你的密钥已经过期的报错,如果他们不经常从公共密钥服务器上更新他们的GPG密钥串的话。
  • 叫你的朋友和同事对你的公钥签名
(互相之间)对公钥进行签名有助于增强每个人的私钥的正当性(译者注:意思是说为了让彼此信任的人的信任可以串成一系列信任链(即:web of trust),信任便可得到传递下去,而实现这个的方法就是信任的人之间互相给对方的公钥签名)。现在就叫你的朋友/同事给你的公钥做数字签名吧。你也要给他们的公钥签名。
另一个类似的方法是,用 keybase.io ——一个在线服务可以让你的身份和你的公钥匹配,这样他人就可以通过你的公钥找到你,或通过你在网站上留下的信息得到你的公钥。- 给每个外发的消息都进行签名!
是的,而且要把带时间戳的发送者和接收者的电邮地址包含在数字签名里!(再把你的数字签名放进邮件的正文里)你可以同时使用一个服务来提供足够的保护对付重放攻击(replay attacks)(比如 nonce, 会话token, 时间戳等等)。
  • 转发带数字签名的消息时要特别留心
我们来看个例子,Bob 把一封 Alice 写并签名了的邮件转发给了 Carol。那么对于收件人来说只有 Alice 的身份是能够确认的。Carol 不能以为转发了邮件的 Bob 是数字签名的所有者,或,是正确的发件人。除非,Bob 也对自己转发的邮件做了签名。 验证对方的数字签名
  • “尝试”预先获得发件人的密钥指纹!
如果可能,提前取得发送给你邮件的人的公钥的指纹!(最好是当面取得)——或者如果发件人是个名人,那么尝试做些搜索(比如去他/她的网站、博客、社交帐号、keybase.io 等等)或许能找到他/她的指纹。
  • 一定要总是确认消息发件人的密钥对的指纹!
自动化验证(把原始消息的哈希跟解密后的哈希进行匹配)当然好,但你仍不能免受冒充攻击,就是说一个攻击者可以创建一个假的某人身份给你发送消息并用一个假的密钥签名了(这个假的密钥并不是被冒充的发送者的密钥)。这样,要鉴别发送者的密钥只能通过验证他/她的密钥指纹来进行了。你要跟所有发送者确认他们各自的密钥指纹。
千万不要依赖密钥的短或长 ID!
  • 确保签名密钥没有被撤回或过期!
确保签名密钥没有被撤回或过期!这很重要!应用程序通常没有准确把这个通知给用户。
不过,如果签名了的密钥是在密钥过期或撤回之前进行签名的,这签名还是有效的。
  • 导出消息到本地时带上它的数字签名!
因为当今帐号被盗变得更常见了,重要消息最好能在本地保留一份。
此外,你还可以对本地保存的消息再次加密以增加安全性。
  • 数字签名的法律价值
数字签名也可以用来证明某人是特定信息的作者。然而数字签名的精确法律意义还要看该签名是在哪里生成的,以及当地的法律。
这篇文章很好地讨论了这一领域的议题。
当然,在法律场景里运用数字签名技术的话还有很多有争议的地方。一个争议会出现在签名者自己的私钥丢失的可能性。如何验证(鉴别)一个人的私钥的丢失是非主观的被偷走的,还是故意而为?
注:反过来说如果你在某些很反常的场景中不想出现令自己“不可抵赖”的可能性,那么就得去用基于「可否认的验证」技术了!
OpenPGP 数字签名现在是比较常用的了,因为它不需要你必须获得收件人的公钥。
注:如果你当前使用的邮箱帐号本身并不安全,那么以上的 OpenPGP 数字签名最佳实践是帮不到你的。(译者补充:这通常是对于直接使用邮件服务页面里的密钥签名功能的人来说的,因为任何人只要能登录你的邮箱界面就能直接使用你的私钥,进行签名等等操作。所以前提还是要保护你的邮箱帐号密码。若你使用密钥的方式跟邮箱本身是脱离的就还比较安全,如EnigmailMailvelope。)

---------------------------------

OpenPGP 最佳实践 - 密钥服务器

配置一个密钥服务器并让你的计算机同步密钥

如果你不经常地刷新你手中的 PGP 公钥,你就没法及时地了解到十分需要关注的 PGP 公钥的过期或者撤销情况。关于密钥接受有两个步骤,许多用户会把自己的公钥上传到密钥服务器上,为了保证你能接受到这些同步,你需要先正确地配置密钥服务器。

安全地连接一个 sks 密钥服务器池而不是某台具体的服务器

很多 OpenPGP 客户端被配置了一个固定的密钥服务器,这样当服务器出现问题的时候你就有可能没法接受到重要的密钥同步,除了这种单点故障之外,这也会是一个泄漏 OpenPGP 用户之间关联信息的一个主要方式,从而成为一个被攻击的目标。

因此我们推荐使用 sks 密钥服务器池,这个池中的机器会被定期地检查运行状态,如果出现故障就会被移出这个池。

你也需要保证你是在使用 hkps 加密地与这个服务器池通讯,为了使用 hkps,你需要先安装 gnupg-curl

sudo apt-get install gnupg-curl

然后,要使用这个密钥池,你需要下载 sks-keyservers.net 的 CA 证书 并把它保存在你机器上的某处,然后你需要 验证这个 CA 的 PGP 指纹,现在你需要在 ~/.gnupg/gpg.conf 中添加两行。

keyserver hkps://hkps.pool.sks-keyservers.net
keyserver-options ca-cert-file=/path/to/CA/sks-keyservers.netCA.pem # 注意,这个证书地址就是之前下载的那个 CA 的位置

现在你与证书服务器之前的通讯就会通过 hkps,这个可以在有人嗅探你的流量的时候保护的你的社交关系。如果你使用的不是 hkps 而是 hkp 的话,当你在某个密钥服务器上 gpg --refresh-keys 的时候,嗅探你流量的人就可以看到你同步 key 的信息,有了这些信息就事情就会变得十分有趣了。

Note: hkps://keys.indymedia.org, hkps://keys.mayfirst.org and hkps://keys.riseup.net 都提供 hpks 密钥服务器(当然我们还是建议你使用一个密钥服务器池)

保证所有的密钥都是通过你选择的密钥服务器同步

当创建一个密钥对的时候,我们可以指定某一台服务器来拉取他们的 Key,我们建议你使用以下配置信息来忽略对服务器指定:

keyserver-options no-honor-keyserver-url

这样做有如下好处:

  • 可以防止有人通过指定的服务器通过不安全的方式拉取密钥
  • 如果指定的服务器使用了 hkps,如果对方没有下载 CA 证书的话密钥就可能永远没法被同步

需要注意的是攻击者也可以指定一个密钥服务器并且监控你是从哪儿同步了他们的密钥(注:类似 BT 种子钓鱼,可以钓出对方的 IP 地址)

慢慢同步你的密钥并且一次同步一个

现在已经配置好了一个不错的服务器池,你现在需要做的就是定期地同步你的密钥,对于 Debian 和 Ubuntu 用户来说最好的方式就是使用 parcimonie:

sudo apt-get install parcimonie

parcimonie 是一个走 Tor 的缓慢的密钥同步守护进程。它使用随机化休眠机制,并且所有同步密钥的流量都是通过 Tor. 这样会让攻击者难以通过你的手中公钥来关联到你。

你不应该使用 gpg --refresh-keys 或者邮件客户端上的刷新按钮来刷新密钥,因为这样的话密钥服务器管理员,监听者都可以知道你在刷新的密钥了。

不要盲目地相信来自密钥服务器的密钥

所有人都可以把自己的密钥上传到密钥服务器上所有你不应该仅仅是下载下密钥就盲目地认为就是你需要的那个。你用该通过线下或者电话的方式向对方确认其密钥的指纹信息。当你确定了对方的指纹后,你就可以通过如下指令下载对方的公钥:

gpg --recv-key '<fingerprint>'

下一步就是确认你下载你的密钥就是你需要的那个,密钥服务器可能会给了一个其他密钥的给你。如果你的 GnuPG 版本小于 2.1,那你就需要手动确认你下载到的 Key,如果你的 GnuPG 版本大于 2.1 的话它会自动拒绝来自密钥服务器的不正确的密钥。

你可以用两种方式来确认密钥指纹:

  1. 直接检查密钥指纹
gpg --fingerprint '<fingerprint>'
  1. 尝试在本地用那个指纹给一个密钥签名
gpg --lsign-key '<fingerprint>'

如果你确定你拿到了那个人的正确的密钥,比较建议的是在本地给那个密钥签名,如果你希望公开的表明你和那个人的联系的话,你可以公开 --sign-key.

注意上面命令中密钥指纹只需要被单引号或者双引号包含。

不要依靠 Key ID

短的 OpenPGP ID,比如 0×2861A790,只有 32 位长,他们已经 被证明 一个其他的 Key 可以有先同的 Key ID. 长的 OpenPGP ID,比如 0xA1E6148633874A3D 有 64 位长,也是可以 被碰撞 的,所以 也是一个严重的问题

如果你需要一个强密码学保证的验证方法,你应该使用全指纹,你永远不应该以来或短或长的 Key ID.

你至少应该在 GPG 配置文件中写上 keyid-format 0xlong 和 with-fingerprint 来保证所有密钥都是显示 64 位长的 ID 且显示指纹。

在导入前检查

如果你在一个地方(比如网站上面)下载了一个密钥,你应该在导入前验证密钥指纹。

gpg --with-fingerprint <keyfile>

---------

OpenPGP 最佳实践 - 密钥配置

你应该已经可以从一个密钥服务器上同步其他人的密钥了,现在你需要保证你的 OpenPGP 密钥已经被正确的配置。

使用一个强壮的主密钥

(注:此段有许多内容较为过时,我将选择性翻译) 在 2011 年美国 NIST放弃了 DSA-1024 加密算法。

现在推荐使用 sha512 生成的 4096 位的 RSA 密钥,并且使用双密钥签名的 密钥转移声明,并且让其他人知道你的密钥,有一份 不错的文档 写清楚了这么做的所有步骤。

转变会比较艰难,但是这样是值得的,且这也是一个最好的使用工具实践的方法。

密钥过期时间不要超过 2 年

很多人不希望他们的密钥过期,但是你真的这么认为么?密钥的过期时间可以在任意时刻(哪怕已经过期)修改。所以过期时间更像是一个自动的定时开关,如果你在没有及时重置开关则密钥可以自动失效,这样可以让其他人知道你一直对密钥有所有和管理能力。

当然,如果设置一个过期时间的话就意味着你以后在某个时间需要延长一次,这个是一个你需要记住的微小的工作。

你可能会认为这样很烦人并且不想处理它,但是这个是一个基础的让你保持对 OpenPGP 工具熟悉的方法,它表明了你的密钥仍然在被使用着。此外,有些人不会对一个没有过期日期的密钥签名。

假设你已经生成了一个没有过期时间的密钥,你可以通过以下指令来添加过期时间:

gpg --edit-key <密钥指纹>

现在选择一个你想设置过期时间的子密钥,并且输入 expire 指令:

gpg> key 1
gpg> expire

然后设置一个合理的过期时间并且退出(比如两年):

Key is valid for? (0) 2y
gpg> save

然后你需要把你的公钥推到密钥服务器上来宣布这次改动:

gpg --send-key <密钥指纹>

在日历上提醒你延长过期时间

你肯定记不住密钥过期时间的,所以在过期前的一两个月设置一个提示告诉你该更新过期时间了。

生成一个吊销证书

如果你忘记了你的密码或者你的私钥已经被他人获取,你唯一的希望就是等到的密钥过期(当然,这样并不是一个好的解决方法),或者把你的吊销证书推到密钥服务器上,这样可以通知到其他人你的密钥已经被吊销不再使用了。

一个被吊销的密钥仍然可以用来验证证书或者解密信息(如果你还能解开私钥的话),但是已经不能被其他人用来加密消息发送给你,使用以下指令创建一个称为 revoke.asc 的销毁证书:

gpg --output revoke.asc --gen-revoke '<fingerprint>'

你可能会希望把这个文件打印出来并且隐藏起来。如果有人得到了这个,他们就可以吊销你的密钥,这样就会很不方便,但如果他们得到了你的私钥,那么吊销的密钥就十分必要了。

需要注意这个在 GnuPG 2.1 及以上会默认进行。

主密钥只用来认证(或者签名),使用一个子密钥用来加密

这个在 GnuPG 1.4.18 及以上是默认进行的。

用一个单独的子密钥来签名

默认 GnuPG 使用同一个子密钥来签名消息和给其他密钥签名。如果使用单独的子密钥来操作的话会十分有用,因为签名消息远远比签名其他的密钥重要。

在这个情况下你的主密钥仅仅用来认证,且很少被使用。

可以使用 edit-key 来编辑,使用 addkey 来生成子密钥。

把主密钥完全放在线下

这个保护你的主密钥的方法很有技巧性。如果你的主密钥被盗,攻击者就可以用它来创建的新的身份并且吊销你的证书,将主密钥完全放在线下可以很好的防止这类攻击。

确保你使用了单独的签名密钥,你没法使用已经被下线的主密钥给邮件签名。

# 导出你的主密钥
gpg -a --export-secret-key john.doe@example.com > secret_key
# 导出所有子密钥
gpg -a --export-secret-subkeys john.doe@example.com > secret_subkeys.gpg
# 删除 keyring 上的所有私钥,这样就只有子密钥的
$ gpg --delete-secret-keys john.doe@example.com
Delete this key from the keyring? (y/N) y
This is a secret key! - really delete? (y/N) y
# 重新导入你的子密钥
$ gpg --import secret_subkeys.gpg
# 确认已经导入完成
$ gpg --list-secret-keys
# 在磁盘上把子密钥删除
$ rm secret_subkeys.gpg

然后你需要保护好 secret_key 内容,比如放在一个 U 盘中,或者使用智能卡(注:比如 Yubikey)来储存密钥,设备的安全性决定了你密钥的安全性。

请确保你有吊销证书。

你可以通过 --list-secrect-keys 参数,通过判断私钥部分显示的是 sec# 而不是 sec 来确认私钥部分已经被移除。

提示:在上述操作中私钥在你的磁盘上时使用明文储存的,所以一个单独的 rm 并不能彻底删除掉,考虑使用 wipe 工具,当然,如果你使用的是 SSD 的话,操作前请确保已经上了 FDE(全盘加密),否则没法彻底删除。 

No comments:

Post a Comment