tls
tls握手面试可能会问, 之前我恰好也有点兴趣, 所以找了一些东西学习了一下.
tls1.2
握手
目前主流的tls1.2握手会使用ECDHE, 比较早期的只是简单的RSA密钥交换.
- RSA密钥交换
- 客户端准备一个随机数A, 发给服务端
- 服务端准备一个随机数B, 跟自己的证书一起发给客户端
- 客户端准备一个随机数C(这个C也叫预主密钥), 校验证书, 用证书上的公钥加密随机数C, 将密文发给服务端(Finished)
- 服务端解密随机数, 用预主密钥和两个随机数计算出通话密钥(Finished)
这个Finished是指, 将握手信息哈希, 然后用通话密钥加密发过去, 让对方确认.
在这里可以发现, 预主密钥完全是客户端决定的. 并且这里证书不仅参与了验证身份, 还参与了加密预主密钥.
如果未来有一天服务端的私钥泄漏了, 并且中间人保留了历史流量. 那中间人就可以利用泄漏的私钥, 解密得到预主密钥, 进而计算出通话密钥, 这个会话的数据就泄漏了.
- ECDHE
在此之前, 需要介绍一下椭圆曲线算法. 一般来讲是这样的
其中, K是客户端或服务端的临时私钥, G是算法规定的数字, 公开的,
因此, 有
大概就是这样.
客户端会准备一个临时私钥, 并计算出自己的椭圆曲线参数(Key Share), 然后发给服务端.
服务端拿到客户端Key Share后, 就可以计算出结果了. 然后把自己的Key Share也发过去, 让客户端也计算.
在采用ECDHE的tls1.2中, 预主密钥是双方通过这种方式协商得到的.
- 客户端准备一个随机数, 发过去
- 服务端准备一个随机数和一个临时私钥, 计算出Key Share并用证书对应的私钥签名, 把随机数, KeyShare, 签名和证书一起发过去
- 客户端准备一个临时私钥, 校验证书, 计算自己的Key Share发过去, Finished
- 服务端Finished
这里的预主密钥是双方协商得到的, 之后通话密钥同样是使用预主密钥和两个随机数计算得到.
不管怎么样, tls1.2的握手是2RTT
会话恢复
在握手完成前, 服务端会将通话密钥和一些信息(如过期时间)打包, 用自己的另一个私钥加密, 得到session ticket. 这东西类似jwt, 也是无状态的. 不过它是密文.
然后服务端会把这个ticket发给客户端, 会话恢复的时候客户端就可以带上ticket了.
- 客户端准备一个随机数, 将随机数和ticket发给服务端
- 服务端解密ticket, 如果有效, 提取出上次的通话密钥作为预主密钥, 准备一个随机数, 然后发给去并Finished
- 客户端使用上次的通话密钥作为预主密钥, 和两个随机数计算出通话密钥, Finished. 这里已经可以带上应用数据了.
因此, 会话恢复是1RTT
tls1.3
握手
tls1.3废弃了很多方法, 一般采用ECDHE.
- 客户端会猜测服务端支持哪些算法, 并将自己的Key Share发过去
- 如果命中, 服务端准备一个Key Share, 计算出通话密钥. 然后用通话密钥加密证书, 跟自己的Key Share一起发过去, Finished
- 客户端拿到服务端Key Share, 计算出通话密钥, 解密证书并校验, Finished
同样的, 最后一步客户端也可以带上应用数据了.
因此, tls1.3的握手是1RTT的.
这里的证书只参与身份校验了.
会话恢复
在握手完成前, 双方会使用通话密钥计算出一个预共享密钥(PSK). 服务端会将PSK和一些信息打包, 用自己的另一个私钥加密得到session ticket发过去.
- 客户端用PSK加密应用数据, 然后将应用数据密文, ticket和新的Key Share发过去
- 服务端解密ticket, 如果有效, 提取出PSK并解密应用数据. 同时, 计算出新的通话密钥, 加密自己的应用数据. 然后将新的Key Share, 应用数据密文发过去
- 客户端用两个新的Key Share计算出新的通话密钥, 解密应用数据
在这里, 客户端不需要握手就可以发送应用数据. 因此, tls1.3的会话恢复是0RTT的.
不过由于ticket是无状态的, 只要在有效期内可以随意使用. 因此, 一般要求0RTT的请求必须是幂等请求.