来自公众号:新世界杂货铺
在“码了2000多行代码就是为了讲清楚TLS握手流程”这一篇文章的最初挖了一个坑,明天这篇文章就是为了填坑而来,因而本篇次要剖析TLS1.2的握手流程。
在写前一篇文章时,笔者的Demo只反对解析TLS1.3握手流程中发送的音讯,写本篇时,笔者的Demo曾经能够解析TLS1.x握手流程中的音讯,有趣味的读者请至文末获取Demo源码。
论断后行
为保障各位读者对TLS1.2的握手流程有一个大略的框架,本篇仍旧论断后行。
单向认证
单向认证客户端不须要证书,客户端验证服务端证书非法即可拜访。
上面是笔者运行Demo打印的调试信息:
依据调试信息知,TLS1.2单向认证中总共收发数据四次,Client和Server从这四次数据中别离读取不同的信息以达到握手的目标。
笔者将调试信息转换为下述时序图,以不便各位读者了解。
双向认证
双向认证不仅服务端要有证书,客户端也须要证书,只有客户端和服务端证书均非法才可持续拜访(笔者的Demo如何开启双向认证请参考前一篇文章中HTTPS双向认证局部)。
上面是笔者运行Demo打印的调试信息:
同单向认证一样,笔者将调试信息转换为下述时序图。
双向认证和单向认证相比,Server发消息给Client时会额定发送一个certificateRequestMsg
音讯,Client收到此音讯后会将证书信息(certificateMsg
)和签名信息(certificateVerifyMsg
)发送给Server。
双向认证中,Client和Server发送的音讯变多了,然而总的数据收发依然只有四次。
总结
1、单向认证和双向认证中,总的数据收发仅四次(比TLS1.3多一次数据收发),单次发送的数据中蕴含一个或者多个音讯。
2、TLS1.2中除了finishedMsg
其余音讯均未加密。
3、在TLS1.2中,ChangeCipherSpec
音讯之后的所有数据均会做加密解决,它的作用在TLS1.2中更像是一个开启加密的开关(TLS1.3中疏忽此音讯,并不做任何解决)。
和TLS1.3的比拟
音讯格局的变动
比照本篇的时序图和前篇的时序图很容易发现局部音讯格局产生了变动。上面是certificateMsg
和certificateMsgTLS13
的定义:
// TLS1.2type certificateMsg struct { raw []byte certificates [][]byte}// TLS1.3type certificateMsgTLS13 struct { raw []byte certificate tls.Certificate ocspStapling bool scts bool}
其余音讯的定义笔者就不一一列举了,这里仅列出格局发生变化的音讯。
TLS1.2 | TLS1.3 |
---|---|
certificateRequestMsg | certificateRequestMsgTLS13 |
certificateMsg | certificateMsgTLS13 |
音讯类型的变动
TLS1.2和TLS1.3有雷同的音讯类型也有各自独立的音讯类型。上面是笔者例子中TLS1.2和TLS1.3各自独有的音讯类型:
TLS1.2 | TLS1.3 |
---|---|
serverKeyExchangeMsg | - |
clientKeyExchangeMsg | - |
serverHelloDoneMsg | - |
- | encryptedExtensionsMsg |
音讯加密的变动
前篇中提到,TLS1.3中除了clientHelloMsg
和serverHelloMsg
其余音讯均做了加密解决,且握手期间和利用数据应用不同的密钥加密。
TLS1.2中仅有finishedMsg
做了加密解决,且利用数据也应用该密钥加密。
TLS1.3会计算两次密钥,Client和Server读取对方的HelloMsg
和finishedMsg
之后即可计算密钥。
“Client和Server会各自计算两次密钥,计算机会别离是读取到对方的HelloMsg和finishedMsg之后”,这是前篇中的形容,计算机会形容不精确以下面为准。
TLS1.2只计算一次密钥,Client和Server别离收到serverKeyExchangeMsg
和clientKeyExchangeMsg
之后即可计算密钥,和TLS1.3不同的是TLS1.2密钥计算后并不会立刻对接下来发送的数据进行加密,只有当发送/承受ChangeCipherSpec
音讯后才会对接下来的数据进行加解密。
生成密钥过程
TLS1.2和TLS1.3生成密钥的过程还是比拟类似的, 下图为Client读取serverKeyExchangeMsg
之后的局部解决逻辑:
图中X25519
是椭圆曲线迪菲-赫尔曼(Elliptic-curve Diffie–Hellman ,缩写为ECDH)密钥替换计划之一,这在前篇曾经提到过故本篇不再赘述。
依据Debug后果,本例中ka.preMasterSecret
和TLS1.3中的共享密钥生成逻辑完全一致。不仅如此,在后续的代码剖析中,笔者发现TLS1.2也应用了AEAD
加密算法对数据进行加解密(AEAD在前篇中曾经提到过故本篇不再赘述)。
下图为笔者Debug后果:
图中prefixNonceAEAD
即为TLS1.2中AEAD加密算法的一种实现。
这里须要留神的是TLS1.3也会计算masterSecret
。为了不便了解,咱们先回顾一下TLS1.3中生成masterSecret
的局部源码:
// 基于共享密钥派生hs.handshakeSecreths.handshakeSecret = hs.suite.extract(hs.sharedKey, hs.suite.deriveSecret(earlySecret, "derived", nil))// 基于hs.handshakeSecret 派生hs.masterSecreths.masterSecret = hs.suite.extract(nil, hs.suite.deriveSecret(hs.handshakeSecret, "derived", nil))
由上易知,TLS1.3先通过共享密钥派生出handshakeSecret
,最初通过handshakeSecret
派生出masterSecret
。与此相比,TLS1.2生成masterSecret
仅需一步:
hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random)
masterFromPreMasterSecret
函数的作用是利用HMAC
(HMAC在前篇中曾经提到故本篇不再赘述)算法对Client和Server的随机数以及共享密钥进行摘要,从而计算失去masterSecret
。
masterSecret
在后续的过程中并不会用于数据加密,上面笔者带各位读者别离看一下TLS1.3和TLS1.2生成数据加密密钥的过程。
TLS1.3生成数据加密密钥(以Client计算serverSecret为例):
serverSecret := hs.suite.deriveSecret(hs.masterSecret, serverApplicationTrafficLabel, hs.transcript)c.in.setTrafficSecret(hs.suite, serverSecret)
前篇中提到hs.suite.deriveSecret
外部会通过hs.transcript
计算出音讯摘要从而重新得到一个serverSecret
。setTrafficSecret
办法外部会对serverSecret
计算失去AEAD加密算法所须要的key和iv(初始向量:Initialization vector)。
因而可知TLS1.3计算密钥和Client/Server生成的随机数无间接关系,而与Client/Server以后收发的所有音讯的摘要无关。
补充:
IV通常是随机或者伪随机的。它和数据加密的密钥一起应用能够减少应用字典攻打的攻击者破解明码的难度。例如,如果加密数据中存在反复的序列,则攻击者能够假设音讯中相应的序列也是雷同的,而IV就是为了避免密文中呈现相应的反复序列。参考:
https://whatis.techtarget.com...
https://en.wikipedia.org/wiki...
TLS1.2生成数据加密密钥:
clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV := keysFromMasterSecret(tr.vers, suite, p.masterSecret, tr.clientHello.random, tr.serverHello.random, suite.macLen, suite.keyLen, suite.ivLen)serverCipher = hs.suite.aead(serverKey, serverIV)c.in.prepareCipherSpec(c.vers, serverCipher, serverHash)
前文中提到masterSecret
的生成与Client和Server的随机数无关,而通过keysFromMasterSecret
计算AEAD所需的key和iv仍旧与随机数无关。
小结:
1、本例中TLS1.2和TLS1.3均应用X25519
算法计算共享密钥。
2、本例中TLS1.2和TLS1.3均应用AEAD
进行数据加解密。
3、TLS1.3通过共享密钥派生两次才失去masterSecret
,而TLS1.2以共享密钥、Client和Server的随机数一起计算失去masterSecret
。
4、TLS1.3通过音讯的摘要再次计算失去一个数据加密密钥,而TLS1.2间接通过masterSecret
计算失去AEAD所需的key和iv。
TLS1.1和TLS1.0不反对HTTP2
在后面提到本文的例子曾经反对解析TLS1.x的握手流程,这个时候笔者忽然很好奇浏览器还反对那些版本的TLS协定。
而后笔者在谷歌浏览器上首先测试了TLS1.1的服务,为了不便测试笔者革新了之前服务器推送的案例:
server := &http.Server{Addr: ":8080", Handler: nil}server.TLSConfig = new(tls.Config)server.TLSConfig.PreferServerCipherSuites = trueserver.TLSConfig.NextProtos = append(server.TLSConfig.NextProtos, "h2", "http/1.1")// 服务端反对的最大tls版本调整为1.1server.TLSConfig.MaxVersion = tls.VersionTLS11server.ListenAndServeTLS("ca.crt", "ca.key")
运行Demo后失去如下截图:
图中红框局部obsolete
的意思笔者也不知,正好学习一波(技术人的英语大略就是这样缓缓积攒起来的吧)。
这下笔者明确了,TLS1.1曾经不被反对所以页面才无奈失常拜访,然而事实真是如此嘛?
直到几天后笔者开始写这篇文章时,心田仍是非常纳闷,于是应用了curl
命令再次拜访。
图中蓝框局部正是TLS1.1的握手流程,有趣味的读者能够应用笔者的例子和curl -v
命令进行双向验证。
图中红框局部提醒说“HTTP2的数据发送失败”,笔者才豁然开朗,将上述代码作如下微调后页面可失常拜访。
server.TLSConfig.NextProtos = append(server.TLSConfig.NextProtos, "http/1.1")
通过笔者的测试,TLS1.0同TLS1.1一样均不反对HTTP2协定,当然这两个协定也不举荐持续应用。
写在最初
“纸上得来终觉浅,绝知此事需躬行”。笔者不敢保障把TLS握手流程的每个细节都讲的非常分明,所以倡议各位读者去github克隆代码,而后本人一步一步Debug必然可能加深印象并彻底了解。当然,顺便关注或者star一下这种顺手为之的小事,笔者置信各位读者还是非常乐意的~
最初,衷心希望本文可能对各位读者有肯定的帮忙。
注:
- 写本文时, 笔者所用go版本为: go1.15.2
- 文章中所用残缺例子:https://github.com/Isites/go-...