1. 引言
在此并不太关心"赋能开发者",而是对 "把这些事情真正做对" 这件事持相当悲观的态度。
在学术文献以及最复杂的现代系统中,针对其中许多问题,确实存在"更好"的答案。如果在为资源受限的嵌入式系统开发,可以使用 STROBE,并基于单一的、类似 SHA-3 的 sponge 结构,构建一套现代且健全的认证加密栈。也可以使用 NOISE 来构建一个自带 AKE 的安全传输协议。说到 AKE,其实可以选择的大概有三十多种基于密码的 AKE。
但如果你是开发者,而不是密码学工程师,你就不该这么做。你应该保持方案简单、传统、易于分析------用 Google TLS 团队的话说,就是要"无聊(boring)"。
(本文内容由不同的人在十多年间逐步形成和更新。本文保留了 Colin Percival 在 2009 年的观点、Thomas Ptacek 在 2015 年的观点,以及 2018 年Latacora团队的观点,便于对比。若现在(2018年)正在设计系统,直接使用 2018 年 Latacora 的建议即可。)
2. 数据加密
数据加密相关正确答案为:
- Percival,2009:AES-CTR + HMAC
- Ptacek,2015:
- (1) NaCl/libsodium 的默认方案
- (2) ChaCha20-Poly1305
- (3) AES-GCM
- Latacora,2018:KMS 或 XSalsa20 + Poly1305
在什么情况下需要关心这个?:
当需要对用户或网络隐藏信息时。
如果有条件使用 KMS(比如 Amazon 或 Google 的硬件安全模块时间共享服务),那就用 KMS。如果"可以用 KMS",但只是把加密当成一个周末小项目,想着少用点 KMS 能省点钱------还是用 KMS。如果只是要在应用启动时加密 API Token 这类秘密,用 SSM Parameter Store,它本质上也是 KMS。开发者甚至不需要理解 KMS 是怎么工作的。
否则,理想情况下应该使用 AEAD:
- 带附加数据的认证加密(支持对明文头部进行认证)。
主流的认证加密方式,通常是将流密码(一般是 CTR 模式下的 AES)与多项式 MAC(一种加密 CRC)组合使用。
这些主流方案的问题在于 nonce:
- 它们要求开发者为每一条加密流提供一个唯一(通常是随机)且绝不能重复的值。
- 最简单的做法是用安全随机数生成器生成 nonce,因此需要一个在这方面足够友好的方案。
nonce 对 AES-GCM 尤其重要,而 AES-GCM 又恰恰是目前最流行的加密模式。不幸的是,AES-GCM 对 nonce 的要求非常苛刻,使用随机 nonce 几乎、但又不完全处在安全边缘。
因此,推荐使用 XSalsa20-Poly1305。它属于 "ChaPoly" 构造的一种,而 ChaPoly 系列是 AES-GCM 之外最常见的加密构造。注意应通过 libsodium 或 NaCl 获取 XSalsa20-Poly1305。
相比 ChaCha20 和 Salsa20,XSalsa20 的优势在于它支持扩展 nonce:
- nonce 足够大,可以为每一条加密流生成一个很长的随机 nonce,而无需担心到底加密了多少条流。
目前也有一些所谓的 NMR / MRAE 方案,声称即使 nonce 使用不当也能提供一定安全性,比如 GCM-SIV(所有 SIV 系列)以及 CAESAR 竞赛的决赛方案 Deoxys-II。它们很有意思,但目前几乎没人真正支持或使用;而且在已经有扩展 nonce 的情况下,安全性提升也很有限。它们不够无聊。现在,继续保持无聊。
避免使用:
- AES-CBC、单独使用的 AES-CTR、64 位分组大小的分组密码------尤其是莫名其妙很流行的 Blowfish、OFB 模式。永远不要使用 RC4,它已经烂到近乎滑稽。
3. 对称密钥长度
对称密钥长度相关正确答案为:
- Percival,2009:使用 256 位密钥
- Ptacek,2015:使用 256 位密钥
- Latacora,2018:继续使用 256 位密钥
在什么情况下需要关心这个?:
只要在使用密码学,就该关心。
但要记住:
- AES 密钥 被破解的概率,远低于 公钥对。如果非要在密钥长度上纠结,那公钥才更值得 加大位数。
避免使用:
- 超大密钥的构造、密码级联(cipher cascades)、小于 128 位的密钥。
4. 对称"签名"【MAC】
对称"签名"【MAC】相关正确答案为:
- Percival,2009:使用 HMAC
- Ptacek,2015:没错,用 HMAC
- Latacora,2018:仍然是 HMAC
在什么情况下需要关心这个?:
当在保护 API、加密会话 Cookie,或者(违背医嘱地)在不使用 AEAD 的情况下加密用户数据时。
如果只做认证而不加密(如 API 请求),不要搞复杂。存在一大类密码学实现漏洞,源自向 MAC 输入数据的方式。如果在从零设计一个新系统,建议搜索一下 "crypto canonicalization bugs"。另外,一定要使用安全的比较函数。
如果使用 HMAC,总会有人指出:SHA-3(以及截断版 SHA-2)可以使用 "KMAC",也就是说直接拼接 key 和 data 再哈希就足够安全。这意味着在理论上,HMAC 在 SHA-3 或截断 SHA-2 上做了一些"多余"的工作。但那又怎样?把 HMAC 当成设计中的廉价保险吧,以防将来有人切换到非截断的 SHA-2。
避免使用:
- 自定义"带密钥哈希"、HMAC-MD5、HMAC-SHA1、复杂的多项式 MAC、加密后的哈希、CRC。
5. 哈希算法
哈希算法相关正确答案为:
- Percival,2009:使用 SHA-256(SHA-2)
- Ptacek,2015:使用 SHA-2
- Latacora,2018:仍然是 SHA-2
在什么情况下需要关心这个?:
永远。
如果条件允许:使用 SHA-512/256,它通过输出截断来规避长度扩展攻击。
Latacora团队仍然认为,从 SHA-2 升级到 SHA-3 的可能性,反而不如 从 SHA-2 升级到某种比 SHA-3 更快的算法的可能性大。而且 SHA-2 目前看起来依然非常优秀,所以安心地、舒舒服服地继续用 SHA-2 吧。
避免使用:
- SHA-1、MD5、MD6。
6. 随机 ID(Random IDs)
随机 ID(Random IDs)相关正确答案为:
- Percival,2009:使用 256 位随机数。
- Ptacek,2015:使用 256 位随机数。
- Latacora,2018:使用 256 位随机数。
在什么情况下需要关心这个?:
永远。
随机数来源:
/dev/urandom
避免使用:
- 用户态随机数生成器、OpenSSL RNG、havaged、prngd、egd、
/dev/random。
/dev/random 和 /dev/urandom 都是类 Unix 系统(Linux、BSD、macOS)提供的内核级随机数设备。现代系统中,几乎所有场景都应该使用 /dev/urandom。
/dev/random 既慢又容易阻塞,而且在现代内核下并不比 /dev/urandom 更安全。
| 特性 | /dev/random |
/dev/urandom |
|---|---|---|
| 是否阻塞 | 会阻塞 | 不会阻塞 |
| 熵池不足时 | 等待新的真实熵 | 继续用 CSPRNG 输出 |
| 性能 | 慢 | 快 |
| 实际安全性 | 不比 urandom 更高 | 足够安全 |
| 易用性 | 差 | 好 |
7. 密码处理(Password handling)
密码处理(Password handling)相关正确答案为:
- Percival,2009:scrypt 或 PBKDF2。
- Ptacek,2015:按优先级顺序,使用 scrypt、bcrypt,如果实在没别的可用,再用 PBKDF2。
- Latacora,2018:按优先级顺序,使用 scrypt、argon2、bcrypt,如果实在没别的可用,再用 PBKDF2。
在什么情况下需要关心这个?:
接收用户密码,或者在系统的任何地方使用了人类可理解的密钥(口令)。
不过说实话:甚至可以随便掷个飞镖来选一个。技术上讲,argon2 和 scrypt 明显优于 bcrypt,而 bcrypt 又明显优于 PBKDF2。但在实践中,最重要的是开发者确实使用了一个真正安全的密码哈希算法,至于具体用哪一个,反而没那么重要。
不要设计复杂的"密码哈希可切换(agility)"方案。
避免使用:
- SHA-3、直接使用的 SHA-2、SHA-1、MD5。
8. 非对称加密(Asymmetric encryption)
非对称加密(Asymmetric encryption)相关正确答案为:
- Percival,2009:使用 RSAES-OAEP,配合 SHA-256 和 MGF1+SHA-256,指数 65537。
- Ptacek,2015:使用 NaCl / libsodium(box / crypto_box)。
- Latacora,2018:使用 NaCl / libsodium(box / crypto_box)。
在什么情况下需要关心这个?:
需要把同一类消息加密发送给许多不同的人(其中一些可能是陌生人),而他们需要异步接收消息(类似存储转发的电子邮件),并在离线状态下解密。这是一个相当狭窄的使用场景。
在所有加密学"正确答案"中,这是开发者最不可能自己正确实现的一个。不要自己"即兴发挥"实现公钥加密,也不要直接使用像 OpenSSL 或 BouncyCastle 这样的底层加密库来拼装方案。
下面是应该停止使用 RSA、转而使用椭圆曲线的几个原因:
- RSA(以及 DH)会拖向"向后兼容",也就是对不安全系统的降级攻击兼容
- RSA 会诱使实现者直接用公钥原语来加密数据,而这通常并不是开发者真正想做的
- RSA 的可配置项太多;而在现代曲线系统(如 Curve25519)中,所有参数都已经为安全性预先设定好
NaCl 使用 Curve25519(目前最流行的现代曲线之一,经过精心设计,以消除 NIST 标准曲线中的若干类攻击)并结合 ChaPoly AEAD 方案。各种编程语言几乎肯定有 NaCl / libsodium 的绑定(Go 语言甚至有自己的实现),直接用它们。不要尝试自己拼装实现。
libsodium 提供了各语言bindings绑定列表。
不要使用 RSA。
避免使用:
- 2015 年之后设计、却仍然使用 RSA 的系统;RSA-PKCS1v1.5;RSA;ElGamal;或者什么 Merkle-Hellman knapsacks背包之类的------总之,避开 RSA。
9. 非对称签名
非对称签名相关正确答案为:
- Percival, 2009:使用 RSASSA-PSS,搭配 SHA256,然后是 MGF1+SHA256。
- Ptacek, 2015:使用 NaCl、Ed25519,或 RFC6979。
- Latacora, 2018*:使用 NaCl 或 Ed25519。
在什么情况下需要关心这个?:
正在设计一种新的加密货币;或者一个用于对 Ruby Gems、Vagrant 镜像进行签名的系统;或者一个 DRM 方案,其中需要对在不同时刻随机到达的一系列文件进行离线真实性校验,并且这些校验都基于同一个密钥;又或者正在设计一种加密消息传输系统。
前一个答案(非对称加密(Asymmetric encryption))中的所有指控在此一并纳入,视同已完整陈述。
在过去 10 年中,非对称签名最主要的两个使用场景是:
- 加密货币,
- 以及 具备前向保密性的密钥协商(如 ECDHE-TLS)。
这些场景中占主导地位的算法全部基于椭圆曲线。对于仍然使用 RSA 签名的新系统,应保持高度警惕。
近几年,一个显著趋势是:
- 从传统的 DSA 签名转向对误用更具抵抗力的"确定性"签名方案,其中最典型的代表就是 EdDSA 和 RFC6979。
- 可以将这些方案理解为对 PlayStation 3 上 ECDSA 漏洞的"防用户误用"回应------当时由于随机数复用而导致私钥泄露。应优先使用确定性签名方案,而不是其他任何签名方案。
Ed25519(NaCl / libsodium 的默认方案)是比特币之外最流行的公钥签名方案。它对误用具有很强的抵抗能力,并且在其他方面也经过了精心设计。同样不应该自己实现它------应直接从 NaCl 中获取。
避免使用:
- RSA-PKCS1v15、RSA、ECDSA、DSA;尤其要避免传统的 DSA 和 ECDSA。
10. Diffie-Hellman
Diffie-Hellman相关正确答案为:
- Percival, 2009:使用 2048 位的 Group #14,并以 2 作为生成元。
- Ptacek, 2015:可能仍然是 DH-2048,或者使用 NaCl。
- Latacora, 2018:可能什么都不用;或者使用 Curve25519。
在什么情况下需要关心这个?:
正在设计一种加密传输或消息系统,它将来可能会被陌生人使用,因此静态的 AES 密钥并不可行。
2015 年版本的这份文档把所有人都搞糊涂了。
问题的一部分在于,本文的"正确答案"本身是对 Colin Percival 的"正确答案"的回应,而他给出了一个"Diffie-Hellman"的答案,仿佛"自己搞 Diffie-Hellman"是开发者日常会做的事情。实际上,开发者根本就不应该自行设计加密传输协议。要理解这件事情有多复杂,可以去阅读 Noise Protocol Framework 的文档。如果在做基于 DH 的密钥交换,那么很可能需要一个能够抵抗密钥泄露冒充(KCI)的认证密钥交换(AKE)方案,因此所使用的 DH 原语并不是唯一重要的安全考量。
不过,话说回来。
事实仍然是:
- 如果能直接使用 NaCl,那就用 NaCl。开发者甚至不需要关心 NaCl 内部具体做了什么------这正是 NaCl 存在的意义。
否则:
- 使用 Curve25519。几乎所有语言都有相应的库。
2015 年时,Latacora团队还担心鼓励人们自己去实现 Curve25519,会出现各种 JavaScript 大整数实现的噩梦。但实际上,Curve25519 的一个重要设计目标,就是整条曲线都经过精心选择,以尽量减少实现层面的错误。不要自己写!但总之,直接用 Curve25519 就对了。
不要在 NIST 曲线 上做 ECDH,因为那样必须在进行椭圆曲线运算之前,小心翼翼地验证曲线点是否合法,否则就会泄露密钥。这种攻击非常容易实现,比 CBC 填充预言机还简单,却要致命得多。
2015 年的文档里提到过:
- 相比那些"看起来不靠谱的曲线库",宁愿用 DH-1024。
- 这一点现在依然成立------既成立,又很蠢。解决"DH-1024 还是可疑曲线库"这个问题的方法,和解决"该用 Blowfish 还是 IDEA?"是一样的:别让自己面对这种问题。用 Curve25519。
避免使用:
- 传统 DH、SRP、J-PAKE、各种握手/协商机制、只基于分组密码的复杂密钥协商方案、
srand(time())。
11. 网站安全
网站安全相关正确答案为:
- Percival,2009:使用 OpenSSL。
- Ptacek,2015:仍然是 OpenSSL,如果可以的话用 BoringSSL;或者干脆用 AWS 的 ELB。
- Latacora,2018:使用 AWS ALB/ELB,或者 OpenSSL + Let's Encrypt。
在什么情况下需要关心这个?:
有一个网站。
如果能付钱让 AWS 来代替操心这个问题,则建议这么做。
否则,2010 到 2016 年之间确实有过一段"OpenSSL 可能不是正确答案"的黑暗时期,但那已经过去了。OpenSSL 变得更好了,更重要的是,它在漏洞披露和响应方面做得非常到位。
使用 OpenSSL 之外的任何东西,都会在几乎没有、没有,甚至是负安全收益的情况下,大幅复杂化所开发的系统。所以,保持简单就好。
说到简单:Let's Encrypt 是免费的,而且是自动化的。设置一个 cron 任务定期重新获取证书,并进行测试。
避免使用:
- 小众的 TLS 库,如 PolarSSL、GnuTLS、MatrixSSL。
12. 客户端---服务器应用安全
客户端---服务器应用安全相关正确答案为:
- Percival, 2009:将服务器的 RSA 公钥随客户端代码一起分发,并且不要使用 SSL。
- Ptacek, 2015:使用 OpenSSL;如果可以的话,使用 BoringSSL。或者干脆使用 AWS 的 ELB。
- Latacora, 2018:使用 AWS ALB/ELB,或使用 OpenSSL + Let's Encrypt。
在什么情况下需要关心这个?:
与 前面对公钥密码学的建议 有关。*
鉴于 TLS 近年来的"黑历史",推荐它看起来似乎有点疯狂:
- Logjam DH 协商攻击
- FREAK 出口级密码攻击
- POODLE CBC 预言机攻击
- RC4 灾难
- CRIME 压缩攻击
- Lucky13 CBC 填充预言机时序攻击
- BEAST CBC 链式 IV 攻击
- Heartbleed
- 重新协商(Renegotiation)问题
- Triple Handshakes
- 被攻破的 CA
- DROWN
尽管如此,仍然应该在自定义传输协议中使用 TLS,原因如下:
- 在自定义协议中,不必(也不应该)依赖第三方 CA。甚至可以完全不使用 CA(当然,搭一个自己的 CA 也不难);可以直接使用自签名证书白名单------这基本上就是 SSH 默认的做法,也是自己设计时大概率会想到的方案。
- 既然在做自定义协议,就可以直接使用最优的 TLS 密码套件:TLS 1.2+、Curve25519、ChaPoly。这能消除绝大多数针对 TLS 的攻击。之所以很多系统做不到这一点,是因为它们需要向后兼容;而在自定义协议中,不需要这种包袱。
- 这些攻击中有很多只对浏览器有效,因为它们依赖受害者接受并执行攻击者控制的 JavaScript,从而生成大量已知或可控的明文。
避免:
- 自己设计加密传输协议(这是一个真正困难的工程问题);
- 使用 TLS 但保持默认配置(比如直接用
curl); - 使用
curl或 IPSEC。
13. 在线备份
在线备份相关正确答案为:
- Percival, 2009:使用 Tarsnap。
- Ptacek, 2015:使用 Tarsnap。
- Latacora, 2018:将使用 PMAC-SIV 加密的归档文件存储到 S3,并把备份的指纹保存到兼容 ERC20 的区块链上,或使用Tarsnap。
在什么情况下需要关心这个?:
真的在做备份。
参考资料
1\] Latacora团队2018年博客 [Cryptographic right answers](https://www.latacora.com/blog/cryptographic-right-answers/)