https://blog.csdn.net/m0_37621078/article/details/106126033?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163451992416780265476402%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=163451992416780265476402&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_v2~rank_v29-5-106126033.pc_v2_rank_blog_default&utm_term=web%E6%8A%80%E6%9C%AF&spm=1018.2226.3001.4450
前文介绍的ECDHE(Elliptic-Curve Diffie–Hellman Ephemeral)密钥协商方案可以实现认证加密算法(比如AES-GCM)共享密钥的交换,但密钥协商过程容易遇到中间人攻击问题。通过引入数字签名(比如ECDSA / RSA)可以确认通信对端的身份,但要正确使用数字签名,前提就是用于验证签名的公钥必须属于真正的通信对端,该如何确认自己得到的公钥是真实合法的呢?
很容易想到的一个思路是引入可信第三方背书,就像买家与卖家之间引入支付宝类似,将要传递的公钥经由可信第三方签名(经过可信第三方签名的公钥可以称为证书或公钥证书),只要我们能使用可信第三方公钥验证公钥证书,就可以确认公钥证书内的公钥是合法的。我们又如何传递可信第三方公钥呢?由于可信第三方属于中心化组织,全球需要的可信第三方数量很有限,因此可以将可信第三方的公钥集成到操作系统或浏览器中,不需要再额外传递。
但为了防止攻击者也申请一个公钥证书伪装通信对端,公钥证书内除了包含订阅人(申请公钥证书的一方)的公钥,还应包含订阅人的身份信息(比如域名Host 信息),当然也应该包含可信第三方(也即证书颁发机构Certificate Authority)的数字签名信息。可以说,公钥证书或数字证书是一个主要包含订阅人公钥、订阅人身份信息、证书颁发者数字签名等相关信息的数字文件,也就是一个让我们可以交换、存储和使用通信对端公钥的壳。
公钥证书或数字证书是由认证机构(CA: Certificate Authority)颁发的,使用者需要对证书进行验证,如果证书的格式千差万别那就不方便了。于是,人们制定了证书的标准规范,其中使用最广泛的是由 ITU(International Telecommunication Union)定义,经PKIX(Public-Key Infrastructure standards for X.509)工作组改造过的X.509规范(RFC5280)。
符合X.509标准的公钥证书不止包含订阅人公钥、订阅人身份信息、证书颁发者数字签名信息,还包括更多别的字段信息,我们以 https 证书为例,使用Microsoft Chromium Edge浏览器 --> 更多工具 --> 开发人员工具 --> 安全,可以查看到证书状态与安全连接设置,点击“查看证书”可以看到证书的基本/常规信息:
我们查看证书的详细信息,看标准的公钥证书一般包含哪些字段:
前面偏黄色的字段是版本V1 支持的基本字段,后面的属于扩展字段,下面给出几个主要字段的作用描述:
字段 | 描述 |
---|---|
序列号 | 每个CA用来唯一标识其所签发的证书,需要是无法被预测的而且至少包括20位 |
签名算法 签名哈希算法 | 指明证书签名所用的算法,需要放到证书里面,这样才能被证书签名保护 |
颁发者 | 包括了证书颁发者的身份信息,比如认证机构名称、域名和所在地等信息 |
有效期 | 包括开始日期和结束日期,在这段时间内证书是有效的 |
使用者 | 包含证书申请者的身份信息,包括申请者域名、组织名称、组织所在地等信息 |
公钥 公钥参数 | 包含了证书使用者的公钥、使用的签名算法,可选参数等信息,使用ECC比RSA有更短的公钥也即更小的通信量 |
仅制定证书的规范还不足以支持公钥的实际运用,我们还需要很多其它的规范,例如应该由谁来颁发可信的证书、如何颁发、私钥泄露时应该如何作废证书?到这一步,我们就已经踏入了社会学的领域,需要让公钥以及数字签名技术称为一种社会性的基础设施,即公钥基础设施(Public-Key Infrastructure),简称PKI。可以说,PKI 是为了能够更有效的运用公钥而制定的一系列规范的总称。PKI 主要由证书订阅人(申请并提供证书的服务端)、证书登记注册机构(RA: Registration Authority)、证书认证颁发机构(CA: Certification Authority)、证书使用者(请求验证证书的客户端)等部分构成,证书的申请与使用过程如下:
PKI 构成要素 | 功能描述 |
---|---|
订阅人 | 向注册登记机构申请证书,根据需要申请作废已注册的公钥 |
注册登记机构RA | 主要完成一些证书签发的相关管理工作,比如对用户进行必要的身份验证(域名验证、组织验证、扩展验证等)、找CA签发证书等 |
认证颁发机构CA | 会在确认申请用户的身份之后签发证书,同时CA会在线提供其所签发证书的最新吊销信息,方便客户端验证证书的有效性 |
使用证书的客户端 | 向服务端请求证书,使用本地(比如操作系统或浏览器中)保存的CA公钥验证来自服务端的证书签名,检测证书的吊销状态等有效性信息 |
当证书申请者出现私钥泄露或者不再使用证书等情况时,就需要将证书作废。要作废数字证书需要符合相应的标准或流程,目前主要有两种证书吊销标准:
有了用于管理传递公钥的 PKI,也就解决了前一章提到的数字签名技术无法确认通信对端真实有效性的问题。网络攻防一直都存在,自然也少不了针对证书的攻击,比如攻击者可以在公钥注册认证前替换待认证的公钥,证书申请者可以在向CA认证机构提交公钥时,使用CA公钥加密后再发送给认证机构,同时认证机构也可以再次与申请者确认收到的公钥是否正确。还包括对认证机构本身的攻击,比如窃取认证机构的私钥就可以伪造证书,这主要就靠认证机构如何妥善保管升级自己的私钥了,对于已经泄露的私钥,及时对相关证书进行吊销处理,尽快减小损失。
前面已经介绍了TLS 安全传输的所有技术,包括信息认证加密、共享密钥协商、通信双方身份认证等技术方案,接下来简单介绍TLS 协议的工作原理。前篇博文TLS 加密原理中已经介绍了,TLS 协议是由TLS 记录协议和TLS 握手协议这两层协议叠加而成的,位于底层的TLS 记录协议负责进行信息传输和认证加密,位于上层的TLS 握手协议则负责除加密以外的其它各种操作,比如密钥协商交换、身份认证等。TLS 记录协议的报文结构与工作原理已经在前篇博文中简单介绍过了,这里重点介绍TLS 握手协议。
TLS 握手协议可以分为四个子协议:
TLS 握手过程也可以看到这四个子协议发挥作用的地方,警告协议只在发生错误异常时才发挥作用,所以正常的握手过程只看得到握手协议、密码规格变更协议、应用数据协议这三部分。TLS 1.3 版本进一步优化了整个握手过程,因为废弃了很多不安全的加密算法,仅支持5种安全高效的加密组件(TLS 1.2支持多达37个加密组件),精简的加密组合可以不再需要密码规格变更协议,减少了通信双方握手阶段的往返次数。
由于TLS 1.2 协议仍是目前主流,这里先介绍TLS 1.2 的握手过程(TLS 1.3的优化改进比较大,将在下文介绍),图示如下:
TLS 握手过程主要包括协商双方使用的密码套件、协商交换共享密钥、通信对端身份认证、协商变更密码方式等过程,从安全性角度考虑,完整的握手过程应该包含客户端与服务器端通信双方的身份认证,我们在访问网上银行之类对身份验证比较敏感的网站时,常需要安装数字证书,这个数字证书就是用于进行客户端身份认证的。
但申请使用数字证书是要收费的,数字证书的费用通常由服务器端支付(比如我们使用网上银行在个人电脑上安装的数字证书是由银行帮我们申请并支付费用的),很多时候我们访问网站主要是为了获取信息,并不需要对服务器资源进行敏感重要的修改,服务器并不需要对客户端进行身份认证(可以由上层HTTP协议使用基于账号密码等只有用户知道的信息进行用户认证),因此可以省去服务器向客户端请求身份认证的过程,也就是上图右半部分所示的握手过程。
从安全性与完整性角度考虑,下面主要按照上图左半部分所示的完整握手过程进行介绍,从上图可以看出TLS 1.2 完整的握手过程(从发送第一个握手报文到发送第一个用户数据报文)需要在客户端与服务器之间完成两次往返通信,也即 2-RTT(Round Trip Time)。把客户端与服务器之间完整握手过程中每个握手报文的作用及传递的主要参数字段展开,如下图所示(可以参考博文:The Illustrated TLS 1.2 Connection):
上图中的握手过程主要完成了下面几个任务:
客户端报文名 | 任务描述 | 服务器端报文名 |
---|---|---|
ClientHello - | 通信双方协商确定使用的密码套件与压缩方式 | - ServerHello |
- - Certificate CertificateVerify | 双方通过公钥证书交换公钥,并通过数字签名验证对方的身份 | Certificate CertificateRequest - - |
- ClientKeyExchange | 通信双方协商预备共享密钥(后续还需要通过计算生成主共享密钥),有两种方案: 一、使用(EC)DHE密钥协商算法,双方交换域参数和公共参数,再各自计算出预备共享密钥(推荐,TLS 1.3 支持的唯一密钥协商方案); 二、使用RSA加密法,客户端生成预备共享密钥,使用服务器公钥加密后发送给服务器(不推荐,因不支持前向保密,TLS 1.3 已弃用该方案); | ServerKeyExchange - |
ChangeCipherSpec - | 通信双方约定后续对消息的加密方式将变更为,使用已协商出的共享密钥进行认证加密通信 | - ChangeCipherSpec |
Finished - | 表示握手过程结束,双方对整个握手过程的所有报文消息进行完整性验证,防止攻击者在中途伪造握手报文 | - Finished |
TLS 1.2 握手过程ClientHello与ServerHello报文中,双方交换的随机数有什么用呢?ClientKeyExchange与ServerKeyExchange报文中协商出的共享密钥为何叫预备共享密钥呢?主要是因为使用不同的密钥交换方法,得到的共享密钥长度可能不同,所以需要基于预备共享密钥和客户端/服务器的随机数再计算出主共享密钥,用于后续消息认证加密的密钥。
还记得前篇博文介绍的伪随机函数PRF(Pseudo Random Function)吗?TLS 1.2 握手过程协商出的预备共享密钥作为PRF的secret参数,双方交换的随机数作为PRF的seed参数,经PRF计算得出后续认证加密所用的主共享密钥master_secret:
master_secret = PRF(pre_master_secret, "master secret", ClientHello.random + ServerHello.random)
完整的握手协议比较复杂,需要很多握手消息和两次网络往返(2-RTT)才能开始发送应用数据,如果之前客户端与服务器之间已经完成握手过程,因为网络问题或暂时不用而断开了连接,再次恢复TLS 会话时能否利用之前的握手信息省去一些过程,迅速恢复TLS 会话呢?
还记得前面TLS 1.2握手过程ClientHello与ServerHello报文中的会话ID字段信息吗?最初的会话恢复机制就是利用会话ID字段信息(由服务器在首次收到ClientHello握手报文后创建,并通过ServerHello握手报文传递给客户端)实现会话快速恢复的,客户端和服务器在TLS 连接断开后都会将会话的安全参数保存一段时间。如果客户端想快速恢复会话,将对应的会话ID 通过ClientHello握手报文发送给服务器,服务器如果愿意恢复会话,就将相同的会话ID放入ServerHello消息返回,接着使用之前协商的主共享密钥生成一套新的共享密钥(为了前向保密,尽量做到每次会话使用不同的密钥),通信双方再通过ChangeCipherSpec报文约定变更到新的加密方式,最后通过Finished报文完成简短握手过程的完整性校验,后面就可以进行应用数据认证加密通信了。
TLS 1.2 会话恢复的简短握手过程从发送第一个报文ClientHello到发送应用数据,只需要一次网络往返 1-RTT(上图中的客户端发送5/6报文后可以立即发送应用数据,故在发送应用数据前仅算一次网络往返),显然比前面介绍的完整握手过程高效得多(省去了身份认证过程和密钥协商过程)。但使用会话ID 快速恢复会话也有弊端,比如:
为了解决使用Session ID的问题,又引入了Session Ticket 作为新的会话恢复方案。Session Ticket 是用只有服务器端知道的安全密钥加密过的会话信息(包含恢复会话所需的所有信息),由服务器端在完成完整的握手过程后生成New Session Ticket 并发送给客户端保存。如果客户端想快速恢复会话,可以将对应的Session Ticket 通过 ClientHello 报文发送给服务器,只要服务器能验证Session Ticket 的完整性,并成功解密其内容,就可以使用其中的信息快速恢复会话。
Session Ticket 与 Session ID 会话恢复方案的主要区别就是,Session Ticket 将会话恢复所需的信息保存在客户端(与HTTP Cookie的原理类似),Session ID 将会话恢复所需的信息保存在服务器端,当采用分布式服务器集群时,显然Session Ticket 是更合适的会话恢复方案(TLS 1.3 仅支持类似Session Ticket 的会话恢复方案)。
TLS 1.3 废弃了RSA密钥交换方案(因为RSA不具有前向保密性),仅支持(EC)DHE密钥协商方案,简化的密钥协商过程能带来TLS 握手过程的简化吗?
RSA密钥交换方案需要客户端先拿到服务器的公钥证书,使用服务器公钥来加密要发送的预备共享密钥,所以RSA密钥交换需要在第二个网络往返中交换共享密钥。DHE密钥协商方案则没有这个限制,虽然理论上客户端也需要先拿到服务器提供的域参数(比如前篇博文介绍DHE中的G、P两个参数,或者ECDHE中的椭圆曲线类型)才能计算出自己的密钥协商参数(比如DHE中的Gc mod P),但这并非必要条件。客户端可以像发送支持的加密套件列表那样,向服务器发送支持的域参数组合列表(比如椭圆曲线类型列表)及其对应的密钥协商参数,服务器只需要从列表中选择一组确定为双方使用的域参数即可,这样就可以在第一个网络往返中协商共享密钥了。
TLS 1.3 可以在第一个网络往返中完成共享密钥协商和身份认证,前面介绍TLS 1.2完整握手过程时也谈到,TLS 1.3 精简的加密组合可以不再需要密码规格变更协议,在完成共享密钥协商和身份认证后可以直接切换到应用数据协议,所以TLS 1.3 完整握手过程只需要一个网络往返(1-RTT)便可完成,很显然比TLS 1.2 完整握手所需的2-RTT 要更加高效。TLS 1.3 完整握手过程图示如下:
从TLS 1.3 的握手过程可以看出,在第一个网络往返就借助key_share 扩展字段完成了密钥协商,后续的身份认证报文均被协商出的handshake_key 加密处理,整个握手过程的明文信息减少了。为便于理解key_share 扩展字段是如何交换(EC)DHE密钥协商方案的有限域参数组(比如椭圆曲线类型)和对应的密钥协商参数的,下面给出key_share 字段的数据结构(参考自:TLS 1.3 Handshake Protocol):
struct {
NamedGroup group; //有限域参数组或椭圆曲线类型
opaque key_exchange<1..2^16-1>; // 密钥协商参数
} KeyShareEntry;
// 下面每一个名称对应一个确定的椭圆曲线或一组确定的有限域参数,按优先级从高到低排列,由ClientHello报文的Extension - Supported Groups扩展字段记录这些用于密钥交换的命名组
enum {
/* Elliptic Curve Groups (ECDHE) */
secp256r1(0x0017), secp384r1(0x0018), secp521r1(0x0019),
x25519(0x001D), x448(0x001E),
/* Finite Field Groups (DHE) */
ffdhe2048(0x0100), ffdhe3072(0x0101), ffdhe4096(0x0102),
ffdhe6144(0x0103), ffdhe8192(0x0104),
/* Reserved Code Points */
ffdhe_private_use(0x01FC..0x01FF),
ecdhe_private_use(0xFE00..0xFEFF),
(0xFFFF)
} NamedGroup;
由于取消了密码规格变更协议,服务器在发送完CertificateVerify握手报文后可以直接发送Finished握手结束报文,服务器握手结束后在不需要对客户端进行身份认证时(也即仅对服务器端进行身份认证的情形)可以直接发送应用数据。客户端完成服务器身份认证后向服务器发送自己的身份认证报文,客户端发送完Finished握手结束报文后可以直接向服务器发送应用数据(已对服务器进行过身份认证)。从客户端发送ClientHello握手报文到发送应用数据,中间只经过一次网络往返(1-RTT)。
值得一提的是,TLS 1.3 完整握手过程允许服务器对客户端进行后握手身份认证(Post-Handshake Client Authentication),也即初始握手时先不对客户端进行身份认证,当客户端请求访问某些敏感资源时,才要求客户端进行身份认证。
TLS 1.3握手报文ClientHello与ServeHello在进行密钥协商时并没有进行数字签名,通信双方的身份认证主要靠后续的Certificate与CertificateVerify报文保证,因为CertificateVerify报文是对之前的握手消息(自然包括密钥协商消息)进行签名,身份认证放到后面也不会影响整个握手过程的安全性。把客户端与服务器之间完整握手过程中每个握手报文的作用及传递的主要参数字段展开,如下图所示(可以参考博文:The New Illustrated TLS 1.3 Connection):
上图中的握手过程主要完成了下面几个任务:
客户端报文名 | 任务描述 | 服务器端报文名 |
---|---|---|
ClientHello:Cipher_Suites ClientHello:Signature_Algorithms - - | 通信双方协商确定使用的密码套件和数字签名算法 | - - ServerHello:Cipher_Suites ServerHello:Signature_Algorithms |
ClientHello: Key_Share ClientHello: Supported_Groups - - | 通信双方使用(EC)DHE密钥协商方案,交换椭圆曲线类型和密钥协商参数,再各自计算出预备共享密钥 | - - ServerHello: Key_Share ServerHello: Supported_Groups |
- - Certificate CertificateVerify | 双方通过公钥证书交换公钥,并通过对握手消息的数字签名验证对方的身份 | Certificate CertificateRequest - - |
- Finished | 表示握手过程结束,双方对整个握手过程的所有报文消息进行完整性验证,防止攻击者在中途篡改或伪造握手报文 | Finished - |
TLS 1.3 完整握手过程在ClientHello与ServerHello报文中交换了各自的随机数,也协商出了预备共享密钥,使用预备共享密钥生成主共享密钥的方式跟前面介绍的TLS 1.2差不多,但升级了伪随机函数PRF。
TLS 1.3 使用HKDF(HMAC based Key Derivation Function)生成主共享密钥(TLS 1.2使用的是PRF伪随机函数),相比PRF可以输出安全性更强的新密钥。HKDF包括extract_then_expand的两阶段过程,extract过程增加密钥材料的随机性,在TLS 1.2中使用的密钥派生函数PRF实际上只实现了HKDF的expand部分,并没有经过extract,而直接假设密钥材料的随机性已经符合要求。HKDF的算法比较复杂,这里就不展开介绍了,有兴趣了解的可以参考博文:TLS协议中PRF和TLS1.3中的HKDF。
TLS 1.3 的完整握手过程相比TLS 1.2 已经有了不小的优化,从2-RTT 优化到了1-RTT。如果客户端与服务器之间已完成过完整的握手过程,中间因为某些原因断开了,利用会话缓存快速恢复会话,还能比TLS 1.2 带来更简短快捷的握手过程吗?
得益于TLS 1.3 取消了密码规格变更协议,会话缓存快速恢复的简短握手过程应该是可以比TLS 1.2 更优的,TLS 1.2 会话恢复简短握手只需要一个网络往返 1-RTT,TLS 1.3 要做到更简短,是可以达到 0-RTT 吗?由于通信双方之前已经完成密钥协商与身份认证,会话缓存中是有双方的共享密钥的,TLS 1.3 做到会话恢复 0-RTT 的简短握手也是完全可以的,握手过程如下:
TLS 1.3 采用了新的会话缓存恢复方案 — PSK (pre_shared_key)握手,该方案相当于TLS 1.2 Session Ticket 会话恢复方案的升级版。还记得TLS 1.3 完整握手过程ClientHello报文中出现的两个扩展字段psk_key_exchange_modes和pre_shared_key吗?PSK会话缓存恢复方案正需要借助这两个字段:
字段名 | 作用 |
---|---|
psk_key_exchange_modes | 从PSK建立主密钥的模式有两种,该字段可能的取值也有两种: 1. psk_ke:仅使用PSK建立主密钥,Server 不提供key_share 值(0-RTT); 2. psk_dhe_ke:使用PSK 和 (EC)DHE 建立主密钥,Client 和 Server 必须提供key_share 值(1-RTT)。 |
pre_shared_key (PSK) | 预共享密钥标识,也即"New Session Ticket + Binders",New Session Ticket 包含PSK初始值、名称、有效期等信息,Binders 包含PSK与当前握手的绑定信息。 |
early_data | 当通信双方有预共享密钥 PSK 时,TLS 1.3 允许在ClientHello报文的扩展字段 early data 中携带应用数据(携带数据量不超过max_early_data_size值),early_data中的应用数据使用PSK 生成的early_secret 加密。 |
EndOfEarlyData | 如果在ClientHello报文中发送了early_data,在收到Server_Finished报文后发送EndOfEarlyData报文,表示已传输完了所有 0-RTT application_data 消息,并且接下来的application_data 消息使用新的密钥application_secret 加密 |
TLS 1.3 完成完整握手过程后,服务器也会生成一个 New Session Ticket 并发送给客户端保存。New Session Ticket 中包含恢复共享密钥、有效期、随机数nonce等信息,可以使用New Session Ticket 中的信息生成预共享密钥PSK,过程大概如下:
struct {
uint32 ticket_lifetime;
uint32 ticket_age_add;
opaque ticket_nonce<0..255>;
opaque ticket<1..2^16-1>;
Extension extensions<0..2^16-2>;
} NewSessionTicket;
// 通过HKDF生成预共享密钥PSK,resumption_master_secret是在完整握手过程随主密钥一起生成的
// ticket_nonce对于每个NewSessionTicket都是不同的,保证不同的NewSessionTicket生成不同的PSK
PSK = HKDF-Expand-Label(resumption_master_secret,
"resumption", ticket_nonce, Hash.length)
从上图TLS 1.3 会话恢复简短握手过程可以看出,发送首个报文ClientHello时就可以通过early_data 发送应用数据,因此从发送首个报文到发送应用数据之间并没有经过网络往返,也即0-RTT。因为发送early_data 不依赖ServerHello消息,安全性自然更弱一些,如果攻击者捕获发送到服务器的 0-RTT 数据包,他们可以重播该数据包,并且服务器可能会接受该数据包为有效数据包,甚至借此改变服务器上的重要资源状态,这就是 0-RTT 重放攻击:
为了应对 0-RTT 攻击,一般会限制 0-RTT 会话恢复的使用范围,比如在 0-RTT 中发送不改变服务器资源状态的HTTP GET报文等。TLS 1.3 会话恢复简短握手过程完成后,用于加密应用数据的密钥是双方再计算出来的密钥,包含了双方随机数或握手报文散列值信息,保证每次会话尽可能使用一次性密钥,同时也具备前向保密性。
TLS 主要是为了保证网络信息安全传输,核心是密钥交换、身份认证、加密解密等,我们想优化TLS 性能,可以从加解密效率、密钥交换效率、身份认证效率、附加传输数据量、握手过程网络往返次数、网络往返时间等方面着手。
TLS 协议最重要的数据处理就是加解密,包括对称加解密、MAC计算、密钥协商计算、数字签名与验证等。
首先,看对称加密与MAC计算相关的算法效率对比(参考前篇博文:TLS 1.2/1.3 加密原理):
从上图可以看出,选择安全高效的加解密算法,能显著提高TLS 的整体效率。TLS 1.3 优先推荐的认证加密算法AES-GCM 支持并向计算,具有更高的加解密效率。
接下来看密钥协商算法的效率对比:
从上图可以看出,椭圆曲线运算能显著提高密钥协商算法的效率,TLS 1.3优先推荐的ECDHE-ECDSA密钥协商与数字签名算法具有更高的效率,能明显提升TLS 的整体效率。公钥证书的签名算法自然也包含在内,使用椭圆曲线运算的签名算法能带来更高的身份验证效率。
除了选择效率更高的加解密算法和密钥协商-数字签名算法,还可以在有特殊需求的设备上,使用硬件安全模块(hardware security module),带来更进一步的效率提升。硬件安全模块可以在占用更少处理器资源的情况下,实现比处理器更高的计算效率,因为硬件安全模块是为加解密算法专门优化设计的处理器,自然比电脑上使用的通用处理器有更高的加解密效率。
TLS 协议数据传输主要包括传输数据量、握手过程往返次数、网络往返时间等。
TLS 握手协议身份验证过程,需要传输整个数字证书信任链,因为根认证机构数量有些,不可能所有的服务器都直接找根认证机构申请证书。为了提高证书的申请与管理效率,证书的申请认证是分层级的,从根证书到申请机构可能有多个中间层级的认证机构,这些中间层级的认证机构都需要有上一级的认证机构数字签名才是可信的,最终一直到根认证机构签名。我们使用的操作系统或浏览器中可能只有根认证机构的公钥,从根认证机构的证书逐层往下验证到服务器的证书颁发机构。很显然,证书链越短,进行身份认证需要传输的数据量越少,身份认证效率自然越高。
证书的吊销检查也是影响效率的一个关键环节,使用OCSP(Online Certificate Status Protocol)进行吊销状态实时查询显然比CRL(Certificate Revocation List)能带来更高的查询效率。为了进一步优化证书吊销状态查询时间,服务端可以在证书链中封装证书颁发机构的 OCSP响应(服务器可提前查询证书状态,获得OCSP响应),从而让浏览器跳过在线证书状态查询过程,这种优化技术叫做OCSP stapling。
TLS 记录报文的附加数据量(比如报文标头、初始向量/Nonce、填充/HMAC等)也是影响效率的一环,上层对应用数据的压缩可以减小传输量,选用不同的密码套件需要的附加数据量大小也有区别,下面给出几种加密算法的传输开销对比(传输时需要附加的数据量大小):
从上图可以看出,认证加密算法AES-GCM 和 ChaCha20-Poly1305(因RC4已不再安全,这里替换为ChaCha20)具有更小的传输开销,能为 TLS 带来整体的传输效率提升。
接下来看TLS 握手过程的性能优化,最直观的就是网络往返次数与单次往返时间,先看TLS 1.2与TLS 1.3 分别在首次完全握手与会话恢复简短握手过程中需要的网络往返次数对比:
从上图可以看出,TLS 1.3 不管是在首次完整握手过程,还是会话恢复的简短握手过程中,都比TLS 1.2 少了一次网络往返,自然能带来更高的握手效率。因此,使用TLS 1.3 不仅更安全,更重要的是能带来明显的效率提升。
还有一个影响TLS 效率的重要因素:单次网络往返时间,减少了网络往返次数,自然也不能放过对单次网络往返时间的优化。影响单次网络往返时间的因素主要有哪些呢?主要是网络带宽与网络距离两个方面,网络带宽由宽带运营商提供,购买更大带宽的网络服务自然能明显提高网络传输效率。
如果网络带宽固定,能否通过缩短网络距离来获得更高的传输效率呢?你可能会说服务器和客户端的位置都是固定的,怎么缩短二者之间的距离呢?客户端访问服务器重要的是访问上面的资源,也就是说哪个设备上面有我们想要访问的资源,哪个设备就可以看作服务器,虽然源服务器位置固定,但可以把相应的资源放到离客户端更近的设备上,这就缩短了网络访问距离。
有一种很流行的缩短网络访问距离的方案CDN(Content Delivery Network)内容分发网络,可以将用户可能访问的资源放到地理上分散的服务器缓存(边缘缓存)中,用户直接从就近的边缘服务器中获取资源,网络访问距离自然大大缩短。CDN中这些地理上分散的服务器还可以与源服务器建立长时间的连接,来降低客户端与源服务器建立连接的成本,缩短了网络连接距离。
TLS 握手过程如果从与远端的源服务器握手,到与附近的CDN服务器握手,这就极大的缩短了网络访问距离,自然也就缩短了单次网络往返时间,显著提高 TLS 传输效率。
从上图可以看出,用户与附加的CDN服务器进行数据传输的单次网络往返时间明显短得多,用户需要访问的资源如果附加的CDN服务器没有,可以由该边缘服务器向源服务器请求相应资源后给用户响应。
从TLS 协议与下层网络协议的交互过程着手,也可以想到性能优化的方法,我们知道网络分层架构中每层协议支持的数据包大小不同,每一层协议都会对应用数据进行分片封装,TLS 协议要想减少网络传输的数据包总数,可以设置合适的TLS 记录报文大小来配合下层协议,尽量让下层协议尽可能按最大能力传输数据包,不至于出现很多较小数据量的网络数据帧。通俗点说就是,尽可能让每个网络数据帧都接近满载传输,避免出现接近空载的网络数据帧。