TLS传输层安全性协议实现加密传输的秘诀

我们知道,传输层 TCP 协议负责提供端到端的流式数据传输能力,应用程序在其基础上组织应用层协议。以 HTTP 协议为例,客户端先与服务器建立 TCP 连接,再通过它来发送 HTTP 请求。

由于 TCP 协议只提供最基本的数据传输服务,数据不经加密明文传输,很容易被中间人窃取:

  • 窃听风险eavesdropping ),第三方获悉敏感信息;
  • 篡改风险tampering ),第三方修改数据内容;
  • 冒充风险pretending ),第三方可以冒充他人身份,例如钓鱼网站;

想要保护数据安全,我们就必须对数据进行加密。虽然数据理论上也可以在应用层加密,但这样做并不明智。因为应用层协议有很多,每个协议都自己实现加密逻辑,成本太高:

如果能够在 TCP 协议之上,专门实现新的协议提供加密的流式传输能力,应用层协议设计将得到极大简化。实际上,网络协议栈就是这样实现的:

负责加密实现安全传输的新协议,位于传输层 TCP 协议和应用层协议之间,这就是本文要介绍的 传输层安全性协议transport layer security ),简称 TLSTLS 的前身是 安全套接层secure sockets layerSSL ),它实际上是 SSL 的后续版本。

简介

TLSTCP 协议基础上,为应用层提供安全的流式数据传输能力,主要实现两个功能:

  • 服务端身份认证
    • 服务端提供数字证书用于身份校验
    • HTTPS 利用这个特性预防钓鱼网站
  • 数据加密传输
    • 采用对称加密算法加密数据
    • 对称加密密钥由非对称加密算法加密保护

TLS 协议内部进一步分为四个子协议,各自负责一部分子功能:

  • 握手协议( handshake protocol ),主要完成证书验证,并协商主加密密钥;
  • 密钥规格变更协议( change cipher spec protocol ),主要用于更改密钥规格;
  • 应用数据协议( application data protocol ),根据主加密密钥加密应用传输的数据;
  • 警报协议( alert protocol ),用于传递告警消息,接到致命级别的告警则立即关闭连接;

每个 TLS 子协议分别承担自己的职责,也有自己的协议报文,由位于底层的 记录协议record protocol )承载。换言之,TLS 协议报文分为两层,记录协议负责描述报文的类型、协议版本、长度和内容等基本信息。记录协议中的内容则承载着子协议的报文,而类型字段则表明子协议报文的类型。

  • 类型type ),占 1 字节,表示数据内容的类型;
  • 协议版本version ),占 2 字节,表示 TLS 的版本;
  • 长度length ),占 2 字节,表示数据内容的长度;
  • 数据内容content ),不定长,用来搭载子协议报文,比如握手协议报文;

握手协议

TLS 建立在 TCP 协议基础上,在 TCP 连接成功建立后,执行握手协议验证证书并协商主加密密钥。握手过程需要来回交换多次报文,报文由握手协议定义,最终用记录协议封装,作为数据通过 TCP 连接传输。

  1. 客户端发送 ClientHello 握手报文,向服务器提供自己的信息,包括:
    • 本端支持的 TLS 协议版本,比如 1.2 版本;
    • 一个客户端生成的随机数,后续由于生成主加密密钥;
    • 支持的加密算法,比如 RSA 公钥加密;
    • 支持的压缩算法;
  2. 服务器收到 ClientHello 报文后,向客户端回复 ServerHello 报文,告知客户端:
    • 确认使用的 TLS 协议版本,比如 1.2 版本,如果双方版本不兼容,则直接关闭连接;
    • 一个服务端生成的随机数,后续用于生成主加密秘钥;
    • 确认使用的加密算法,比如 RSA 公钥加密;
    • 于此同时,服务器向客户端发送 Certificate 报文,提供自己的证书;
    • 如果需要客户端提供证书,服务器还会发送 CertificateRequest 报文;
    • 最终发送 ServerHelloDone 报文,并等待客户端回复;
  3. 客户端对服务器进行校验,并生成一个随机数作为 预主密钥pre-master key );
    • 预主密钥由证书中的服务器公钥加密后,再发给服务器;
    • 预主密钥和前面两个随机数一起生成主加密密钥;
    • 之所以用随机数是为了让密钥每次都不一样;
    • 之所以用三个随机数是为了增加随机性(伪随机数可能不够随机,容易被猜出来);
  4. 服务器收到客户端的预主密钥,用三个随机数计算主加密密钥,并发送密钥变更通知;
  5. 此后双方使用主加密密钥,通过 应用数据协议 传输加密数据;

由于握手协议需要经过两轮消息交换,加上 TCP 的三次握手, TLS 建立连接会有比较明显的耗时。TLSTCP 的基础上实现安全传输,代价是 TLS 握手带来的时延以及加解密过程带来的计算开销。

如上图,蓝色阶段为 TCP 三次握手;绿色阶段为 TLS 握手;黑色部分为 TLS 加密数据传输的阶段。注意到,数据传输由 应用数据 子协议负责,它根据握手阶段协商出来的主加密密钥加密数据。

由于 TLS 握手阶段需要来回交换数据两次,而 TCP 三次握手只需往返一次,因此建立 TLS 连接的耗时至少是 TCP 的三倍( $\frac{1+2}{1}=3$ )。

您可能会问,TCP 三次握手不是需要 3 个握手包吗?为什么是一次往返呢?原因很简单——三次握手最后的 ACK 包可以跟数据( TLS 握手报文)一起发出,甚至还可以捎带在数据中发出。

如上图第一个绿色箭头为 ServerHello 握手包,握手包作为数据搭载在 TCP 分组中,理论上可以捎带三次握手最后那个 ACK 。就算 ACK 分组和 ServerHello 分组是分开的,它们也可以同时发出,因为客户端不必等 ACK 送达才发送 ServerHello

因篇幅关系,密钥规格变更协议、应用数据协议以及警报协议等子协议就不再展开赘述了。

编程

TLS 协议为上层应用建立可靠的加密连接,提供流式数据传输能力。TLS 协议通常由程序库实现,比如开源的 openssl ,应用层只管收发数据,不用关心加密细节。

下面这个程序,以 Go 语言为例,演示如何自己建立 TLS 连接并发起 HTTP 请求:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

package main

import (
    "crypto/tls"
    "fmt"
    "io"
    "os"
)

// 请求URL:https://fasionchan.com/about.txt
func main() {

    // 域名
    domain := "fasionchan.com"
    // 调用tls类库连接fasionchan.com的443端口
    conn, err := tls.Dial("tcp", fmt.Sprintf("%s:443", domain), nil)
    if err != nil {
        panic(err)
    }

    // 格式化HTTP请求报文
    request := []byte(fmt.Sprintf("GET /about.txt HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", domain))

    // HTTP报文作为数据通过TLS连接发送出去(可能部分成功,因此循环直到全部写完)
    for len(request) > 0 {
        // 每次只管往连接中写数据,不用关心加密细节
        sent, err := conn.Write(request)
        if err != nil {
            panic(err)
        }

        request = request[sent:]
    }

    // 从加密连接读取数据,并写到标准输出,同样不用关心加密细节
    // 由此可以看到服务器返回的HTTP响应报文
    if _, err := io.Copy(os.Stdout, conn); err != nil {
        panic(err)
    }
}

由此可见,HTTPS 协议只是使用 TLS 作为传输通道的 HTTP 协议而已,此外没什么特别的。TLS 协议为 HTTP 协议提供服务端身份认证以及数据加密能力。

顺便复习一下,例子中的 HTTP 请求报文格式如下:

1
2
3
4
GET /about.txt HTTP/1.1
Host: fasionchan.com
Connection: close

第一行为请求行,包含请求方法、资源路径以及 HTTP 协议版本;紧接着的是 HTTP 头部,Host 头为请求域名,而 Connection 控制是否保持长连接,close 表示请求结束后直接关闭连接;头部后面是一个空行,再往后是请求体; GET 请求没有请求体,因此这部分是空的。

小菜学网络】系列文章首发于公众号【小菜学编程】,敬请关注:

【小菜学网络】系列文章首发于公众号【小菜学编程】,敬请关注: