密码学的正确答案(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/)

相关推荐
mutourend1 天前
如何在后量子密码学库中避免侧信道攻击?
密码安全·量子密码学
mutourend2 天前
借助Wycheproof发现 elliptic 库中密码学漏洞
密码安全
mutourend3 天前
无效曲线点攻击——破解蓝牙配对
密码安全
向上的车轮20 天前
NordPass“最常用200个密码”报告深度解读与安全密码设置实用指南
运维·服务器·安全·密码安全
SCIS5884 个月前
解决方案:新时代电力的安全命题
数据安全·电力·密码安全
SCIS5886 个月前
智慧城市的安全密码:商用密码如何守护万物互联?
智慧城市·密码安全·商用密码
星尘安全1 年前
中国研究员使用量子计算机破解 RSA 加密
密码学·量子计算·密码·密码安全
FreeBuf_1 年前
LDAPWordlistHarvester:基于LDAP数据的字典生成工具
ldap·密码安全·账户安全·字典生成
知白守黑V2 年前
网络安全“三保一评”深度解析
数据安全·等保测评·等保2.0·密码安全·商密测评·分保·关保