Featured image of post 深入理解 SSL/TLS

深入理解 SSL/TLS

本文参照RFC5246 TLSv1.2 深入描述了TLS的细节内容。

引子

 过去几个月一直在和 HTTPS 打交道,准确的说是和 HTTPS 的实现(OpenSSL,GoTLS等)打交道,对 HTTPS 相关的知识比较碎片化而且不够深入。于是,乘着最近有点时间,决定深入的理解一下 HTTPS 的方方面面,主要是阅读了《深入浅出HTTPS:从原理到实战》和《HTTPS 权威指南》两本书,以及翻阅了RFC 5246 规范的内容(TLS v1.2)。从头到尾的看下来,HTTPS 涉及的知识确实很多,单论其中的密码学知识,随便一个密码算法背后都有巨大的知识待消化。(如果有时间,我一定好好深入学习一下密码学。毕竟,只要还在信息安全行业,总有一天是需要啃下这块骨头的。

SSL/TLS 综述

    本章节将分别介绍 SSL/TLS 致力于解决问题的目标和历史发展过程,并解释了 OpenSSL、HTTPS 与 SSL/TLS 之间的关系,以及目前 SSL/TLS 协议流行的一些实现库。

历史发展

1994年,Netscape 为了解决 HTTP 的安全问题,创建了 SSL 协议,这时是 SSL1.0 版本即 SSL v1,这个版本从未公开发布和广泛使用,因为在设计阶段就存在严重安全漏洞,后被抛弃;

1995年,NetScape 发行 SSL2.0版,即SSL v2,但很快发现该版本也存在严重的安全漏洞,包括容易受到中间人攻击的风险,后逐渐被淘汰;

1996年,SSL 3.0 版即 SSL v3 问世,修复了 SSL 2.0 的一些安全漏洞,并得到广泛应用;

1999年,为了解决 SSL 存在的安全问题,ISOC 接替 NetScape,发布了 SSL3.0 的升级版即TLS 1.0,TLS 1.0 基本上与 SSL 3.0 兼容,但对一些安全性问题进行了修复和加强,尽管与SSL 3相比,版本修改并不大,但是为了取悦Microsoft,协议还是进行了更名

2006年和2008年,TLS 进行了两次升级,分别为 TLS1.1 和 TLS1.2 ,进一步改进了安全性和性能,并增加了新的加密算法和协议扩展;

2018年,TLS1.3 正式发布,是目前最新版本,TLS 1.3 在安全性、性能和隐私方面都有显著改进,同时简化了握手过程,提高了连接安全性。

值得一提的是,TLS 1.0 通常被标识为 SSL 3.1,TLS 1.1 为 SSL 3.2,TLS 1.2为 SSL 3.3,TLS1.3 为 SSL3.4,而 GmSSL(TLCP)则为了避免冲突,标识为0x0101

在OpenSSL 和 GmSSL 的源码中我们都能找到类似的定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// OpenSSL: include/openssl/tls1.h
# define TLS1_VERSION                    0x0301
# define TLS1_1_VERSION                  0x0302
# define TLS1_2_VERSION                  0x0303
# define TLS1_3_VERSION                  0x0304
# define TLS_MAX_VERSION                 TLS1_3_VERSION  

// GmSSL : include/gmssl/tls.h 
typedef enum {
    TLS_protocol_tlcp            = 0x0101,
    TLS_protocol_ssl2            = 0x0200,
    TLS_protocol_ssl3            = 0x0300,
    TLS_protocol_tls1            = 0x0301,
    TLS_protocol_tls11            = 0x0302,
    TLS_protocol_tls12            = 0x0303,
    TLS_protocol_tls13            = 0x0304,
    TLS_protocol_dtls1            = 0xfeff, // {254, 255}
    TLS_protocol_dtls12            = 0xfefd, // {254, 253}
} TLS_PROTOCOL;

解决目标

从网络信息安全的基本属性出发,SSL/TLS 本质上还是在解决 CIA 问题:

  • 机密性:传输内容不得泄露给未经授权的第三方或被窃取 =》加密传输

  • 完整性:防止传输内容被篡改或损坏 =》信息摘要和数字签名

  • 可用性:其实 SSL/TLS 的目标主要是保障机密性和完整性啦….

都知道 SSL/TLS 一开始是为了解决 HTTP 的安全问题,那么它在网络协议栈的中位置如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
+----------------------+     +----------------------+
|        HTTP          |     |        HTTP          |
+-----+------------+---+     +-----+------------+---+
      v            |               v            |
+-----+------+     |         +-----+------+     |
| SSL/TLS    |     |         | SSL/TLS    |     |
+-----+------+     |         +-----+------+     |
      |            |               |            |
+-----v------------v---+     +-----v------------v---+
|      TCP Layer       |     |      Linux API       |
+----------------------+     +----------------------+
|      IP Layer        |     |     Socket Layer     |
+----------------------+     +----------------------+
|      Link Layer      |     |     TCP Layer        |
+----------------------+     +----------------------+
                             |     IP Layer         |
                             +----------------------+
                             |     Network Driver   |
                             +----------------------+
 SSL/TLS在网络栈中的位置       Linux 下HTTPS/HTTP 网络协议栈

可以看到在 HTTPS 协议中,SSL/TLS 位于应用层协议和 TCP 之间,构建在 TCP 之上,由TCP 协议保证数据传输的可靠性,任何数据到达TCP之前,都经过 SSL/TLS 协议处理。

SSL/TLS 协议的目标如下:

  • 加密安全:数据加密传输,不可伪造和篡改;

  • 互操作性:SSL/TLS 是标准的,任何开发者基于 RFC 设计规范都可以实现;

  • 可扩展性:允许双方协商都支持的密码学算法;

  • 效率:解决方案必须是高效的,SSL/TLS 涉及众多密码运算,带来一定开销。

OpenSSL、HTTPS与SSL/TLS的关系

SSL/TLS协议是设计规范,OpenSSL是其众多实现中的一种,比如还有LibreSSL、BoringSSL等。

OpenSSL是一个底层密码库,封装了所有的密码学算法、证书管理、TLS/SSL协议实现。

HTTP和SSL/TLS协议组合在一起就是HTTPS, HTTPS等同于HTTP+SSL/TLS,就是说HTTPS拥有HTTP所有的特征,并且HTTP消息由SSL/TLS协议进行安全保护。

SSL/TLS的一些实现

SSL/TLS协议目前在不同平台、不同语言有众多实现,不过最经典的当属OpenSSL:

实现开发者说明
OpenSSLOpenSSL 工程组SSL/TLS 协议最流行的一个实现
BoringSSLGoogleOpenSSL的一个分支,主要用在Google的产品上,比如Android、Chrome/Chromium
LibreSSLOpenBSD 工程组著名的“心脏出血”安全问题出现后,从OpenSSL fork并重构的分支,代码以实现安全、简洁为目标,也称为“OpenSSL 2.0”
GnuTLSGnuTLS 工程组由于OpenSSL库不兼容GPL许可证,所以GNU项目自行实现了TLS/SSL协议
SChannel微软用于Windows产品的一个SSL/TLS协议
Secure TransportApple用于OS X和iOS的一个SSL/TLS协议实现
mbed TLSARM基于嵌入式设备的SSL/TLS协议实现
NetWork Security Services(NSS)MozillaMozilla开发的密码库,其中包含了SSL/TLS协议实现,用于Mozilla很多产品中
Java Security Socket Extension(JSSE)OracleOracle使用java语言实现的SSL/TLS协议
GoTLSGoGo语言的SSL/TLS协议实现
rustlsRustRust语言的SSL/TLS协议实现
GmSSLGmSSL 工程组国密SSL指的是采用国密算法(SM1/2/3/4等),符合国密标准(GM/T0024-2014和GB/T38636-2020)的安全传输协议。
TongSuo阿里巴巴原名“BaBaSSL”,铜锁/Tongsuo是一个提供现代密码学算法和安全通信协议的开源基础密码库

密码学知识

关于Alice 与 Bob的故事:

密码学中的李雷和韩梅梅。在密码学中,经常会使用 Alice 和 Bob 来举例子,通常 Alice把信息发送给 Bob。同样的,还有窃听者Eve,恶意攻击者 Mallory……

本章节介绍和SSL/TLS协议相关的密码学算法,但不做深入的算法细节分析(要是深入加密算法原理,每一个细节拎出来都足以令让人耗费巨大精力)。

基本目标

密码学的四个基本目标:

  • 机密性:在密码学中,对称加密算法和非对称加密算法都能够保证机密性;

  • 完整性:在密码学中,主要使用消息验证码(MAC)算法保证完整性;

  • 身份认证:在密码学中,一般使用数字签名技术确认身份;

  • 不可抵赖性:在密码学中,数字签名技术能够避免抵赖。

哈希算法

哈希算法可以用下列的公式描述:

$$ 摘要/散列值/指纹=hash(消息) $$

hash表示特定的Hash算法,消息就是输入值,哈希算法的主要特性如下:

  • 相同的消息总是得到相同的摘要值,特定的哈希算法,不同长度的消息最终输出的摘要值长度是相同的;

  • 哈希运算总是非常迅速;

  • 单向性,摘要值是不可逆的,想要通过摘要值逆向原始消息几乎是不同可能的,通常对哈希算法的攻击有暴力攻击、彩虹表和字典攻击等方式;

  • 原始消息一旦修改,即便是轻微的改动都会引起摘要值的变化;

  • 唯一性,几乎很难找出两个不同的消息的摘要值是相同的。

如下是哈希算法的一下实际应用:

  • 文件比较:比如通过比对文件的MD5值检查网络上下载的文件是否经过篡改;

  • 身份校验:系统将用户的口令经过哈希计算后的摘要值,存储到数据库,避免原始密码的泄露(但这种方式很显然也有安全风险)。

MD5

MD5是一种比较常用的哈希算法,摘要值长度固定是128比特,MD5算法目前被证明已经不安全了,MD5算法违反了强抗碰撞性原则,但是还没有破坏单一性原则。

SHA

SHA(Secure Hash Algorithms)算法是美国国家标准与技术研究院(NIST)指定的算法,SHA算法不是一个算法,而是一组算法,主要分为三类算法。

  • SHA-1:输出的摘要长度为160比特,目前该算法已被证明是不安全的;

  • SHA-2:SHA-256、SHA512、SHA-224、SHA-284;

  • SHA-3:SHA3-256、SHA3-512、SHA3-224、SHA3-384。

对称加密算法

在密码学中,用于数据加密的算法主要有两种,分别是对称加密算法(Symmetric-key Algorithms)和非对称加密算法(Asymmetrical Cryptography)。

$$ 密文=E(明文,算法,密钥)\ 明文=D(密文,算法,密钥) $$

块密码算法

如下是常见的块密码算法(block ciphers):

算法密钥长度分组长度说明
AES128、192、256比特128比特Advanced Encryption Standard
DES56比特64比特Data Encryption Standard
3DES/TDEA128或者168比特64比特Triple Data Encryption Algorithm
IDEA128、192、256比特128比特International Data Encryption Algorithm

流密码算法

算法密钥长度说明
RC4可变密钥长度,建议2048比特目前已被证明不安全

非对称加密算法

非对称加密算法也称公开密钥算法,该算法不是一个算法而是一组算法。与对称加密算法有如下差异:

  • 对称加密算法主要用于加密与解密。非对称算法功能较多,可用于加解密、密钥协商、数字签名等;

  • 与对称加密算法加密和解密都使用相同的密钥不同,非对称加密算法的密钥是一对,分别是公钥(public key )和私钥(private key),公钥是公开的,私钥只有持有者知道;

  • 非对称加密算法运算速度缓慢,不会用于实际数据的加解密而是在密钥协商和数字签名中发挥作用。

非对称加密算法最经典的算法当然是RSA算法,由Ron Rivest、Adi Shamir、Leonard Adleman三个人创建。

RSA密钥文件内部结构大概如下:

1
2
3
4
5
6
7
8
typedef struct rsa_st
{
    BIGNUM p;
    BIGNUM q;
    BIGNUM n;
    BIGNUM e;
    BIGNUM d;
} RSA;

生成密钥对的过程:

  • 首先选取两个很大的质数p和q;

  • 求这两个数的乘积n;

  • 取一个公开指数e,这个数的值小于(p-1)(q-1), e对应的值和(p-1)(q-1)的值互质;

  • e和n组合起来就相当于公钥。n值的长度就相当于密钥对的长度;

  • 通过e、p、q能够计算出私钥d, d和n组合起来就是私钥。

RSA算法加解密如下:

$$ C = M^e (mod n) \ M = C^d (mod n) $$

公开密钥算法的标准称为PKCS(Public Key Cryptography Standards),这个标准由很多的子标准组成,指导使用者正确地使用公开密钥算法。

子标准子标准全称说明
PKCS #1RSA Crytography StandardRFC 8017,主要描述了RSA密钥对的关系,描述RSA算法如何进行加密解密,生成和验证签名
PKCS #3Diffe-Hellman Key Agreement Standard描述了一种公开密钥算法,主要用于在不安全的通道中协商出一个安全密钥
PKCS #5Password-based Encryption StandardRFC 8018,基于口令的加密标准
PKCS #6Extend-Certificate Syntax Standard描述 X.509 证书扩展的一个标准
PKCS #7Cryptographic Message Syntax Standard密码学消息语法标准
PKCS #8Priate-Key Infomation Syntax StandardRFC 5958,用于定义私钥的标准
PKCS #9Selected Attribute TypesRFC 2985,定义其他一些标准的可选类型属性
PKCS #10Certification Request StandardRFC 2986,证书CSR请求的标准
PKCS #11Cryptographic Token Interface产生密码学令牌的一个标准
PKCS #12Personal information Exchange Syntax StandardRFC 7292,主要用来保存私钥和证书的一种标准
PKCS #13Elliptic Curve Cryptography StandardECC 椭圆曲线的一个标准,是公开密钥算法非常重要的补充
PKCS #14Pseudo-random Number Generation伪随机生成器标准

消息验证码

哈希函数可以用于验证数据完整性,但尽在数据的哈希散列与数据本身分开传输的条件下如此。否则攻击可以同时修改数据和摘要值,从而轻易地避开检测。消息验证码(message 哈authentication code,MAC),是以身份验证扩展了哈希函数的密码学函数。

$$ MAC值=mac(消息,密钥) $$

在密码学中,MAC算法有两种形式,分别是CBC-MAC算法和HMAC算法。CBC-MAC算法从块密码算法的CBC分组模式演变而来,简单地说就是最后一个密文分组的值就是MAC值,在HTTP中应用最多的MAC算法是HMAC算法。

HMAC(Hash-based Message Authentication Code)算法使用Hash算法作为加密基元,HMAC结合Hash算法有多种变种,比如HMAC-SHA-1、HMAC-SHA256、HMAC-SHA512。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
Function hmac
  Inputs:
      key:       HMAC算法的密钥
      message:   原始消息
      hash:      HMAC算法采用的加密基元,比如可以是SHA-1 摘要算法
      blockSize:  Hash算法的分组长度,比如SHA-1 算法的分组长度是 160 比特

# 确保密钥长度等于分组长度
if (length(key) > blockSize) then
  key ← hash(key)
if (length(key)
  key ← Pad(key, blockSize)

    # 0x5c * blockSize的值称为opad,对 0x5c迭代多次得到,长度等同于分组长度
    # 0x36 * blockSize的值称为ipad,对 0x36 迭代多次得到,长度等同于分组长度
    o_key_pad = key xor [0x5c * blockSize]
    i_key_pad = key xor [0x36 * blockSize]

    # 进行多次Hash运算得到MAC值
    return hash(o_key_pad ∥hash(i_key_pad ∥message))

数字签名

数字签名(digital signature)主要解决的是抵赖问题,即解决如何证明消息是对方发送的。数字签名技术主要有以下特点:

  • 防篡改:数据不会被修改;

  • 防抵赖:消息签署者不能抵赖;

  • 防伪造:发送的消息不能够伪造。

数字签名使用私钥对消息的摘要值进行加密,与消息一同发送给接收方,接受方使用发送者的公钥对加密的摘要值解密,并于计算的消息摘要值比对,验证消息的可靠性。

数字签名技术的标准是DSS(Digital Signature Standard),其标准算法就是DSA签名算法(Digital Signature Algorithm),它是美国国家标准技术研究所(NIST)在1991年提出的签名算法,只能进行签名,不能进行加密解密。

1
2
3
4
5
6
7
8
typedef struct dsa_st
{
    BIGNUM p;
    BIGNUM q;
    BIGNUM g;
    BIGNUM pub_key;
    BIGNUM priv_key;
} DSA;

国密算法

国密算法是指由中国国家密码管理局发布的密码算法标准,旨在保障国家信息安全。目前,国家密码管理局已发布了一系列国产商用密码标准算法,包括SM1(SCB2)、SM2、SM3、SM4、SM7、SM9以及祖冲之密码算法(ZUC)等。

密码算法说明
SM1对称算法,
SM2基于ECC(Elliptic Curve Cryptography)椭圆曲线的非对称加密算法
SM3杂凑算法、哈希、信息摘要、杂凑值为256比特,消息分组长度为512比特
SM4对称加密,分组长度和密钥长度都为128比特
SM9标识密码,在商用密码体系中,SM9主要用于用户的身份认证
ZUC序列密码标准

国密SSL/TLCP

其实就是使用国密算法的SSL/TLS(可能有些小细节会略微不同)。

通常,国密SSL指的是采用国密算法(SM1/2/3/4等),符合国密标准(GM/T0024-2014和GB/T38636-2020)的安全传输协议。

国密SSL 最早并不是作为一个独立的标准存在,而是定义在虚拟私有网络的行业技术规范中,即《GM/T 0024-2014 SSL VPN技术规范》。国密SSL 参照了 TLS1.0规范,但两者并不兼容,主要有以下不同:

  • 协议的版本号不同,握手和加密协议细节不同;

  • 协议采用的主要是国密SM2/SM3/SM4算法,不同于TLS采用的国际密码算法;

  • 采用的是SM2双证书体系。

随着网络环境日益复杂和国家对网络信息安全的重视,国密SSL 从行业的标准上升为单独的国家标准即《GB/T 38636-2020 信息安全技术 传输层密码协议》,于 2020 年 4 月发布,在2020 年11 月实施,这时候叫传输层密码协议 Transport Layer Cryptography Protocol(TLCP)。TLCP 协议采用 SM 系列密码算法和数字证书等密码技术保障传输层的机密性、完整性、身份认证和抗攻击。

因而,对于国内开发者而言(尤其信创领域),现在 SSL/TLS 又多了一个 TLCP 需要了解和熟悉。

SSL/TLS 协议原理

本章节主要介绍 SSL/TLS 协议的细节内容,虽然是 SSL/TLS 协议但是纵观其发展历史,目前其实使用的大多数都是 TLS。,因而以TLS 1.2协议作为示例。

概述

SSL/TLS 协议位于应用层协议和传输层 TCP 协议之间,协议本身分两层:

  • 接近应用层的高层协议为握手协议(TLS Handshaking Protocols);
  • 接近TCP 协议的底层协议是记录层协议(TLS Record Protocol)。

可以注意到握手协议的 Protocol 是加 s 后缀的,其由四个子协议构成:

  • 握手协议(TLS Handshaking Protocol)
  • 警报协议(Alert Protocol)
  • 应用数据协议(Application Data Protocol)
  • 密钥规格变更协议 (Change Cipher Spec Protocol)
1
2
3
4
5
+----------+-----------+-----------------+-------------+
| 握手协议  |   警报协议  |  密钥规格变更协议 | 应用数据协议  |
+----------+-----------+-----------------+-------------+
|                     TLS 记录层协议                     |
+------------------------------------------------------+

对于分层结构而言,透明和职责明确是最显著的特点,而对而 SSL/TLS:

  • 记录层的职责就是对上层握手层子协议的包装,然后加解密传递到下层TCP;

  • 而握手层则为记录层提供加解密需要的必要特性(参数),通过握手、协商和密钥交换得出。

记录层协议

TLS记录层协议会封装所有的握手协议(包含其他三个子协议),TLS记录层协议有固定的消息头格式:

  • 消息类型 1 个字节,消息类型有 4 种,即高层握手协议的 4 个子协议:

    消息头类型
    CHANGE_CIPHER_SPEC0x14
    ALERT0x15
    HANDSHAKE0x16
    APPLICATION_DATA0x17
  • 版本号即 SSL/TLS 协议的版本号,由两个字节组成,比如 v1.3 即 0x0304

  • 消息长度由两个字节组成,长度也包含消息头的长度。

每个TLS记录层协议消息头由三部分组成,消息头的长度固定是5个字节。

1
2
3
4
5
6
7
8
9
消息类型     版本号     消息长度
    +-+        +        +-+
      |        |        |
   +--v---+----v----+---v---+
   | type | version | length|
   +------+---------+-------+
   1 byte  2 bytes   2 bytes

   记录层协议消息头

从结构上看,TLS 记录层协议作用如下:

  • 封装和处理所有上层的子协议的消息,添加消息头;

  • 对上层的应用层协议进行密码学保护, 即只关注数据传输和加密,而将所有其他特性转交给子协议。

握手协议

握手协议是 SSL/TLS 种最重要且最复杂的部分,其主要工作是客户端和服务端协商出双方都认可(支持)的密码套件,基于密码套件协商出密钥块,TLS 记录层协议进行加解密的密钥就是由握手过程产生的。根据配置和支持的协议扩展不同,通常协商交换的过程有:

  • 完整的握手,对服务器进行身份验证;

  • 恢复之前的会话采用的简短握手;

  • 对客户端和服务器都进行身份验证的握手。

握手协议也有固定的消息格式,握手协议的消息最终会由TLS记录层协议处理,添加消息头,握手协议的消息格式如下:

  • 类型,一个字节;

    子消息类型
    HELLO_REQUEST0x00
    CLIENT_HELLO0x01
    SERVER_HELLO0x02
    CERTIFICATE0x0b
    SERVER_KEY_EXCHANGE0x0c
    CERTIFACATE_REQUEST0x0d
    SERVER_DONE0x0e
    CERTIFICATE_VERIFY0x0f
    CLIENT_KEY_EXCHANGE0x10
    FINSHED0x14
  • 消息长度 3 个字节;

  • 可变长的消息内容。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
+--------------------------SSL/TLS 握手协议 -----------------------------+
|                                                                       |
|                                      消息类型   长度     消息内容        |
|                                       +-+        +        +-+         |
|                                       |          |        |           |
|                                     +-v----+-----v----+---v-----+     |
|  TLS 握手层                          | type | length   | content |     |
|                                     +------+----------+---------+     |
|                                       1 byte  3 btyes   variable .... |
|            +------+---------+-----------------------------------+     |
|  TLS 记录层 | 0x16 | version | length| ..........................|     |
|            +------+---------+-----------------------------------+     |
|             1 byte  2 bytes   2 bytes                                 |
+-----------------------------------------------------------------------+

struct {
   HandshakeType msg_type;
   uint24 length;
   HandshakeMessage message;
} Handshake;

握手协议的一条消息可以由多个子消息组成,多个子消息最终由 TLS 记录层协议封装成一条消息(或多条消息)交给 TCP 处理。

警报协议

警报协议、告警协议……

警报的目的是以简单的通知机制告知对端通信出现异常状况。在客户端和服务器端建立一条连接后,会通过握手协议协商密钥块,在协商和认证过程中,可能会产生错误。错误信息由警报协议处理,警告协议有多个错误,某些错误可能是致命的,会直接终止客户端和服务器端的连接。

警报协议的消息格式如下,由两部分组成:

  • 告警错误级别;

  • 告警协议的详细描述信息。

1
2
3
4
5
# 警告协议由两部分组成
struct {
    AlertLevel level;
    AlertDescription description;
} Alert;

告警错误级别为 fatal 的消息会立即终止当前连接并使会话失效,发送警告通知的一端不会主动终止连接,而是交由接收端通过发送它自己的严重警报对该警告自行作出反应。

应用数据协议

应用数据协议携带着应用消息,只以TLS的角度考虑的话,这些就是数据缓冲区。记录层使用当前连接安全参数对这些消息进行打包、碎片整理和加密。

密钥规格变更协议

密钥规格变更协议的作用是通知 TLS 记录层协议其加/解密所需要的密钥块已经准备好了,一个TLS连接一旦客户端和服务器端发出了Change Cipher Spec子协议,TLS记录层协议就可以对应用层协议(Application Data协议)进行加密保护了。

可以看到SSL/TLS 协议的知识体系十分庞大,以下是一些可以辅助学习的相关 RFC 文档:

文档名称RFC 编号说明
The Transport Layer Security (TLS) Protocol Version 1.3RFC 8446TLS v1.3目前最新版
The Transport Layer Security (TLS) Protocol Version 1.2RFC 5246TLS v1.2
Datagram Transport Layer Security Version 1.2RFC 6347DTLS协议实现的RFC 文档
TLS Extensions: Extension DefinitionsRFC 6066SSL/TLS协议扩展文档,主要描述如何定义扩展
Internet X.509 Public Key infrastructure Certificate and Certificate Revocation List(CRL) ProfileRFC 5280学习X.509证书最重要的文档
HTTP Over TLSRFC 2818主要描述HTTP 如何结合SSL/TLS协议
TLS Session Resumption without Server-Side StateRFC 5077介绍Session Ticket会话恢复
HTTP Strict Transport Security(HSTS)RFC 6797HSTS是完善HTTPS最重要的一个特性
Internet Security Glossary,Version 2RFC 4949前向加密性
X.509 Internet Public Key Infrastructure Online Certificate Suites for TLSRFC 6960OCSP
Elliptic Curve Crtptography(ECC)Cipher Suites for TLSRFC 4492ECC

记录层协议细节

安全参数

安全参数由 TLS 握手层协议确定,并作为参数提供给 TLS 记录层,以便初始化连接状态。安全参数的结构定义如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
enum { null(0), (255) } CompressionMethod;
enum { server, client } ConnectionEnd;
enum { tls_prf_sha256 } PRFAlgorithm;
enum { null, rc4, 3des, aes } BulkCipherAlgorithm;
enum { stream, block, aead } CipherType;
enum { null, hmac_md5, hmac_sha1, hmac_sha256, hmac_sha384,
    hmac_sha512} MACAlgorithm;
struct {
    ConnectionEnd          entity;
    PRFAlgorithm           prf_algorithm;
    BulkCipherAlgorithm    bulk_cipher_algorithm;
    CipherType             cipher_type;
    uint8                  enc_key_length;
    uint8                  block_length;
    uint8                  fixed_iv_length;
    uint8                  record_iv_length;
    MACAlgorithm           mac_algorithm;
    uint8                  mac_length;
    uint8                  mac_key_length;
    CompressionMethod      compression_algorithm;
    opaque                 master_secret[48];
    opaque                 client_random[32];
    opaque                 server_random[32];
} SecurityParameters;
  • entity:表示操作方是客户端还是服务器端;

  • prf_algorithm:伪随机函数,在握手协议中,使用该函数将预备主密钥转换为主密钥,主密钥转换为密钥块,在TLS v1.2中默认使用的是SHA-256算法;

  • bulk encryption algorithm:加密函数,比如可以选择3des、aes算法等;

  • client random:在TLS/SSL协议中,在连接阶段,客户端会传递一个随机数,长度是32个字节;

  • server random:和client random一样,服务器端也会向客户端传递一个32字节的随机数;

  • cipher_type:相当于最终TLS记录层协议使用的加密模式,在TLS记录层协议中,有三种模式stream, block, aead;

  • enc_key_length:加密算法密钥的长度;

  • block_length:加密数据的长度;

  • mac_algorithm:指定的MAC算法;

  • mac_length:MAC值的长度;

  • mac_key_length:MAC算法使用的密钥长度;

  • compression_algorithm:TLS记录层协议使用的压缩算法,一般不启用;

  • master_secret:主密钥。

在 SSL/TLS 协议中,每个 TLS 连接都有一个连接状态的概念,连接状态有4个:

  • 待读状态(pending read states)

  • 待读状态(pending read states)

  • 可读状态(current read states)

  • 可写状态(current write states)

在客户端和服务器端初始化连接的时候,客户端和服务器的连接状态是待读状态和待写状态(客户端和服务器端分别保持自己的连接状态)。一旦所有的加密参数已经准备好,那么连接状态进入可读状态和可写状态,对于TLS记录层协议来说,只有连接状态是可读状态和可写状态,才会进行数据加密和完整性保护

对于 TLS 记录层协议而言,每个连接状态有4个部分组成:

  • compression state,压缩状态,一般不启用压缩;

  • cipher state,每个连接使用的加密算法(可以有三种加密模式)和加密算法使用的密钥块;

  • MAC key,每个连接的MAC密钥;

  • 序列号,每个TLS记录层协议消息都有一个序列号,客户端和服务器各自维护一个序列号,序列号本身并不包含在TLS记录层协议消息中。

处理步骤

接下来看看 TLS 记录层协议是如何传输应用数据的。经过上层协议完成握手之后,TLS 记录层协议对应用数据提供密码学保护所需的安全参数信息可以确定。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
+-----------+
| 应用层数据  | +----> 1.数据分块
+-----------+
                     +--------------+
                     |              |
                     +--------------+

                     2.数据压缩
                     +--------------+-----+
                     |              | MAC | 增加消息验证码
                     +--------------+-----+

                     3.数据加密
                     +--------------------+
                     |      密文          |
                     +--------------------+

                     4.增加消息头
             +-------+--------------------+
             |header |      密文          |
             +-------+--------------------+

所有上层协议的数据进入 TLS 记录层协议后,首先需要将消息拆分成块,每个块的大小小于214字节,结构如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
struct {
    uint8 major;
    uint8 minor;
} ProtocolVersion;

enum {
    change_cipher_spec(20), alert(21), handshake(22),
    application_data(23), (255)
} ContentType;

struct {
    ContentType type;
    ProtocolVersion version;
    uint16 length;
    opaque fragment[TLSPlaintext.length];
} TLSPlaintext;

TLSPlaintext 是 TLS 记录层协议分块后的数据结构。type 是高层协议(握手协议)的类型,version 是SSL/TLS 协议版本号,fragment 相当于高层协议的消息,length 是 TLS 记录层协议分块后的大小,即 length 就是 fragment 的长度。

接下来是数据压缩,一般在 SSL/TLS 协议中不启用压缩算法。数据压缩后的结构如下:

1
2
3
4
5
6
struct {
    ContentType type;
    ProtocolVersion version;
    uint16 length;
    opaque fragment[TLSCompressed.length];
} TLSCompressed;

数据经过压缩步骤之后,接下来是具体的加密和完整性处理。TLS 记录层协议将TLSCompressed 结构转换为 TLSCiphertext 结构。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct {
    ContentType type;
    ProtocolVersion version;
    uint16 length;
    select (SecurityParameters.cipher_type) {
        case stream: GenericStreamCipher;
        case block:  GenericBlockCipher;
        case aead:   GenericAEADCipher;
    } fragment;
} TLSCiphertext;

主要关注其中的 cipher_type 字段:可以看到其共有 3 种类型,stream、block、aead。

握手协议细节

TLS 记录协议中安全参数(security parameterd)的值都是在 SSL/TLS 握手协议中填充完成的,对应的值是由客户端和服务端共同协商而得。对于一个完整的握手会话,通常需要客户端与服务端经过几个来回才能协商出安全参数。

和安全参数关联最大的概念是密码套件,客户端和服务端会列举出支持的密码套件,然后选择双方都支持的密码套件,基于密码套件协商出所有的安全参数。安全参数中最重要是的主密钥(master secret)。

下面是一个完整的SSL/TLS 协议握手过程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
  Client                                               Server

      ClientHello                  -------->
                                                      ServerHello
                                                     Certificate*
                                               ServerKeyExchange*
                                              CertificateRequest*
                                   <--------      ServerHelloDone
      Certificate*
      ClientKeyExchange
      CertificateVerify*
      [ChangeCipherSpec]
      Finished                     -------->
                                               [ChangeCipherSpec]
                                   <--------             Finished
      Application Data             <------->     Application Data

对于上面的流程,需要补充的是:

  • ChangeCipherSpec 并不是握手协议的一部分;

  • 星号标记表示对应的子消息是否发送取决于不同的密码套件,比如 RSA 密码套件不会出现ServerKeyExchange子消息。

握手过程中,客户端和服务端主要进行以下四个步骤:

  • 交换各自支持的功能(密码套件等),对需要的连接参数达成一致;

  • 验证出示的证书,或使用其他方式进行身份验证;

  • 对将用于保护会话的共享主密钥达成一致;

  • 验证握手消息并未被第三方修改。

以下是一张 cloudflare 绘制的 SSL/TLS 握手流程图,我们只需要关注 Alice 和 Bob 的对话即可:

  1. 首先,从客户端发起握手连接,将自身支持的功能(比如密码套件)发送给服务端;

  2. 然后,服务端从子消息中检查会话恢复,如果是新的连接则从两者都支持的密码套件中选取一个,并与(server random)、证书链等一同发送给客户端;

  3. 服务端通知对方自己完成了协商过程;

  4. 客户端验证证书有效,生成新的随机数(premaster secret),并使用证书中的公钥加密该随机数;

  5. 客户端切换加密方式并通知服务器;

  6. 服务器切换加密方式并通知客户端;

  7. 最后,客户端和服务端校验的Finished子消息,避免握手协议的消息被篡改;

  8. 然后TLS 记录层协议开始使用握手协议提供加密参数传输数据。

注意,握手协议中的子消息必须按照特定的顺序发送,对于客户端和服务器端来说,如果收不到特定顺序的消息就会产生一个致命的错误。

ClientHello

ClientHello是握手过程中的第一条消息,消息结构如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct {
    ProtocolVersion client_version;
    Random random;
    SessionID session_id;
    CipherSuite cipher_suites<2..2^16-2>;
    CompressionMethod compression_methods<1..2^8-1>;
    select (extensions_present) {
        case false:
            struct {};
        case true:
            Extension extensions<0..2^16-1>;
    };
} ClientHello;
  • client_version 表示客户端支持的SSL/TLS协议版本,该值表示支持的最高版本号;
  • random 包含32字节的数据,客户端生成的随机数,使用PRF算法计算主密钥和密钥块时会用到,校验握手信息完整,生成预主密钥时都会用到,主要是为了避免可能的重放攻击;
  • session_id 与会话恢复有关;
  • cipher_suites 客户端支持的密码套件列表,可以发送多个密码套件,优先使用第一个;
  • compression_methods 客户端支持的压缩方法;
  • extensions 扩展属性。

客户端发送ClientHello消息后,就会等待服务器发送ServerHello消息,如果没有接收到该消息或者接收到的是其他子消息,会产生一个致命错误。

ServerHello

服务端接收到客户端的连接请求后,会从客户端支持的密码套件中选取一个自己也支持的密码套件作为双方协商的结果,然后返回告诉客户端。ServerHello的结构如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
struct {
    ProtocolVersion server_version;
    Random random;
    SessionID session_id;
    CipherSuite cipher_suite;
    CompressionMethod compression_method;
    select (extensions_present) {
        case false:
            struct {};
        case true:
            Extension extensions<0..2^16-1>;
    };
} ServerHello;
  • server_version 服务端根据客户端传递的版本号选择一个双方都支持的版本;
  • random 服务端生成的随机数;
  • session_id 如果客户端传输的session_id不为空,则服务器会从缓存中寻找是否存在该session_id,如果找到则会话恢复,如果没有则进行一个完整的握手连接;
  • cipher_suite 服务端根据客户端传递的密码套件,选择双方都支持的密码套件进行处理;
  • compression_methods 根据客户端传递的compression_method,决定使用的压缩算法;
  • extensions 根据客户端传递的扩展列表,服务器会处理一系列扩展。扩展列表必须和客户端发送的扩展列表有关联,客户端没有发送的扩展不能出现在服务器发送的扩展列表中,否则握手过程失败。

与ClientHello不同的是,ServerHello每个字段只包含一个选项。

Certificate

服务器发送Server Hello消息后,一般会立刻发送Server Certificate子消息,Server Hello子消息和Server Certificate子消息在同一个网络包中(同一个TLS记录层消息中),如果拆分为多个包,会进一步增加网络延迟。

服务器必须保证它发送的证书与选择的算法套件一致。另外,Certificate消息是可选的,因为并非所有套件都使用身份验证,也并非所有身份验证方法都需要证书。

服务器发送证书一般有两个目的:

  • 身份验证;

  • 证书中包含公钥,可以使用公钥结合密码套件的密钥协商算法协商出预主密钥。

1
2
3
4
5
opaque ASN.1Cert<1..2^24-1>;

struct {
    ASN.1Cert certificate_list<0..2^24-1>;
} Certificate;

典型的Certificate消息用于携带服务器X.509证书链。证书消息包含的就是证书链,每张证书是一个ASN.1Cert结构,服务器实体证书是第一张证书,接下来是中间证书。

接下来重点描述客户端签名算法、证书签名算法、密码套件、服务器公钥四者之间的关系。

证书签名算法和客户端签名算法

证书中包含了CA机构的信息,其中最重要的就是证书的数字签名算法,客户端发送CilentHello时包含signature_algorithms扩展,表示客户端支持的所有数字签名算法,如果证书链中的证书签名算法客户端不支持,则握手失败。

密码套件中和握手协议相关的是密码协商算法身份验证算法,密码套件中的密码协商算法不一样。

ServerKeyExchange

该消息是可选的,如果证书包含的信息不足以进行密钥交换,那么必须发送消息。

下列的密码套件,服务器会发送ServerKeyExchange子消息:

  • DHE_DSS

  • DHE_RSA

  • ECDHE_ECDSA

  • ECDHE_RSA

上述密码套件都是使用临时DH/ECDH密码协商算法,客户端每次连接服务器时,服务器会发送动态DH信息(DH参数和DH公钥),这些信息不存在证书中,需要通过ServerKeyExchange消息传递,传递的DH参数需要使用服务器的私钥进行签名

下列的密码套件需要服务器发送ServerKeyExchange子消息。

  • DH_anon

  • ECDH_anon

使用的是静态DH/ECDH协商算法,由于没有证书(没有Certificate消息),所以需要ServerKeyExchange消息传递相关DH信息,传递的DH信息需要使用服务器的私钥进行签名。

下列密码套件不允许服务器发送ServerKeyExchange子消息:

  • RSA

  • DH_RSS

  • DH_RSA

对于RSA套件,客户端计算出预备主密钥,然后使用服务器RSA公钥发送给服务器,服务器解密出预备主密钥即可,无需ServerKeyExchange消息也能完成协商。

对于DH_RSS/DH_RSA密码套件,证书中已经包含静态DH信息,无需服务器额外发送ServerKeyExchange消息。

消息结构

一般HTTPS网站会部署ECDHE_RSA、DHE_RSA、ECDHE_ECDSA、RSA这4个密码套件中的某一个,ServerKeyExchange子消息主要包含DH/ECDH的参数和公钥。

服务器支持的密码套件:

1
2
3
enum {
   dhe_dss, dhe_rsa, dh_anon, rsa, dh_dss, dh_rsa, ec_diffie_hellman
} KeyExchangeAlgorithm;

DH参数和公钥结构

该结构包含临时DH参数和公钥,dh_p代表大质数,dh_g代表生成元,dh_Ys表示服务器DH公钥。

1
2
3
4
5
struct {
    opaque dh_p<1..2^16-1>;
    opaque dh_g<1..2^16-1>;
    opaque dh_Ys<1..2^16-1>;
} ServerDHParams;

ECDHE参数和公钥结构:

1
2
3
4
5
6
struct {
    # ECDH参数,主要是命名曲线
    ECParameters   curve_params;
    # 公钥
    ECPoint        public;
} ServerECDHParams;

ECC公钥结构:

1
2
3
struct {
    opaque point <1..2^8-1>;
} ECPoint

ServerHelloDone

服务端发送ServerHello后,会立刻发送该消息,等待客户端响应。该消息就是一条空消息:

1
struct { } ServerHelloDone;

ClientKeyExchange

在收到服务端的ServerHelloDone消息后,客户端会立刻发送该消息。该消息的主要作用是协商出预备主密钥

  • 客户端通过RSA/ECDSA算法加密预备主密钥,然后发送给服务器端;

  • 通过服务器发送DH参数计算出客户端的DH公钥,并传递给服务器,两者最终会计算出相同的预备主密钥。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
struct {
    select (KeyExchangeAlgorithm) {
    case rsa:
        EncryptedPreMasterSecret;
    case dhe_dss:
    case dhe_rsa:
    case dh_dss:
    case dh_rsa:
    case dh_anon:
        ClientDiffieHellmanPublic;
    case ec_diffie_hellman:
        ClientECDiffieHellmanPublic;
} exchange_keys;
} ClientKeyExchange;

ChangeCipherSpec

该协议不是握手协议的一部分,但在理解时可以认为是握手协议的子消息。客户端和服务端计算出预备主密钥、主密钥和密码块后,接下来通知对方,后续的消息都需要TLS记录层协议进行加密保护了。

对于客户端和服务器端来说,需要注意发送该消息的时候并不知道对方是否已经计算出主密钥和密钥块,一般情况下是客户端先发送ChangeCipherSpec子消息。

1
2
3
struct {
    enum { change_cipher_spec(1), (255) } type;
} ChangeCipherSpec;

Finished

ChangeCipherSpec消息发送后,理论上TLS记录层协议就可以进行加密保护应用层的数据了。Finished子消息是第一个由TLS记录层协议加密保护的消息

在握手协议中,所有的子消息都没有加密和完整性保护,消息很容易被篡改,为了避免消息篡改,客户端和服务端需要校验对方发送的Finished消息 ,确保所有的握手消息都没有被篡改。

1
2
3
struct {
    opaque verify_data[verify_data_length];
} Finished;   

该verify_data对应的值是通过PRF函数计算出来的,

1
2
verify_data = PRF(master_secret, finished_label, Hash(handshake_messages))
        [0..verify_data_length-1];

PRF函数的输入值有:

  • 主密钥 master_secret

  • 标签 finish_label,如果是客户端发送Finished消息,那么标签的值是“client finished”,如果是服务器端发送的Finished消息,标签为“server finished”;

  • handshake_messages是所有的握手协议消息。对于客户端来说,handshake_messages的内容包含所有发送的消息和接收到的消息,但不包括自己发送的Finished消息;对于服务器端来说,handshake_messages的内容从Client Hello消息开始截止到Finished消息之前的所有消息,也包括客户端的Finished子消息。

handshake_messages消息只包括握手协议的消息,不包括ChangeCipherSpec消息、警告(alert)消息。

Finished子消息是如何避免消息篡改的:

  • 客户端计算verify_data值,其中handshake_message包含的版本号(最开始CilentHello携带的版本号,不是协商出来的版本号);

  • 服务器端接收到客户端Finished消息后,解出客户端发送的handshake_messages,比对其中的版本号与自己接受到的CilentHello消息中的版本号。

会话恢复

一个SSL/TLS连接建立会话会,如果由于某些原因被中断。这时如果从0开始握手那又是一个耗费网络资源的过程,因而有需要有一种机制能够从之前的连接中恢复连接会话。SSL/TLS会话恢复不是一个完整的握手过程,而是复用之前的连接。

基于SessionID

会话恢复有两种形式,分别是基于SessionID的会话恢复和基于Session Ticket的会话恢复。

一个完整的握手协议完成后,服务器端会在内存中保存会话信息,包括如下部分:

  • 会话标识符(session identifier):每个会话都有一个唯一的编号;

  • 证书(peer certificate):对端的证书;

  • 压缩算法(compression method):压缩方法;

  • 密码套件(cipher spec):客户端和服务器端协商出的密码套件;

  • 主密钥(master secret):每个会话会保存一个主密钥;

  • 会话可恢复标识(is resumable):表示某个会话是否可恢复。

在ClientHello子消息中,有一个字段是SessionID。在完整的握手过程中,其传递的SessionID值为空。

服务器端收到ClientHello消息后,检查SessionID值,如果为空则进行完整的握手。并生成一个SessionID,通过接下来的ServerHello消息传递回给客户端。客户端接收到服务器端的SessionID后,会记录在内存中。服务器端和客户端完整处理FInished消息后,代表一个完整的握手过程结束,服务器端会将会话信息保存到Session Cache中。

会话恢复:客户端再次请求相同的网站时,如果网站对应的SessionID不为空,则在CientHello消息中填写该值。服务器端接收到消息后,检查Session Cache 是否根据SessionID找到会话信息,如果找不到或者不可恢复会话,则进行新的完整握手。如果能够恢复本次连接,则直接发送ChangeCipherSpec和Finished消息,跳过密码协商的过程。最终,客户端也发送ChangeCipherSpec和Finished消息,表示会话恢复成功。

基于Session Ticket

SessionTicket的处理标准定义在RFC 5077中,SessionTicket以TLS扩展的方式完成会话恢复,SessionTicket扩展的实现定义在RFC 4507上。

服务器端将会话信息加密后以票据(ticket)的方式发送给客户端,服务器本身不存储会话信息。客户端收到票据后将其存储到内存中,如果想恢复会话,则下一个连接时间票据发送回服务器端,服务器端解密确认无误后即进行会话恢复。

携带SessionTicket的完整握手过程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 Client                                               Server

         ClientHello
         (SessionTicket extension) -------->
                                                         ServerHello
                                     (empty SessionTicket extension)
                                                        Certificate*
                                                  ServerKeyExchange*
                                                 CertificateRequest*
                                  <--------          ServerHelloDone
         Certificate*
         ClientKeyExchange
         CertificateVerify*
         [ChangeCipherSpec]
         Finished                 -------->
                                                    NewSessionTicket
                                                  [ChangeCipherSpec]
                                  <--------                 Finished
         Application Data         <------->         Application Data
  • 对于完整的握手过程,如果期望服务器端支持Session Ticket会话恢复,则在客户端CilentHello消息中包括一个空的Session Ticket TLS扩展;
  • 如果服务器支持Session Ticket会话恢复,服务器的ServerHello消息中也包括一个Session Ticket TLS扩展;
  • 服务器端对会话信息进行加密保护,生成一个票据然后再NewSessionTicket子消息中发送该票据,NewSessionTicket子消息是握手协议的一个独立子消息;
  • 客户端收到NewSessionTicket子消息后,将票据存储起来,以便下次使用。

从SessionTicket恢复会话的过程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Client                                                Server
         ClientHello
         (SessionTicket extension)      -------->
                                                          ServerHello
                                      (empty SessionTicket extension)
                                                     NewSessionTicket
                                                   [ChangeCipherSpec]
                                       <--------             Finished
         [ChangeCipherSpec]
         Finished                      -------->
         Application Data              <------->     Application Data
  • 客户端的CilentHello消息包括一个非空的SessionTicket TLS扩展;

  • 服务器端接收到非空票据后,对票据进行解密校验,如果可以恢复则在服务器ServerHello中发送一个空的SessionTicket TLS扩展;

  • 接下来发送一个NewSessionTicket子消息更新票据(票据是有有效期的);

  • 客户端和服务器端接着发送和检验Finished消息表示握手完成,完成会话恢复。

NewSessionTicket子消息

该消息必须在ChangeCipherSpec协议发送之前发送,如果服务器端的ServerHello消息中包含SessionTicket LTLS扩展,则必须发送该消息,否则不能发送该消息。

1
2
3
4
5
6
7
8
9
struct {
    HandshakeType msg_type;
    uint24 length;
    select (HandshakeType) {
        case certificate_url:    CertificateURL;
        case certificate_status:  CertificateStatus;
        case session_ticket:     NewSessionTicket;
    } body;
} Handshake;

NewSessionTicket子消息中包括最重要的元素就是票据,票据也有生命周期,服务器应该检验票据的有效期:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct {
        # 票据的有效期
        uint32 ticket_lifetime_hint;
        opaque ticket<0..2^16-1>;
    } NewSessionTicket;

struct {
    opaque key_name[16];
    opaque iv[16];
    opaque encrypted_state<0..2^16-1>;
    opaque mac[32];
} ticket;

struct {
    ProtocolVersion protocol_version;
    CipherSuite cipher_suite;
    CompressionMethod compression_method;
    opaque master_secret[48];
    # 客户端的标识符
    ClientIdentity client_identity;
    # 票据过期时间
    uint32 timestamp;
} StatePlaintext;

票据的生成完全由服务器端控制,客户端只负责传输,设计及票据的解密。

  • key_name 票据加密使用的密钥文件;

  • iv 初始化向量,AES 加密算法需要使用;

  • mac 票据需要加密和完整性保护;

  • encrypted_state 票据详细信息,存储的是会话信息。

密钥交换/密钥协商

密钥交换是握手过程中最引人入胜的部分。在TLS中,会话安全性取决于成为主密钥(master secret)的48字节共享密钥。密钥交换的目的是为了交换预备主密钥(premaster secret)。

一些常用的密钥交换算法如下:

密钥交换算法描述
dh_anonDiifie-Hellman(DH)密钥交换,未经身份验证
dhe_rsa临时DH(E即ephemeral)密钥交换,使用RSA身份验证
ecdh_anon临时椭圆曲线DH(elliptic curve DH)密钥交换,未经身份验证(RFC 4492)
ecdhe_rsa临时ECDH密钥交换,使用RSA身份校验
ecdhe_ecdsa临时ECDH密钥交换,使用ECDSA身份验证
krb5Kerberos密钥交换 RFC 2712
rsaRSA密钥交换和身份验证
psk预共享密钥(pre-shared key)密钥交换和身份验证
dhe_psk临时DH密钥交换,使用PSK身份验证
rsa_pskPSK密钥交换,使用RSA身份验证

使用哪种密钥交换由协商出来的套件决定,实际中使用的密钥交换算法主要有以下4种:

  • RSA RSA是一种事实上的标准密钥交换算法,它得到了广泛的支持。但也面临着严重的威胁:一旦被动攻击者能够访问服务器的私钥,则可以解密所有加密数据;

  • DHE_RSA 临时 Diffie-Hellman 密钥交换算法是一种构造完备的算法。优点是支持先前保密,缺点是执行缓慢。

  • ECDHE_RSA和ECDHE_ECDSA 临时椭圆曲线Diffie-Hellman密钥交换建立在椭圆曲线加密的基础之上。

RSA密钥交换

客户端生成预备主密钥(46字节随机数),然后使用服务器公钥对其加密,将其包含在CilentKeyExchange消息中发送出去。服务器使用私钥解密即可得到预备主密钥。

客户端仅仅是加密,而没有完整性保护,消息可能会被篡改,在实现的时候一定要注意,有两种加密体制,分别是RSAES-PKCS1-v1_5和RSAES-OAEP加密方式,RSAES-OAEP相对更安全,但SSL/TLS协议仍然使用RSAES-PKCS1-v1_5加密方式。

Diffie-Hellman密钥交换

DH密钥交换是一种密钥协定的协议,它使两个团体在不安全的信道上生成共享密钥成为可能。

以这种方式协商共享密钥时不会受到被动攻击的威胁,但主动攻击者却可以劫持通信信道,冒充对端。这就是DH密钥交换通常与身份验证联合使用的原因。

DH的诀窍是使用了一种正向计算简单、逆向计算困难的数学函数,即使交换中某些因子已被知晓,情况也是一样。例子:如果有两种颜色,那么很容易将其混在一起得到第三种颜色;但是如果只有第三种颜色的话,就很难确定究竟它是由哪两种颜色混合而成的。

预备主密钥、主密钥与密钥块

客户端与服务器端协商出预备主密钥后,就会通过PRF函数计算出主密钥。一旦主密钥生成,预备主密钥就会从内存中立即被删除。

1
2
3
4
master_secret = PRF(pre_master_secret,
                    "master secret",
                    ClientHello.random + ServerHello.random)
                    [0..47];

PRF函数的输入值是:

  • 预备主密钥 pre_master_secret

  • 标签 label 是 “master_secret”

  • seed 由客户端和服务器端的随机数组合而成

主密钥的长度固定为 48 字节,而预备主密钥长度由密钥套件算法决定,对于使用RSA算法协商密钥,预备主密钥的长度是48字节。

客户端和服务端计算出主密钥后,立刻会计算密钥块(key_block),TLS记录层协议需要使用这些密钥块进行密码学机密性和完整性保护。

1
2
3
4
key_block = PRF(SecurityParameters.master_secret,
                "key expansion",
                SecurityParameters.server_random   +   SecurityParameters.
client_random);

PRF函数的输入值是:

  • 主密钥 master_secret

  • 标签 label 是 “key expansion”

  • seed 服务器端和客户端的随机值组合(与生成主密钥时的顺序调换)

根据密码套件可以计算出密钥块的长度,然后将密钥块拆分成各个密钥值,每个密钥块长度由加密参数(security parameters)决定,密钥块主要有6个:

1
2
3
4
5
6
client_write_MAC_key[SecurityParameters.mac_key_length]
server_write_MAC_key[SecurityParameters.mac_key_length]
client_write_key[SecurityParameters.enc_key_length]
server_write_key[SecurityParameters.enc_key_length]
client_write_IV[SecurityParameters.fixed_iv_length]
server_write_IV[SecurityParameters.fixed_iv_length]

关于PRF算法,PRF算法的基元是HMAC算法,HMAC算法的基元是Hash算法。PRF算法实际上就是对P_hash函数的包装,PRF使用的Hash算法取决于密码套件和TLS版本。

PRF算法Hash算法
prf_tls10TLS 1.0和TLS1.1协议,PRF算法是结合MD5和SHA-1算法
prf_tls12_sha256TLS 1.2协议,默认Hash算法是SHA256
prf_tls12_sha384TLS 1.2 协议,如果密码套件指定的HMAC算法安全级高于SHA256,则采用SHA384算法

接下来的密码套件部分,会介绍密码套件与各个算法之间的关系。

密码套件

密码套件是SSL/TLS协议中的核心,套件大致由以下内容组成:

  • 身份验证方法

  • 密钥交换方法

  • 加密算法

  • 加密密钥大小

  • 密码模式(可选)

  • MAC算法

  • PRF

  • 用于Finished消息的散列函数(TLS v1.2)

  • verify_data结构的长度(TLS v1.2)

密码套件都倾向于使用较长的描述性名称:由密钥交换算法、身份验证方法、密码定义(算法、强度和模式)和可选的MAC或PRF算法组合而成。

tips: TLS 套件会使用 TLS_ 前缀,而 SSL3 套件使用 SSL_ 前缀,SSL2 套件使用 SSL_CK_ 前缀。

密码套件名称密钥交换身份验证密码MACPRF
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256ECDHERSAAES-128-GCMnoneSHA256
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384ECDHEECDSAAES-256-GCMnoneSHA384
TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHADHERSA3DES-EDE-CBCSHA1协议
TLS_RSA_WITH_AES_128_CBC_SHARSARSAAES-128-CBCSHA1协议
TLS_ECDHE_ECDSA_WITH_AES_128_CCMECDHEECDSAAES-128-GCMnoneSHA256

以上仅是部分密码套件,RFC5246#A.5 The Cipher Suite 定义了TLS v1.2 支持的密码套件。

值得注意的是,尽管IANA为每个密码套件都定义了一个名称和编号,但是不同SSL/TLS协议的实现(OpenSSL,GnuTLS),套件的名称可能是不一样的。

扩展

在前面的ClientHello消息中提到,客户端可以根据自己的需求发送多个扩展给服务器,并包含在CilentHello子消息中。服务器端解析CilentHello中的扩展,并在ServerHello消息中返回相同类型的扩展。

  • 扩展是向下兼容的,客户端和服务端并没有严格要求一定要支持某个扩展;

  • 服务端响应的扩展必须是客户端扩展请求的子集;

  • 扩展的详细定义在RFC 6066;

  • 扩展以扩展块的形式加在ClientHello和ServerHello消息的末尾。

    1
    2
    3
    4
    5
    
    Extension extensions;
    struct {
        ExtensionType extension_type;
        opaque extension_data;
    } Extension;
    
类型名称描述
0server_name包含连接欲访问的安全虚拟机
5status_request指示支持OCSP stapling
13 0x0dsugnature_algorithms包含支持的签名算法/散列函数对
15 0x0fheartbeat指示心跳协议
16 0x10application_layer_protocol_negotiation包含客户端希望协商的并且支持的应用层协议
18 0x12signed_certificate_timestamp服务器用来提交证据,以证明证书已被公众共享
21 0x15padding用于解决F5负载均衡中的特定bug
35 0x23session_ticket指示支持无状态会话恢复
13172 0x3374next_protocol_negotiation指示支持次协议协商
65281 0xff01renegotiation_info指示支持安全重新协商

使用Wireshark观察SSL/TLS协议

在Windows 10 下以管理员的权限运行WireShark,捕获WLAN流量。接下来以访问https://example.com 为例子。首先,ping一下主机得到ip地址用于WireShark过滤:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
ping www.baidu.com

正在 Ping www.a.shifen.com [180.101.50.242] 具有 32 字节的数据:
来自 180.101.50.242 的回复: 字节=32 时间=11ms TTL=52
来自 180.101.50.242 的回复: 字节=32 时间=11ms TTL=52
来自 180.101.50.242 的回复: 字节=32 时间=9ms TTL=52
来自 180.101.50.242 的回复: 字节=32 时间=8ms TTL=52

180.101.50.242 的 Ping 统计信息:
    数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
    最短 = 8ms,最长 = 11ms,平均 = 9ms

使用浏览器访问域名:

首先我们看到本地发出的CilentHello消息:

能得出,本地支持TLS v1.2 协议,并且看到了携带的随机数、支持的密码套件(16种密码套件)和SessionID以及一些扩展信息。

接着看ServerHello消息:

能够得到:

  • 选定的TLS版本是v1.2,

  • 服务端的随机数,

  • 协商出来的密码套件是TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256

    • 密钥交换算法是ECDHE

    • 身份验证算法是 RSA

    • 加密算法是 AES-128-GCM

    • MAC和PRF : SHA256

  • 支持SessionTicket 会话恢复

接下来是 Certificate子消息:

然后是ServerKeyExchange消息:

该消息是可选的,由于上面客户端和服务端协商出了ECDHE的密钥交换算法,所以需要通过ServerKeyExchange带上算法的参数信息。

然后就是ServerHelloDone子消息,可以看到该子消息的长度为0:

接下来就是ClientKeyExchange子消息:

可以看到客户端计算出ECDH的公钥发送给服务器端,用于生成预备主密钥。

然后客户端发送ChangeCipherSpec消息:

然后是客户端发送的Finished子消息:

注意,由于Finished子消息是第一条加密的消息,所以WireShark并不能识别出来。

然后服务器端发送NewSessionTicket子消息,用于会话恢复:

然后发送CipherChangeSpec和Finished完成握手过程:

参考引用

《深入浅出HTTPS:从原理到实战》

《HTTPS权威指南:在服务器和Web应用上部署SSL/TLS和PKI》

RFC 5246 - The Transport Layer Security (TLS) Protocol Version 1.2

Announcing Keyless SSL™: All the Benefits of CloudFlare Without Having to Turn Over Your Private SSL Keys

Keyless SSL: The Nitty Gritty Technical Details

图解SSL/TLS协议 - 阮一峰的网络日志

Overview of SSL/TLS Encryption | Microsoft Learn

哦吼是一首歌。
Built with Hugo
Theme Stack designed by Jimmy