密码学的正确答案(Cryptographic right answers)

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/)

相关推荐
SCIS5883 天前
《交通运输数据安全管理办法》征求意见稿发布,首传信安方案精准对标新规
交通物流·密码安全·商用密码
mutourend17 天前
密码学的正确答案:后量子时代版
密码安全
mutourend18 天前
常数时间分析工具
密码安全
mutourend18 天前
密码学末日原则(The Cryptographic Doom Principle)
密码安全
mutourend22 天前
如何在后量子密码学库中避免侧信道攻击?
密码安全·量子密码学
mutourend23 天前
借助Wycheproof发现 elliptic 库中密码学漏洞
密码安全
mutourend24 天前
无效曲线点攻击——破解蓝牙配对
密码安全
向上的车轮1 个月前
NordPass“最常用200个密码”报告深度解读与安全密码设置实用指南
运维·服务器·安全·密码安全
SCIS5885 个月前
解决方案:新时代电力的安全命题
数据安全·电力·密码安全