ProtocolHTTPS

HTTPS 温故知新(四) —— 直观感受 TLS 握手流程(下)

在 HTTPS 开篇的文章中,笔者分析了 HTTPS 之所以安全的原因是因为 TLS 协议的存在。TLS 能保证信息安全和完整性的协议是记录层协议。(记录层协议在上一篇文章中详细分析了)。看完上篇文章的读者可能会感到疑惑,TLS 协议层加密的密钥是哪里来的呢?客户端和服务端究竟是如何协商 Security Parameters 加密参数的?这篇文章就来详细的分析一下 TLS 1.2 和 TLS 1.3 在 TLS 握手层上的异同点。

TLS 1.3 在 TLS 1.2 的基础上,针对 TLS 握手协议最大的改进在于提升速度和安全性。本篇文章会重点分析这两块。

先简述一下 TLS 1.3 的一些优化和改进:

  1. 减少握手等待时间,将握手时间从 2-RTT 降低到 1-RTT,并且增加 0-RTT 模式。

  2. 删除 RSA 密钥协商方式,静态的 Diffie-Hellman 密码套件也被删除了。因为 RSA 不支持前向加密性。TLS 1.3 只支持 (EC)DHE 的密钥协商算法。删除了 RSA 的方式以后,能有效预防心脏出血的攻击。所有基于公钥的密钥交换算法现在都能提供前向安全。TLS 1.3 规范中只支持 5 种密钥套件,TLS13-AES-256-GCM-SHA384、TLS13-CHACHA20-POLY1305-SHA256、TLS13-AES-128-GCM-SHA256、TLS13-AES-128-CCM-8-SHA256、TLS13-AES-128-CCM-SHA256,隐藏了非对称加密密钥协商算法,因为默认都是椭圆曲线密钥协商。

  3. 删除对称加密中,分组加密和 MAC 导致的一些隐患。在 TLS1.3 之前的版本中,选择的是 MAC-then-Encrypt 方式。但是这种方式带来了一些漏洞,例如 BEAST,一系列填充 oracle 漏洞(Lucky 13Lucky Microseconds)。CBC 模式和填充之间的交互也是 SSLv3 和一些 TLS 实现中广泛宣传的 POODLE 漏洞原因。在 TLS 1.3 中,已移除所有有安全隐患的密码和密码模式。你不能再使用 CBC 模式密码或不安全的流式密码,如 RC4 。TLS 1.3 中允许的唯一类型的对称加密是一种称为 AEAD(authenticated encryption with additional data)的新结构,它将加密性和完整性整合到一个无缝操作中。

  4. 在 TLS 1.3中,删除了 PKCS#1 v1.5 的支持,而选择更新的设计 RSA-PSS,提高了安全性。认证方面通过非对称算法,例如,RSA, 椭圆曲线数字签名算法(ECDSA),或 Edwards 曲线数字签名算法(EdDSA)完成,或通过一个对称的预共享密钥(PSK)。

  5. 在 TLS 1.2 的握手流程中,只有 ChangeCipherSpec 之后的消息会被加密,如 Finished 消息和 NewSessionTicket,其他的握手子消息不会加密。TLS 1.3 针对这个问题,对握手中大部分子消息全部进行加密处理。这样可以有效的预防 FREAK,LogJam 和 CurveSwap 这些降级攻击(降级攻击是中间人利用协商,强制使通信双方使用能被支持的最低强度的加密算法,从而暴力攻击计算出密钥,允许攻击者在握手时伪造 MAC)。在TLS 1.3中,这种类型的降级攻击是不可能的,因为服务器现在签署了整个握手,包括密码协商。

  1. TLS 1.3 完全禁止重协商。
  2. 密钥导出函数被重新设计,由 TLS 1.2 的 PRF 算法改为更加安全的 HKDF 算法。
  3. 废除 Session ID 和 Session Ticket 会话恢复方式,统一通过 PSK 的方式进行会话恢复,并在 NewSessionTicket 消息中添加过期时间和用于混淆时间的偏移值。

更多重要的变更,见笔者之前的文章 《TLS 1.3 Introduction》

七. TLS 1.3 首次握手流程

由于笔者在之前的某篇文章中已经将 TLS 1.3 握手流程的细节分析过了,所以这篇文章中不会像上篇分析 TLS 1.2 中那么详细,如果想了解 TLS 1.3 中细节,请阅读这篇文章《TLS 1.3 Handshake Protocol》。本篇文章主要从 wireshark 角度带读者直观感受 TLS 1.3 的握手流程。

在 TLS 1.3 中,存在 4 种密钥协商的方法:

  • Client 支持的加密套件列表。密码套件里面中能体现出 Client 支持的 AEAD 算法或者 HKDF 哈希对。
  • "supported_groups" 的扩展 和 "key_share" 扩展。“supported_groups” 这个扩展表明了 Client 支持的 (EC)DHE groups,"key_share" 扩展表明了 Client 是否包含了一些或者全部的(EC)DHE共享。
  • "signature_algorithms" 签名算法和 "signature_algorithms_cert" 签名证书算法的扩展。"signature_algorithms" 这个扩展展示了 Client 可以支持了签名算法有哪些。"signature_algorithms_cert" 这个扩展展示了具体证书的签名算法。
  • "pre_shared_key" 预共享密钥和 "psk_key_exchange_modes" 扩展。预共享密钥扩展包含了 Client 可以识别的对称密钥标识。"psk_key_exchange_modes" 扩展表明了可能可以和 psk 一起使用的密钥交换模式。

第一种方法是 TLS 1.2 中已经存在的,通过 ClientHello 中的 Cipher Suites 进行协商。第二种方法是 TLS 1.3 新增的,在 TLS 1.3 中完整握手就是通过这种方法实现的。第三种方法也是 TLS 1.3 新增的。这种方法没有第二种方法用的多。第四种方法也是 TLS 1.3 新增的,它将 TLS 1.2 中 Session ID 和 Session Ticket 废除以后,统一通过 PSK 的方式进行会话恢复。TLS 1.3 中的 0-RTT 模式也是通过 PSK 进行的。

TLS 1.3 完整握手的流程如下:

          Client                                               Server

          ClientHello
          + key_share               -------->
                                                          ServerHello
                                                          + key_share
                                                {EncryptedExtensions}
                                                {CertificateRequest*}
                                                       {Certificate*}
                                                 {CertificateVerify*}
                                                           {Finished}
                                    <--------     [Application Data*]
          {Certificate*}
          {CertificateVerify*}
          {Finished}                -------->
                                    <--------      [NewSessionTicket]
          [Application Data]        <------->      [Application Data]

在 TLS 1.3 握手中,主要能分为 3 个阶段:

  • 密钥交换:建立共享密钥数据并选择密码参数。在这个阶段之后所有的数据都会被加密。
  • Server 参数:建立其它的握手参数(Client 是否被认证,应用层协议支持等)。
  • 认证:认证 Server(并且选择性认证 Client),提供密钥确认和握手完整性。

密钥交换是 ClientHello 和 ServerHello,Server 参数是 EncryptedExtensions 和 CertificateRequest 消息。认证是 Certificate、CertificateVerify、Finished。

Client 发起完整握手流程从 ClientHello 开始:

      uint16 ProtocolVersion;
      opaque Random[32];

      uint8 CipherSuite[2];    /* Cryptographic suite selector */

      struct {
          ProtocolVersion legacy_version = 0x0303;    /* TLS v1.2 */
          Random random;
          opaque legacy_session_id<0..32>;
          CipherSuite cipher_suites<2..2^16-2>;
          opaque legacy_compression_methods<1..2^8-1>;
          Extension extensions<8..2^16-1>;
      } ClientHello;

在 ClientHello 结构体重,legacy_version = 0x0303,0x0303 是 TLS 1.2 的版本号,这个字段规定必须设置成这个值。其他字段和 TLS 1.2 含义相同,不再赘述了。

在 TLS 1.3 的 ClientHello 的 Extension 中,一定会有 supported_versions 这个字段,如果这个字段,ClientHello 会被解读成 TLS 1.2 的 ClientHello 消息。在 TLS 1.3 中 Server 根据 supported_versions 这个字段来决定是否协商 TLS 1.3 。

TLS 1.3 之所以能比 TLS 1.2 完整握手减少 1-RTT 的原因就在 ClientHello 中就已经包含了 (EC)DHE 所需要的密钥参数,不需要像 TLS 1.2 中额外用第二次 RTT 来进行 DH 协商参数。在 TLS 1.3 的 ClientHello 的 Extension 中,带有 key_share 扩展,这个扩展中包含了 Client 所能支持的 (EC)DHE 算法的密钥参数。并且 Extension 中还会有 supported_groups 扩展,这个扩展表明了 Client 支持的用于密钥交换的命名组。按照优先级从高到低。

Server 收到 ClientHello 以后,回应一条 ServerHello 消息:

      struct {
          ProtocolVersion legacy_version = 0x0303;    /* TLS v1.2 */
          Random random;
          opaque legacy_session_id_echo<0..32>;
          CipherSuite cipher_suite;
          uint8 legacy_compression_method = 0;
          Extension extensions<6..2^16-1>;
      } ServerHello;

在 ServerHello 消息中,legacy_version = 0x0303,这个也是 TLS 1.3 规范的规定,这个值必须固定填 0x0303(TLS 1.2)。Server 会读取 ClientHello 扩展中 "supported_versions" 扩展字段,如果 Client 能支持 TLS 1.3,那么 Server 在 ServerHello 扩展中的 "supported_versions" 扩展字段标识可以进行 TLS 1.3 的握手。

Server 在协商 TLS 1.3 之前的版本,必须要设置 ServerHello.version,不能发送 "supported_versions" 扩展。Server 在协商 TLS 1.3 版本时候,必须发送 "supported_versions" 扩展作为响应,并且扩展中要包含选择的 TLS 1.3 版本号(0x0304)。还要设置 ServerHello.legacy_version 为 0x0303(TLS 1.2)。Client 必须在处理 ServerHello 之前检查此扩展(尽管需要先解析 ServerHello 以便读取扩展名)。如果 "supported_versions" 扩展存在,Client 必须忽略 ServerHello.legacy_version 的值,只使用 "supported_versions" 中的值确定选择的版本。如果 ServerHello 中的 "supported_versions" 扩展包含了 Client 没有提供的版本,或者是包含了 TLS 1.3 之前的版本(本来是协商 TLS 1.3 的,却又包含了 TLS 1.3 之前的版本),Client 必须立即发送 "illegal_parameter" alert 消息中止握手。

在 ServerHello 的 Extension 中必须要有的这 2 个扩展,supported_versions、key_share(如果是 PSK 会话恢复方式,还必须包含 pre_shared_key)。key_share 扩展标识了 Server 选择了 Client 支持的哪一个椭圆曲线,以及它对应的密钥协商所需参数。这里有两种情况,一种是协商 Diffie-Hellman 参数,具体分析见这一章节,另外一种协商是 ECDHE 参数,具体分析见这一章节

key_share 传输过程中并没有使用私钥加密,整个过程的不可抵赖和防篡改是通过 CertificateVerify 验证 Server 持有私钥,以及 Finished 消息使用 HMAC 验证历史消息来确定的。

发完 ServerHello 消息以后,Server 会继续发送 EncryptedExtensions 和 CertificateRequest 消息,如果对 Client 不进行认证,就不需要发送 CertificateRequest 消息。上面这 2 条消息都是加密的,通过 server_handshake_traffic_secret 中派生的密钥加密的。

early secret 和 ecdhe secret 导出 server_handshake_traffic_secret。再从 server_handshake_traffic_secret中导出 key 和 iv,使用该 key 和 iv 对 Server hello 之后的握手消息加密。同样的计算 client_handshake_traffic_secret,使用对应的 key 和 iv 进行解密后续的握手消息。

       Early Secret = HKDF-Extract(salt, IKM) = HKDF-Extract(0, PSK) = HKDF-Extract(0, 0)
       Handshake Secret = HKDF-Extract(salt, IKM) = HKDF-Extract(Derive-Secret(Early Secret, "derived", ""), (EC)DHE)

       client_handshake_traffic_secret = Derive-Secret(Handshake Secret, "c hs traffic", ClientHello...ServerHello)
       server_handshake_traffic_secret = Derive-Secret(Handshake Secret, "s hs traffic", ClientHello...ServerHello)

       client_write_key = HKDF-Expand-Label(client_handshake_traffic_secret, "key", "", key_length)
       client_write_iv  = HKDF-Expand-Label(client_handshake_traffic_secret, "iv", "", iv_length)

       server_write_key = HKDF-Expand-Label(server_handshake_traffic_secret, "key", "", key_length)
       server_write_iv  = HKDF-Expand-Label(server_handshake_traffic_secret, "iv", "", iv_length)

EncryptedExtensions 消息包含应该被保护的扩展。即,任何不需要建立加密上下文但不与各个证书相互关联的扩展。比如 ALPN 扩展。Client 必须检查 EncryptedExtensions 消息中是否存在任何禁止的扩展,如果有发现禁止的扩展,必须立即用 "illegal_parameter" alert 消息中止握手。

   Structure of this message:

      struct {
          Extension extensions<0..2^16-1>;
      } EncryptedExtensions;
  • extensions:
    扩展列表。

CertificateRequest 消息细节,见这一章节

接下来 Server 还要继续发送 Certificate、CertificateVerify、Finished 消息。这 3 条消息是握手消息的最后 3 条消息。这 3 条消息使用从 sender_handshake_traffic_secret 派生出来的密钥进