0%

《HTTPS 权威指南》读书笔记(01):SSL、TLS 和密码学

本系列文章是《HTTPS 权威指南:在服务器和 Web 应用上部署 SSL/TLS 和 PKI》一书的读书笔记,该书的作者是 Ivan Ristić,除了本书,他的作品还有 《Apache Security》、《ModSecurity Handbook》。以下是他在本书的自我介绍:

1
Ivan Ristić 是一位安全研究员、工程师、作者。他对于 Web 应用防火墙领域的发展,开源 Web 应用防火墙 ModSecurity 的开发,以及在 SSL Labs 网站上对 SSL/TLS 和 PKI 的研究、工具和指南的发表,都作出了很大的贡献,因此享誉世界。

安全领域变得越来越复杂,理解攻击和威胁往往就是其工作的一部分。在日常工作中,通常都是把 OpenSSL 库作为黑盒子使用,但是随着对 Nginx 开发维护的深入,当前在 SSL/TLS 的知识储备不足以支撑我更深入地排查问题,因此想通过系统阅读相关书籍来加深对 SSL/TLS 的理解。以上就是我阅读本书的初衷。

传输层安全

所有连接到互联网的设备都有一个共同点,它们依赖安全套接字层(secure socket layer,SSL)和传输层安全(transport layer security,TLS)协议保护传输的信息。SSL 和 TLS 都是加密协议,旨在基于不安全的基础设施提供安全通信。这些协议保护着通信链路即传输层,这也是 TLS 名称的由来。

TLS 有以下四个主要目标:

  • 加密安全:为任意愿意交换信息的双方启用安全通信
  • 互操作性:独立的编程人员应该能够使用通用的加密参数开发程序和库,使它们可以相互通信
  • 可扩展性:TLS是一种能高效开发和部署加密协议的框架
  • 效率:最终的目标是在实现上述所有目标的基础上保持性能成本在可接受的范围内

网络层

互联网的核心建立在 TCP/IP 协议族之上,但是 TCP/IP 协议族的核心协议本身不提供任何安全保障,任何有权访问通信链路的人都可以获得所有数据,并且可以在不被察觉情况下改变这些数据。

而如果部署了加密,攻击者也许有能力得到加密数据的访问权限,但是不能解密数据或者篡改数据。为了避免伪装攻击,SSL 和 TLS依赖另外一项被称为公钥基础设施(public key infrastructure, PKI)的重要技术,确保将流量发送到正确的接收端。

如果参考 OSI 7 层网络模型,SSL/TLS 位于 TCP 协议之上、应用层协议(如HTTP)之下。当不需要加密时,可以将 TLS 从模型中去掉,这并不会对上层协议产生影响。当需要加密时,就可以利用 TLS 加密 HTTP,以及其他应用层协议。

协议历史

SSL 协议最初由 Netscape 公司开发:

  • 协议的第一个版本从未发布过
  • 1994.11 发布了 SSL2,但是 SSL2 有严重缺点,被认为是失败的协议
  • 1995年底发布了SSL3,它是完全重新设计的协议,该设计一直沿用到今天
  • 1996.5 TLS 工作组成立,开始将 SSL 从 Netscape 迁移至 IETF
  • 1999.1 TLS 1.0 发布(RFC 2246),与 SSL3 相比,版本修改并不大
  • 2006.4 TLS 1.1 发布,仅仅修复了一些关键的安全问题。但是协议的重要更改是作为 TLS 扩展于 2003.6 发布
  • 2008.8 TLS 1.2 发布,该版本添加了对已验证加密的支持,并且基本上删除了协议说明中所有硬编码的安全基元,使协议完全弹性化
  • 2018.12,TLS 1.3 正式发布,它是一次全面升级,相比于之前版本,它有两个优势:增强安全性、提升速度

密码学

密码学是一门通信安全的科学,同时也是一门艺术。部署正确的密码能解决安全的三个核心需求:保持秘密(机密性)、验证身份(真实性),以及保证传输安全(完整性)

构建基块

在最底层,使用密码加密依赖于各种加密基元(cryptographic primitive)。每种基元都着眼于某个特定功能而设计。比如,我们会使用某个基元加密,使用另外一个基元进行完整性检查。单个基元本身的作用并不大,但是我们可以将它们组合成方案(scheme)和协议(protocol),从而提供可靠的安全性。

对称加密

对称加密(symmetric encryption)又称私钥加密(private-key cryptography),是一种混淆算法,能够让数据在非安全信道上进行安全通信。根据 Auguste Kerckhoffs 原则:

1
即使攻击者知晓了整个密码系统除密钥以外的所有情报,系统仍然应当能保证安全。

所以我们可以认为密文的安全性完全取决于密钥。密码可以分为两类:

  • 序列密码
  • 分组密码

序列密码(stream cipher)的操作过程与我们想象中加密的过程一致。将 1 字节的明文输入加密算法,就得到 1 字节的密文输出。在对端则进行相反的过程。整个过程持续重复,直到所有数据处理完成。序列密码的核心是生成一串称为 密钥序列(keystream)的无穷序列:

  • 加密就是将密钥序列中的 1 字节与明文序列中的 1 字节进行异或操作
  • 因为异或操作是可逆的,所以解密就是将密文序列中的1字节与密钥序列中的相同字节进行异或操作

只要攻击者无法预测密钥序列中对应位置的字节,就可以认为加密过程是安全的。RC4是最为人熟知的序列密码,但是它已经不再安全。

分组密码(block cipher)每次加密一整块数据,并且现代的分组密码倾向于使用 128位(16 字节)大小的块。一种分组密码就是一个变换函数:接受输入并生成看似杂乱无章的输出。分组密码有一些限制:

  • 只能使用它们加密长度等于加密块大小的数据。因此在实际使用分组算法时,需要一个方法处理任意长度的数据
  • 分组密码是确定的。对于相同的输入,输出也是相同的。这个特性会使许多攻击成为可能,需要解决。

实践中,人们通过称为 分组密码模式(block cipher mode)的加密方案来使用分组密码。这种方案能规避这些限制,有时还可以添加身份验证。

世界上最流行的分组密码是高级加密标准(advanced encryption standard,AES),可以使用 128 位、192 位和 256 位的加密强度。

分组密码的挑战之一是处理数据长度小于加密块大小的数据加密。一种方法是追加额外的数据到明文的尾部。这些额外的数据就被称为填充(padding)。填充不能由任何随机数据构成,它必须遵循某种格式,这样接收方才可以发现填充并了解需要丢弃多少字节。

  • 在 TLS 中,加密块的最后 1 字节包含填充长度,指示填充有多少字节(不包含填充长度字节)。填充的每字节都被设置成与填充长度字节相同的值
  • 为了在解密后丢弃填充,接收方检查数据块的最后1字节,删除它。接着,接收方删除指定长度的字节数,同时检查它们是否都是相同的值

散列函数

散列函数(hash function)是将任意长度的输入转化为定长输出的算法。散列函数的结果经常被简称为散列值。编程中普遍使用散列函数,但并非所有散列函数都适用于密码学。密码学散列函数有以下几个额外特性:

  • 抗原像性(单向性):给定一个散列,计算上无法找到或者构造出生成它的消息
  • 抗第二原像性(弱抗碰撞性):给定一条消息和它的散列,计算上无法找到一条不同的消息具有相同的散列
  • 强抗碰撞性:计算上无法找到两条散列相同的消息

散列函数最常用的使用场合是以紧凑的方式表示并比较大量数据。散列函数经常被称为指纹、消息摘要,或者简单称为摘要。

消息验证代码

散列函数可以用于验证数据完整性,但仅在数据的散列与数据本身分开传输的条件下如此。否则攻击者可以同时修改数据和散列,从而轻易地避开检测。消息验证代码(message authentication code,MAC)或者使用密钥的散列(keyed-hash)是以身份验证扩展了散列函数的密码学函数。只有拥有散列密钥,才能生成合法的 MAC

MAC 通常与加密一起使用。如果没有 MAC,即使攻击者无法解密密文,他也能修改传输中的数据。加密提供了机密性但无法确保完整性。当 MAC 和密文一起发送时,Bob 就能确认消息并没有篡改。

任何散列函数都能用作 MAC 的基础,另一个基础是基于散列的消息验证代码(hash-based message authentication code,HMAC)。HMAC 本质就是将散列密钥和消息以一种安全的方式交织在一起。

分组密码模式

分组密码模式是为了加密任意长度的数据而设计的密码学方案,是对分组密码的扩展。

  • 电码本(electronic codebook,ECB):模式是最简单的分组密码模式。它只支持数据长度正好是块大小的整数倍的情况,如果数据长度不满足这个条件,就得事先实施填充。加密就是将数据 按块大小切分,再分别加密每一块。ECB 的简单就是它的劣势。因为分组密码是确定的(输入相同,输出也相同)。攻击者可以观察密文并且提交任意明文加密,如此尝试足够的次数,就能猜出明文

  • 加密块链接(cipher block chaining,CBC):模式是从 ECB 发展。为了解决 ECB 天生的确定性,CBC 引入了初始向量(initialization vector,IV)的概念。即使输入相同,IV 也可以使每次的输出都不相同:

    • 整个过程开始于生成一个随机 IV(因此不可预测),长度与加密块相等
    • 加密前,明文第一块内容与 IV 进行异或操作。这一步对明文进行了掩饰,并保证密文总是不尽相同
    • 对于下一个加密块,使用上一块的密文作为 IV,以此类推
    • 这样一来,每次加密操作都是同一个加密链条中的一部分,这也是这种模式名称的由来
    • 至关重要的是,IV 必须通过线路传送到接收端,这个信息是成功解密所必需的

非对称加密

对称加密在高速处理大量数据方面做得非常好,然而随着使用它的团体增加,产生了更多的需求,使得对称加密无法满足:

  • 相同团体的成员必须共享相同的密钥
  • 为了更好的安全性,你可以在每两个人之间使用不同的密钥,但是这个方法不可扩展(管理密钥复杂度过高)

非对称加密(asymmetric encryption)又称为公钥加密(public key cryptography),它是另一种方法,使用两个密钥,而不是一个,其中一个密钥是私密的,另一个是公开的。这两个密钥之间存在一些特殊的数学关系,使得密钥具备一些有用的特性:

  • 如果你利用某人的公钥加密数据,那么只有他们对应的私钥能够解密
  • 如果某人用私钥加密数据,任何人都可以利用对应的公钥解开消息。该操作不提供机密性,但可以用作数字签名

非对称加密使得大规模团体的安全通信大幅简化。虽然公钥密码的属性非常有趣,但它却非常缓慢,不适用于数据量大的场景。因此,它往往被部署于进行身份验证和共享密钥的协商,这些密钥后续将用于快速的对称加密

RSA(得名于三个人的姓氏首字母:Ron Rivest、Adi Shamir和Leonard Adleman)是目前最普遍部署的非对称加密算法。现在推荐的RSA强度是 2048 位,强度等同于 112 位的对称密钥。

数字签名

数字签名(digital signature)是一个密码学方案。它使得验证一条电子消息或者一篇电子文档的真实性成为可能。前面描述的 MAC 就是一种电子签名,它可以利用事先安全交换的散列密钥验证真实性。虽然这种校验非常有用,但仍有不足,因为它仍然依赖于一个私有密钥。

借助公钥密码,数字签名可以与现实生活中的手写签名类似。我们可以利用公钥密码的非称性设计出一种算法,使用私钥对消息进行签名,并使用对应的公钥验证它。实际的方式依照选择的公钥密码体系而有所不同。

随机数生成

在密码学中,所有的安全性都依赖于生成随机数的质量。安全性构建于已知的算法和未知的密钥之上,而密钥最简单的形式就是非常长的随机数。随机数生成是不容易的,是因为计算机是十分善于预测的,它们会严格按照指令执行。

在计算机中,真随机数生成器(true random number generator,TRNG)是很难的,实际使用中依靠的是伪随机数生成器(pseudorandom number generator,PRNG)。当然,PRNG 也要利用少量真正的随机数使系统运转起来。这个过程被称为 种子设定(seeding)。利用种子,PRNG根据需要构造出无限数量的伪随机数。

普通用途的 PRNG 被常常用于编程,但它们并不适用于密码学,尽管其输出看起来就是随机的。加密安全伪随机数生成器(cryptographically secure pseudorandom number generator,CPRNG)是不可预测的 PRNG。这个性质对安全来说非常关键

协议

加密基元本身其实没什么用,诸如加密和散列算法。我们只有将这些元素组合成方案和协议,才能满足复杂的安全需求。一个协议大致要实现如下工作:

  • 在握手阶段开始,包括身份验证和密钥交换(生成后续 加密使用的密钥完整性验证所需要的密钥
  • 数据交换阶段,保证机密性(例如 AES 算法)和完整性(使用只有双方知道的散列密钥计算每个消息的 MAC)
  • 以关闭序列结束

攻击密码

复杂系统往往会受到多种方式的攻击,密码系统也不例外。人们一般都能很好地理解加密基元,因为它们相对直接,并且只完成一件工作。整体方案往往更容易遭受攻击,因为它们引入了额外的复杂性。此外,也存在针对协议实现(implementation)的攻击,即利用软件的 bug。所以,我们通常会说加密被绕过,而不是被攻击。这句话意味着使用的基元都很坚实,但软件体系不牢固。

衡量强度

我们使用攻破某个基元所需执行的操作数量衡量密码系统的强度,以安全位数来表示。实际使用的情况更复杂一些,因为并非所有操作的安全性都能同等度量。当我们讨论安全性时,提出诸如 针对谁的安全多长时间的安全 这种问题会更有价值。

中间人攻击

针对传输层安全性的攻击绝大多数来自中间人(man-in-the-middle,MITM)攻击。理论上,执行 MITM 攻击的最简单方法是加入网络,然后将受害者的通信重新路由到恶意节点。

有一个问题需要额外注意,即是否支持 前向保密(forward secrecy)。例如 TLS 中最常见的密钥交换算法是基于 RSA 算法的,在使用这种算法的系统中,密钥交换使用的 RSA密钥 也用于解密过去所有的会话。