我们知道,传输层 TCP 协议负责提供端到端的流式数据传输能力,应用程序在其基础上组织应用层协议。以 HTTP 协议为例,客户端先与服务器建立 TCP 连接,再通过它来发送 HTTP 请求。
由于 TCP 协议只提供最基本的数据传输服务,数据不经加密明文传输,很容易被中间人窃取:
- 窃听风险( eavesdropping ),第三方获悉敏感信息;
- 篡改风险( tampering ),第三方修改数据内容;
- 冒充风险( pretending ),第三方可以冒充他人身份,例如钓鱼网站;
想要保护数据安全,我们就必须对数据进行加密。虽然数据理论上也可以在应用层加密,但这样做并不明智。因为应用层协议有很多,每个协议都自己实现加密逻辑,成本太高:
如果能够在 TCP 协议之上,专门实现新的协议提供加密的流式传输能力,应用层协议设计将得到极大简化。实际上,网络协议栈就是这样实现的:
负责加密实现安全传输的新协议,位于传输层 TCP 协议和应用层协议之间,这就是本文要介绍的 传输层安全性协议( transport layer security ),简称 TLS 。TLS 的前身是 安全套接层( secure sockets layer,SSL ),它实际上是 SSL 的后续版本。
简介
TLS 在 TCP 协议基础上,为应用层提供安全的流式数据传输能力,主要实现两个功能:
- 服务端身份认证
- 服务端提供数字证书用于身份校验
- 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 连接传输。
- 客户端发送 ClientHello 握手报文,向服务器提供自己的信息,包括:
- 本端支持的 TLS 协议版本,比如 1.2 版本;
- 一个客户端生成的随机数,后续由于生成主加密密钥;
- 支持的加密算法,比如 RSA 公钥加密;
- 支持的压缩算法;
- 服务器收到 ClientHello 报文后,向客户端回复 ServerHello 报文,告知客户端:
- 确认使用的 TLS 协议版本,比如 1.2 版本,如果双方版本不兼容,则直接关闭连接;
- 一个服务端生成的随机数,后续用于生成主加密秘钥;
- 确认使用的加密算法,比如 RSA 公钥加密;
- 于此同时,服务器向客户端发送 Certificate 报文,提供自己的证书;
- 如果需要客户端提供证书,服务器还会发送 CertificateRequest 报文;
- 最终发送 ServerHelloDone 报文,并等待客户端回复;
- 客户端对服务器进行校验,并生成一个随机数作为 预主密钥 ( pre-master key );
- 预主密钥由证书中的服务器公钥加密后,再发给服务器;
- 预主密钥和前面两个随机数一起生成主加密密钥;
- 之所以用随机数是为了让密钥每次都不一样;
- 之所以用三个随机数是为了增加随机性(伪随机数可能不够随机,容易被猜出来);
- 服务器收到客户端的预主密钥,用三个随机数计算主加密密钥,并发送密钥变更通知;
- 此后双方使用主加密密钥,通过 应用数据协议 传输加密数据;
由于握手协议需要经过两轮消息交换,加上 TCP 的三次握手, TLS 建立连接会有比较明显的耗时。TLS 在 TCP 的基础上实现安全传输,代价是 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 请求:
|
|
由此可见,HTTPS 协议只是使用 TLS 作为传输通道的 HTTP 协议而已,此外没什么特别的。TLS 协议为 HTTP 协议提供服务端身份认证以及数据加密能力。
顺便复习一下,例子中的 HTTP 请求报文格式如下:
|
|
第一行为请求行,包含请求方法、资源路径以及 HTTP 协议版本;紧接着的是 HTTP 头部,Host 头为请求域名,而 Connection 控制是否保持长连接,close 表示请求结束后直接关闭连接;头部后面是一个空行,再往后是请求体; GET 请求没有请求体,因此这部分是空的。
【小菜学网络】系列文章首发于公众号【小菜学编程】,敬请关注: