💖SSL/TLS专栏导航💖 |
---|
💥1. SSL/TLS原理知识 |
💥2. Go源码中TLS实现 |
💥3. openssl中TLS实现 |
💥4. SSL卸载 |
💥5. SSL代理 |
💥6. SSL V.P.N |
💥7. SSL 与 IPSec |
💥8. 其他 |
获取PDF版本请搜索关键字:“TLS详解” |
通常情况下,第三次握手报文的载荷包括:
- 可选的Certificate载荷
- ClientKeyExchange载荷
- 可选的CertificateVerify载荷
- ChangeCipherSpec载荷
- Finished载荷
但是在ECDHE方式的TLS握手流程中,主要有:ClientKeyExchange、ChangeCipherSpec、Finished载荷。第三次握手报文如下:
ClientKeyExchange载荷无论在ECDHE密钥交换流程中,还是在RSA密钥交换流程中,都是个非常重要的交互载荷:在RSA流程中,ClientKeyExchange用来传递经过服务端公钥加密的预主密钥; 在ECDHE流程中,ClientKeyExchange载荷用来传递客户端的公钥信息。在ECDHE密钥交换过程中,正是通过该载荷的交换,服务端和客户端同时拥有了两个随机数、两个公钥以及各自的私钥,这便具备了生成TLS密钥的所有材料。之后便是预主密钥、主密钥、会话密钥,最后进行加密通讯。
1. 验证客户端证书
略
2. 计算预主密钥、主密钥
预主密钥和主密钥的计算方式涉及如下几个参数,通过TLS的握手报文,客户端和服务端都已经拥有下面的密钥材料,客户端和服务端各自计算出相同的预主密钥、主密钥以及会话密钥。
预主密钥的计算有两种方式:
🏆 当采用RSA算法进行密钥交换时,则在客户端通过随机产生;然后通过服务端证书的公钥进行加密,最后通过ClientKeyExchange载荷发送到服务端;
🏆 当采用ECDHE算法进行密钥交换时,则需要使用对端的公钥、本端的私钥、以及采用的椭圆曲线函数分别计算生成预主密钥。
Go中生成预主密钥的流程如下:
通过第Client Hello和Server Hello报文的交互,客户端和服务端已经确定了采用的算法套件,进而也就是确定了ServerKeyExchange和ClientKeyExchange载荷的内容以及处理方式。在第二个握手报文的“疑问”小结中,已经列出了算法套件与对应的密钥交换结构。由于此次TLS握手采用的是ECDHE的方式进行密钥配送,因此对应的密钥交换结构为ecdheKeyAgreement
。
从ecdheKeyAgreement
结构的SharedKey()
方法中可以看出,预主密钥的计算涉及:对端的公钥,本端的私钥,以及采用的椭圆曲线。
预主密钥计算完毕后,便可以生成主密钥。主密钥的计算方式比较简单:
到目前为止,预主密钥、主密钥都已经得到。后面便是可以生成6把会话密钥了
3. 制作会话密钥
会话密钥有3类共计6把:
密钥名称 | 作用 |
---|---|
client MAC | 客户端消息认证码密钥 |
server MAC | 服务端消息认证码密钥 |
client KEY | 客户端对称加密密钥 |
server KEY | 服务端对称加密密钥 |
client IV | 客户端初始向量,对称加密时使用 |
server IV | 服务端支持向量,对称加密时使用 |
会话密钥的制作是在RFC2246(TLS1.0)中**“6.3. Key calculation”**定义的,它的计算方式如下:
Go中计算会话密钥的函数如下:
6把会话密钥计算完毕后,后面便应该将生成的密钥存储到对应的连接上供业务通讯时使用。这里还有需要注意的地方:
TLS协议可以实现对报文机密性、完整性、认证的功能;机密性则是通过使用对称密码加密来实现的(此时会使用到6把密钥中的后4把);完整性和认证功能则是通过消息认证码来实现的(此时会用到6把密钥的前2把)。但是除此之外,还有一种更加高级的算法:AE(Authenticated Encryption)或者AEAD(Authenticated Encryption with Associated Data ),中文成为认证加密。认证加密是一种将消息认证码和对称加密算法相结合,可以同时满足机密性、完整性、认证三大功能的机制。关于认证加密相关知识在“密码学知识”中进行介绍,这里不再赘述。
Go中的TLS源码当然也支持这种比较新颖的方式(2000年以后开始出现认证加密机制)。
如果加密套件中存在AEAD(认证加密)算法,则只需要使用到6把密钥中的后4把,MAC密钥不再需要。如果不存在AEAD则采用独立加密算法和消息认证算法,此时6把密钥会全部参与后续加密通讯。
对于TLS服务端而言,ClientCipher套件用来解析使用;ServerCipher套件用来加密使用。分别存在TLS连接的两个半连接上(in半连接用来读取客户端加密信息,存储ClientCipher套件; out半连接用来加密服务端信息,存储ServerCipher套件)
密钥信息目前并不能立刻投入使用,而是在收到对方的ChangeCipherSpec报文后,再切换使用新协商的密钥。在go源码的实现中,将cipher, mac分别对应的nextCipher,nextMac结构中。
4. 读取Change Cipher Spec报文
client的Client Key Exchange载荷处理完毕后,协商双方已经完成了互相认证,预主密钥和会话密钥都已经计算完毕,之后便可以进行加密通讯。但是在加密通讯之前,TLS协议有着更加完备的处理机制:通过Change Cipher Spec载荷来通知协商双方同时完成密钥切换;通过Finished报文来检验协商出的密钥是否可以正常通讯。
CCS载荷的作用就是通知对方开始切换密钥,以后通讯均使用新协商的密钥进行加密通讯。CCS载荷内容很简单:只有1字节长度,值为1。
go源码中使用readChangeCipherSpec()函数处理CCS报文。在函数中经过一系列的校验之后,最后调用如下函数完成密钥的切换:
5. 读取Finished报文
Finished报文主要用来检验双方协商的密钥信息是否可以进行正常通讯。Finished载荷计算方式如下(RFC5246:"7.4.9. Finished"小节):
PRF(master_secret, finished_label, Hash(handshake_messages))
从这里可以看出:参与Finished载荷内容计算的包括:主密钥、Finish标签、所有的握手消息的摘要。之前每一个握手函数都会存储到到hs.finishedHash上,便是为了此刻使用。 关于finished_label,客户端为“client finished”;服务端为"server finished"。这三个数据协商双方都全部拥有,各自独立计算,然后比较是否相同;如果相同,则双方协商出的密钥均准确无误,可以用来加密通讯;如果校验失败,说明协商有误,应立即停止协商。
通过该载荷完成对双方协商出的密钥的校验。