为了保证 Web 应用的安全性,一种合理的思路在于在 TCP/IP 协议栈的各层中加入合适的安全组件。目前已有的方式包括了在网络层的 IPSec,传输层的 TLS/SSL,应用层部署的 Kerberos 和 S/MIME 等。

目前最广泛应用的安全组件还是 TLS 协议,TLS 可以被大多数浏览器和 Web 引擎支持,提供安全的端到端通信服务,而相对来说 SSL 应用较少。

# TLS 结构

TLS 协议实际可以分为两层,如图所示。

TLS 协议基于传输层的 TCP 之上,其内部分为了两层。第一层是 TLS 记录协议(TLS Record Protocol),用来为上层协议提供保密性和完整性服务。TLS 的高层协议包括了握手协议、修改密码规范协议和警报协议,这些协议用来进行 TLS 交换的管理。心跳协议是 TLS 的一个扩展,增加了连接保活的机制。

TLS 协议给出了连接会话的定义。连接是提供合适服务类型的一种传输,在 TLS 中连接是一种对等关系,是一个和会话关联的暂时状态。会话是客户端和服务器之间的一个关联,通过握手协议进行创建,定义了一组多个连接共享的安全参数。

一个简单的理解是连接和会话是一个多对一的关系。一个连接属于一个会话,而一个会话可以存在多个连接。

会话和连接都具有一些状态。连接的状态包括了:

  • 服务器和客户端随机数:服务器和客户端为每个连接指定的随机数
  • 服务器 / 客户端写 MAC 密钥:服务器或者客户端发送数据时 MAC 操作使用的密钥
  • 服务器 / 客户端写密钥:服务器或客户端发送自己的数据或者解密对方发来的数据用的密钥
  • 初始化向量:使用 CBC 模式的初始化向量,通过 TLS 握手协议进行初始化。后续记录的 IV 由上个记录最后一个密文分组来充当。
  • 序号:会话各方为连接收发消息使用的序号

会话的状态包括了:

  • 会话标识:服务器用于标识会话状态的一个随机字节序列
  • 对等实体证书:对等实体的 X.509 证书,可以为空
  • 压缩方法:压缩数据的算法
  • 密码规范:规定了数据加密算法和 MAC 计算方法,以及一些加密属性
  • 主密钥:客户端和服务器共享的密钥
  • 可恢复:标记是否能用这个会话来初始化新的连接

# TLS 协议内容

# TLS 记录协议

TLS 记录协议对数据的处理包括了分段、压缩、加 MAC、加密和增加 TLS 记录头部几个部分。

TLS 记录协议首先会对消息进行分段,得到小于等于 214(16384)字节的若干段,接下来通过可选的压缩算法进行压缩,TLS 要求压缩算法结果不能导致数据长度增加超过 1024 字节。

下一步是计算数据的 MAC,使用的是 RFC 2104 定义的 HMAC 算法:

HMACK(M)=H[(K+opad)H[K+ipadM]]{\rm HMAC}_K(M)=H[(K^+\oplus {\rm opad})||H[K^+\oplus{\rm ipad}||M]]

其中HH 为使用的哈希函数,MM 为待哈希的消息,K+K^+ 为左侧填充全 0 使得和哈希算法分组等长度的密钥。ipad\rm ipadopad\rm opad 分别为重复了 64 次的十六进制数0x36(0b00110110)0x5c(0b01011100)

计算下面的值得到哈希增加在消息的最后:

HMAC_hash(MAC_write_secret,seq_numTLSCompressed.typeTLSCompressed.versionTLSCompressed.lengthTLSCompressed.fragment){\rm HMAC\_hash(MAC\_write\_secret,seq\_num||TLSCompressed.type||TLSCompressed.version||TLSCompressed.length||TLSCompressed.fragment)}

接下来 TLS 使用对称加密算法加密消息和 MAC,得到密文。TLS 同样也要求分组密码加密之后密文的长度增量不能超过 1024 字节。一个注意点是分组密码需要进行填充,填充块的最大长度为 255 字节。通过 TLS 的填充,可以防范一些对密文长度的分析攻击。

在最后一步,TLS 协议会增加一个 TLS 记录协议头部。这个头部包括了下面的字段:

  • 内容类型1 字节 (8 位)):封装段的高层协议,包括了 change_cipher_spec (20)、 alert (21)、 handshake (22)、 application_data (23) 等类型。
  • 主版本号1 字节 (8 位)):使用的 TLS 主版本号。
  • 从版本号1 字节 (8 位)):使用的 TLS 从版本号。
  • 压缩长度2 字节 (16 位)):明文段或者压缩之后的段的长度(字节)。

# 修改密码规范协议

修改密码规范协议的的数据载荷部分仅仅包括了一个一字节的值为 1 的消息,用来将当前的会话改为挂起状态用来更新密码套件。

# 警报协议

TLS 警报协议用来将 TLS 相关的警报传达到对方。字段的取值包括了两个字节的类型信息。其中第一个字节取值用来表示警告的严重程度,分为警告 (1) 和致命 (2) 两类。

对于致命警告,连接会被直接结束。同时本次会话其他连接虽然还可以继续进行,但是不能再新建新的连接。

具体的警报类型由 RFC 5246 定义。

# 握手协议

TLS 握手协议允许客户端与服务器互相认证,同时协商加密和 MAC 的算法。这是 TLS 最核心也是最复杂的部分。

握手协议的数据包包括了三个部分:一个 1 字节的类型字段,一个 3 字节的长度字段和通过长度字段标识的数据字段。

TLSv2 握手协议的类型字段定义了 10 种消息类型,分别是:

类型名称类型参数
请求hello_request
客户端请求client_hello版本,随机数,会话 ID,密码套件,压缩方法
服务器响应server_hello版本,随机数,会话 ID,密码套件,压缩方法
证书certificateX.509 证书链
服务器密钥交换server_key_exchange参数,签名
证书请求certificate_request类型,授权
服务器响应结束server_hello_done
证书验证certificate_verify签名
客户端密钥交换client_key_exchange参数,签名
结束finished散列值

服务器和客户端之间建立 TLS 连接的初始交换需要经过四个阶段。

# 第一阶段

在第一阶段,客户端发起建立连接请求,用以发起逻辑连接并建立关联的安全能力。这一段客户端首先发起客户端请求(client_hello),其中包括了下面这些信息:

  • 版本:客户端 TLS 的最高版本
  • 随机数:客户端生成的随机数,由 32 比特的时间戳和 28 字节的随机数组成,用于密钥交换过程中的防重放攻击。
  • 会话 ID:会话标识符,如果非 0 则表示客户端希望更新现有连接的参数或为会话创建新连接,否则表示客户端希望在新会话上创建一条新连接。
  • 密码套件:按照优先级降序排列的客户端支持的密码套件列表。
  • 压缩方法:客户端支持的压缩方法列表。

其中密码套件部分每一行为一个密码套件,定义了密钥交换算法和密码规格。

发送完 client_hello 之后,客户端会等待服务器响应 server_hello 。其字段组成和客户端请求的字段一致,同时会确定会话 ID 和使用的密码套件和压缩算法。

# 第二阶段

第二部分是服务器认证和密钥交换。其中可选的可以对服务器证书进行验证。如果需要对证书进行验证,那么服务器会发送一个 X.509 证书或者证书 X.509 证书链。这个步骤对于使用匿名 D-H 交换之外的密钥交换算法来说是必须的。

除了已经发送了包含固定 D-H 参数的证书或者使用了 RSA 密钥交换方法的情况,如果使用了匿名 D-H、暂态 D-H 或者 RSA 密钥交换,服务器还会发送一个服务器密钥交换 server_key_exchange 消息。

服务器密钥交换 server_key_exchange 消息中会包含 D-H 交换的参数和其签名,签名使用下面的方法进行:

H(ClientHello.randomServerHello.randomServerParams)\rm H(ClientHello.random || ServerHello.random || ServerParams)

签名中包含了当前会话的一些信息,因此可以防止重放攻击。

如果服务器使用的不是匿名 D-H 算法,服务器可以向客户端请求证书。证书请求 certificate_request 包括两个参数:证书类型和证书机构,其中证书类型指明了证书使用的公钥密码算法和用法,证书机构是一个可以接受的机构名称列表。

第二阶段的最后一条信息服务器结束 server_done 消息。这表示服务器响应 server_hello 相关部分已经结束。

以另外一个例子来看,在 server_hello 阶段,服务器确认使用的密码套件为 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 。接下来的一条 TLS 消息中包括了 certificateserver_key_exchangeserver_done 三个部分。

其中 certificate 部分包括了服务器的证书:

服务器密钥交换消息包括了使用的 ECDH 算法参数,并使用了签名算法对数据进行了签名。

接下来的服务器结束消息没有参数:

# 第三阶段

这一部分是客户端认证和密钥交换,实际上和第二阶段比较类似。客户端如果有需要会检查服务器的证书是否有效,并检查能否接受 server_hello 部分的参数能否被接受。

如果服务器在上一步中请求证书,那么客户端会发送一条证书 certificate 消息。如果客户端没有证书的话那就会发一条无证书警报 no_certificate_alert

接下来是客户端密钥交换 client_key_exchange 。其中的内容由具体实现的加密算法决定。

如果客户端证书具有签名功能,那么客户端可以发送证书验证 certificate_verify 消息以便于验证。

这一部分的一个简单的例子如下,这个 TLS 数据包中包含了一个 client_key_exchange 消息。

# 第四阶段

最后这一部分是安全连接的建立。客户端会发送一个修改密码规格 change_cipher_sec 的消息。这个消息是通过修改密码规格协议进行发送的。

完成之后,客户端会发送一个结束 finished 消息,用于验证密钥交换和认证过程完成,这个消息是两个哈希的连接:

PRF(master_secret,finished_label,MD5(handshake_messages)SHA1(handshake_messages))\rm PRF(master\_secret, finished\_label, MD5(handshake\_messages)||SHA1(handshake\_messages))

服务器端同样采取对等的方式完成该阶段。

# TLS 密钥生成和密码参数计算

TLS 协议需要通过安全的方式创建一个用于本次会话的48 字节 (384bit) 的密钥。这通过交换预备主密钥和计算主密钥两个步骤进行。

首先是第一步,交换预备主密钥。对于使用 RSA 的情况,客户端生成一个 48 字节的预备主密钥 ( pre_master_secret ),使用服务器的 RSA 公钥加密之后发送给服务器即可。而对于使用 D-H 交换的情况来说,客户端和服务器之间会使用 D-H 交换生成一个共享的预备主密钥。

在双方获取了预备主密钥之后,就需要通过这个预备主密钥计算出主密钥。计算的方法如下:

master_secret = PRF(pre_master_secret, "master secret",
                    client_hello.random||server_hello.random)

通过上面的算法会生成一个 48 字节的主密钥。接下来密钥块的计算如下:

key_block = PRF(master_secret, "key expansion",
                SecurityParameters.server_random||SecurityParameters.client_random)

通过上面这个函数可以生成 MAC 密钥、会话密钥和 IV 等参数。

上面使用的 PRF 是一个伪随机函数,实际上是调用了 p_hash 函数。

PRF(secret, label, seed) = P_hash(secret, label || seed)
P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
                        HMAC_hash(secret, A(2) + seed) +
                         HMAC_hash(secret, A(3) + seed) + ...

其中 A() 函数为:

A(0) = seed
A(i) = HMAC_hash(secret, A(i-1))

# 参考资料

[1] 网络安全基础 —— 应用与标准(第六版),William Stallings.

[2] 密码编码学与网络安全 —— 原理与实践(第八版),William Stallings.

[3] https://datatracker.ietf.org/doc/html/rfc5246

此文章已被阅读次数:正在加载...更新于