TLS协议
一,TLS协议的组成
TLS协议架构模块分为两层:TLS记录协议,TLS握手协议
① TLS记录协议: 是所有子协议的基层,规定了TLS收发数据的基本单位。所有子协议都需要通过记录协议发出,多个记录数据可以在一个TCP中发送。主要负责使用对称密码对消息进行加密。
② TLS握手协议:
- 握手协议:客户端和服务端在握手过程中协商 TLS版本号,随机数,密码套件,交换证书,最终获取会话密钥。
- 密码规格变更协议:发送一个通知,告知对方后续的通信采用加密保护。
- 警告协议:向对方发出警告,类似于http中的状态码。不支持旧版本,证书异常都会发送警报,另一方收到警报后可以进行相应的处理
- 应用数据协议:将TLS承载的应用数据传达给通信对象
二,TLS的握手过程
上图是TLS握手的一个简易流程,每一个框就是一个 TLS Record(记录),多个Record合并成一个TCP来进行发送,可以看到整个TLS握手只需要2次消息往返即2RTT就能完成 ( TLS 1.2是2RTT,TLS 1.3可以做到 1RTT甚至0RTT )
接下来我们来结合wireshark抓包来详细介绍每一步的流程:
1,ClientHello
首先客户端先向服务端发起握手请求,在这个请求过程中client会携带以下参数
- TLS 版本: 确定使用1.2还是1.3 但是这个地方固定是1.2。之后我们会在1.2与1.3的兼容性中聊到
- Client提供的随机数: 用于后续生成会话密钥
- Cipher Suites: 可用密码套件列表,供服务端选择
- 会话ID(可忽略): 可用于之后TLS1.3的优化 —— 会话缓存
- 可用压缩方式
密码套件
客户端和服务端在使用TLS建立连接时需要选择一组恰当的加密算法来实现安全通信,这些算法的组合就被称为 密码套件
如图中的密码套件那一行:TLS_ECDHE_RSA_WITH_AES_128-CBC-SHA
他的意思是
- 握手时使用ECDHE算法进行密钥交换
- 用RSA签名和身份认证
- 握手后的通信使用 AES对称算法,密钥长度 128 位
- 分组模式为 CBC
- 摘要算法为 SHA 用于消息认证和产生随机数
2,ServerHello
服务端接收到客户端的握手请求,会回复客户端同时 发送 ServerHello,ServerHello会根据之前的ClientHello的参数来做如下操作:
- **确认 TLS版本 **
- 服务器提供的随机数
- 选择的密码套件
3,Server Certificate
数字证书 与 CA
① CA证书:
我们知道 在 https中 加密采用了非对称加密,通过私钥加密,公钥解密。私钥由双方生成并维护。但是公钥不能任意生成,公钥涉及到一个公钥信任问题,谁都能发布公钥的话,黑客就可以伪造公钥来进行攻击。所以说公钥得由一个公认可信的第三方生成,让他作为信任的起点,递归的终点,构建信任链,这个第三方就是 CA(证书认证机构)。② CA证书链:
CA可以签发三种证书,他们的区别在于可信程度,按照可信程度从高到低来排序即为:EV, OV, DV。
CA如果想证明自己,可以找上级CA为自己签发证书,不断向上直到 Root CA。Root CA会给自己签发自签名证书或者根证书,这个证书是必须相信的。如下图所示即为数字证书CA的信任链
③ 证书弱点:
证书的签发可能失误或者被欺骗,签发了错误的证书,这个时候可以通过 CRL(证书吊销列表) 和 OCSP(在线证书状态协议)来及时对问题证书进行废止
服务端会将自己的证书链发送给客户端,交给客户端进行验证。值得注意的不是只发送一个证书,前文有说到 二级CA的证明需要一级CA的证明,验证二级证书的签名需要一级证书的公钥来进行解密,所以需要返回一整个证书链(无需携带根证书,用户浏览器自带了根证书)
4,Server Key Exchange
以 ECDHE算法为例,服务端生成一个 公钥 名为Server Params,用来实现密钥交换算法,再用私钥进行签名认证
Handshake Protocol: Server Key ExchangeEC Diffie-Hellman Server ParamsCurve Type: named_curve (0x03)Named Curve: x25519 (0x001d)Pubkey: 3b39deaf00217894e...Signature Algorithm: rsa_pkcs1_sha512 (0x0601)Signature: 37141adac38ea4...
5,Client Key Exchange
客户端首先会先对证书链进行验证,再根据服务端所选的算法以及Server Params来生成对应的Client Params返回给服务端
Handshake Protocol: Client Key ExchangeEC Diffie-Hellman Client ParamsPubkey: 8c674d0e08dc27b5eaa…
此时服务端和客户端拥有了 Client Params,Server Params,Client Random,Server Random,服务端会先使用Params用ECDHE算法生成一个Pre-Master,再用 Random + Pre-Master 生成主密钥 Master Secret。
master_secret = PRF(pre_master_secret, "master secret",ClientHello.random + ServerHello.random)// 之所以需要使用三个随机数来生成主密钥,是因为TLS的设计者并不信任服务端和客户端生成的随机数的可靠性。所以将三个不可靠的随机数混合起来来提升随机数的随机程度。
有了主密钥后并不会直接进行使用,而是通过PRF算法扩展出更多的派送会话密钥,来用作每次会话的独立密钥。这样即使主密钥泄露已传输的数据仍然安全,因为每次会话的密钥是独立的具备前向安全性。
6,Change Chiper Spec & Finished
通信转换为使用会话密钥 + 对称算法进行加密通信,并且握手结束。
由于使用的是ECDHE,客户端可以无需等待服务端返回 Finished就直接发出HTTP报文,省去等待一个消息返回的时间浪费,这个叫做
TLS False Start
图示总结(以ECDHE握手为例)
三,TLS 1.3
TLS 1.3 相比于 TLS 1.2有了以下几项巨大的提示:兼容,性能,安全
1,兼容性
在TLS 1.2时期,版本号是记录在Version字段的,即Version:TLS 1.2(0x0303)。我们可能理所应当的认为TLS 1.3就是Version:TLS 1.3(0X0304)。但是这样肯定不行,这样会导致大量只认老协议的代理服务器,网关都无法处理请求,导致握手失败。
如果要进行区分1.2 和 1.3就要通过新引入的协议 扩展协议,在当前的记录后添加一列拓展字段,老版本因为不认识则直接舍弃,新版本则可以对其进行识别处理,这便实现了向后兼容
所以 TLS 1.3 还是保留 Version:TLS 1.2(0x0303),只不过在 扩展协议 中新加了有一个 #supported_version扩展,在这个扩展中标记了TLS的版本号
Handshake Protocol: Client HelloVersion: TLS 1.2 (0x0303)Extension: supported_versions (len=11)Supported Version: TLS 1.3 (0x0304)Supported Version: TLS 1.2 (0x0303)
2,性能提升
TLS 1.3 相比于 TLS 1.2最大性能提升就是 原先TLS1.2握手需要 2RTT,现在TLS1.3握手只需 1RTT,性能整整提升了一倍。
优化的点如下:
1,TLS 1.3舍弃了之前的繁琐的协商方式,在第一次Client Hello时 通过扩展组就将签名算法,客户端公钥参数(Client Params)等等内容全部提供了服务端。同时服务端会在Server Hello时就携带好各种参数以及ChangeCipherSpec。
通过这两点 成功将握手的报文发送提升至 1RTT
3,安全性(待补充)
废弃了一些分组和加密算法,由于没具体学加密算法,学了之后再补充
密码套件大大减少,得益于密码套件的减少,在Client Hello阶段可以直接生成公钥参数以及曲线
四, TLS 1.3 握手过程
1,Client Hello
Handshake Protocol: Client HelloVersion: TLS 1.2 (0x0303)Random: cebeb6c05403654d66c2329…Cipher Suites (18 suites)Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303)Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302)Extension: supported_versions (len=9)Supported Version: TLS 1.3 (0x0304)Supported Version: TLS 1.2 (0x0303)Extension: supported_groups (len=14)Supported Groups (6 groups)Supported Group: x25519 (0x001d)Supported Group: secp256r1 (0x0017)Extension: key_share (len=107)Key Share extensionClient Key Share Length: 105Key Share Entry: Group: x25519Key Share Entry: Group: secp256r1
TLS 1.3与TLS 1.2相比而言,新增了三个拓展项
- supported_version:这个我们之前有提到,TLS 1.3需要使用该拓展项来进行版本区分
- supported_group:支持的曲线
- key_share:曲线对应的参数
可以看到相比于1.2,1.3再hello的时候就将自身支持的参数发送给了服务端
2,Server Hello
收到客户端的消息后,此时服务端就拥有了 Client Random 和 Server Random 、Client Params 和 Server Params,可以直接通过ECDHE算出 Pre-Master。
此时通过 Certificate Verify一个安全增强措施,用私钥将曲线,套件,参数等数据进行数字签名,强化了身份认证和防篡改。
可以发现 生成会话密钥以及进入加密通信的时间要比TLS 1.2更加的早。TLS 1.2还需要进行一次繁琐的 Key Exchange,而1.3在两次Hello中就已经完成了Key的交换
五,HTTPS的优化
HTTPS在HTTP握手的基础上加上了一个TLS握手,所以他的连接速度肯定也会有所下降,有时甚至会比HTTP慢上几秒(远古时期),这下子 HTTPS的已经刻板印象的变成 “Slow” 了。那本章我们来分析 HTTPS究竟慢在哪以及如何优化。
首先HTTPS的加密分为了两部分 TLS握手前的非对称加密 和 TLS握手后的对称加密
**对称加密:**目前TLS支持的对称加密算法性能很好 再加上硬件优化,这块的性能损耗很小可以忽略不计
那我们就可以把 [慢] 聚集于 TLS握手期间,我们可以将销毁总结为以下几点:
- 握手所需的额外报文传输
- 验证证书时 需要访问 CA 获取 CRL或OCSP
- 非对称加密处理 Pre-Master
- 产生用于密钥交换的临时公私钥对
1,加解密优化:
① SSL加速卡 or SSL加速服务器: 使用专门的加速卡来分担CPU的计算压力 或者 使用 SSL加速服务器集群 [卸载] TLS握手时的加解密计算,提升加解密的速度
② 协议优化:
选择更快更好的密码套件,会使加解密,密钥交换过程中更加快速 例如:
性能更高的曲线:x25519
对称加密算法:AES_128 会比 AES_256 更好一点
密钥交换协议:ECDHE运算更快,安全性高,支持False Start,将握手消息往返由 2-RTT减少至 1-RTT
这些都可以在nginx的配置文件中进行设置 例如:ssl_ciphers、ssl_ecdh_curve
ssl_ciphers TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:EECDH+CHACHA20;
ssl_ecdh_curve X25519:P-256;
2,证书优化:
在TLS 1.2中我们有说到,服务器需要将自己的证书链发送给客户端。那这里 证书的传输 以及 证书的验证 就是性能损耗的关键点。
证书的传输: 在传输的方面,我们需要减小证书的大小,这样可以节约带宽以及减小计算量。那我们可以选择ECDHE椭圆曲线的证书,ECDHE证书(224位)相比于RSA证书(2048位)小了很多。
证书的优化: 证书在验证过程中不仅要面对繁琐的解密,如果证书撤销,客户端还需要访问CA,下载CRL或者OSCP的数据,产生DNS查询,连接建立,收发数据等一系列网络通信。
那我们可以选择将 CRL和OSCP保存起来,这样就无需进行额外的网络查询。
- **CRL(不推荐):**CRL是定期发布的,这样会存在时间窗口的安全隐患,而且CRL中存放的是所有被吊销的证书,这个列表只会不断变大,动辄 几MB 保存CRL是不可取的。
- **OCSP Stapling:**这是OSCP的一个新补丁,原先的OCSP是在线证书状态协议,他也需要查询CA然后获取证书状态。但是OCSP Stapling 我们可以让服务器预先访问CA获取OCSP的响应,在握手的过程中将证书和OCSP查询的状态在握手时一起返回给客户端,这样就无需进行额外的查询。
3,会话复用
在客户端和服务器首次连接后会各自保存一个Session ID,并且为当前会话生成一个主密钥Master。那我们能不能将主密钥Master生成这个过程跳过了,这样就跳过了证书验证和密钥交换。答案是 有的,兄弟有的
① 缓存SessionID(1RTT): 在第一次连接后,服务器内存保存SessionId以及当前会话的主密钥,当客户端再次连接时发送一个ID过来,就从缓存中寻找,如果找到对应的主密钥则恢复会话,并且跳过证书验证和密钥交换。
② Session Ticket(1RTT): 第一种是由服务器缓存会话ID,但是对于千万级连接的服务器,这样的内存开销是十分大的。所以这个时候我们可以做一个缓存对象转换.
1,第一次访问时服务端,使用有效期的ticket密钥对Session签发的Ticket,客户端接收到后缓存Ticket。
2,客户端下一次建立连接时使用拓展 session_ticket发送 Ticket。
3,服务端获取Ticket,并进行解密校验是否有效,是否超时。如果正常则恢复会话并进行加密通信。
③ 预共享密钥(0RTT): 在TLS1.3下,可以通过该方法达到 0RTT,在Ticket的基础上同时携带上应用数据(Early Data),这种方式叫做Pre-shared Key,检简称**PSK **
这样可以免去服务器确认的步骤,实现0-RTT。