TLS 协议分析 (四) handshake 协议概览
5. handshake 协议
handshake protocol 重要而繁琐。
TLS 1.3 对握手做了大修改,下面先讲 TLS 1.2,讲完再介绍一下分析 TLS 1.3.
5.1.handshake 的总体流程
handshake protocol 用于产生给 record protocol 使用的 SecurityParameters。在 handshake 中:
客户端和服务器端协商 TLS 协议版本号和一个 CipherSuite,
认证对端的身份(可选,一般如 https 是客户端认证服务器端的身份),
并且使用密钥协商算法生成共享的 master secret。
步骤如下:
交换 Hello 消息,协商出算法,交换 random 值,检查 session resumption.
交换必要的密码学参数,来允许 client 和 server 协商出 premaster secret。
交换证书和密码学参数,让 client 和 server 做认证,证明自己的身份。
从 premaster secret 和交换的 random 值 ,生成出 master secret。
把 SecerityParameters 提供被 record 层。
允许 client 和 server 确认对端得出了相同的 SecurityParameters,并且握手过程的数据没有被攻击者篡改。
Handshake 的结果是在双方建立相同的 Session,Session 包含下列字段:
session identifiersession id,用来唯一标识一个 session,在 session 恢复的时候,也要用到
peer certificate 对端的 X509v3 格式证书. 如果不需要认证对端的身份,就为空。
compression method 压缩算法,一般被禁用
cipher specCipherSuite,如上文介绍,包含: 用于生成 key 的 pseudorandom function (PRF) , 块加密算法例如 AES, MAC 算法 (例如 HMAC-SHA256). 还包括一个 mac_length 字段,在后文的 we 握手协议介绍
master secret48 字节的,client 和 server 共享密钥。
is resumable 一个标志位,用来标识当前 session 是否能被恢复。
以上字段,随后被用于生成 record 层的 SecurityParameters,多个连接可以通过握手协议的 session 恢复功能来复用同一个 session。
握手协议使用 非对称加密/密钥协商/数字签名 3 类算法,因此要求读者对这 3 类算法概念清晰,能准确区分。在此澄清一下,:非对称的算法分为 3 类:,
非对称加密,有:RSAES-PKCS1-v1_5,RSAES-OAEP ,Rabin-Williams-OAEP, Rabin-Williams-PKCS1-v1_5 等
非对称密钥协商,有:DH,DHE,ECDH,ECDHE 等
非对称数字签名:RSASSA-PKCS1-v1_5,RSASSA-PSS,ECDSA,DSA,ED25519 等
另外,非对称加密算法,可以当作密钥协商算法来用,所以 RSAES-PKCS1-v1_5,RSAES-OAEP 也可以当作密钥协商算法来用。
插播一段 RSA:
RSA 的实际工程应用,要遵循 PKCS#1 标准,见 https://www.ietf.org/rfc/rfc3447
其中的 RSAES-PKCS1-v1_5 和 RSASSA-PKCS1-v1_5 是使用 RSA 算法的两种不同 scheme(体制)。RSAES 表示 RSA Encryption schemes,即非对称加密,RSAES 有:RSAES-OAEP,RSAES-PKCS1-v1_5 两种,其中 RSAES-OAEP 更新更安全
RSASSA 表示 Signature schemes with appendix,即 appendix 模式(appendix 和 recovery 的区别请参看密码学教材)的非对称数字签名算法。RSASSA 有: RSASSA-PSS, RSASSA-PKCS1-v1_5 两种, 其中 RSASSA-PSS 更新更安全
RSA 还有一个缺陷,就是很容易被时间侧通道攻击,所以现在的 RSA 实现都要加 blinding ,后文有介绍。
可以看到,RSA 是一种很特殊的算法,既可以当非对称加密算法使用,又可以当非对称数字签名使用。这一点很有迷惑性,其实很多用 RSA 的人都分不清自己用的是 RSA 的哪种模式。
相比之下,ECC(椭圆曲线)这一块的算法就很清晰,ECDSA 只能用作数字签名,ECDH 只能用作密钥交换。
分清楚 RSAES-PKCS1-v1_5 和 RSASSA-PKCS1-v1_5 有什么用涅?
PKCS#1 规范解释:
A generally good cryptographic practice is to employ a given RSAkey pair in only one scheme. This avoids the risk thatvulnerability in one scheme may compromise the security of theother, and may be essential to maintain provable security.
FIPS PUB 186-3 美国标准规定:
An RSA key pair used for digital signatures shall only be used for onedigital signature scheme (e.g., ANS X9.31, RSASSA-PKCS1 v1.5 orRSASSA-PSS; see Sections 5.4 and 5.5). In addition, an RSA digitalsignature key pair shall not be used for other purposes (e.g., keyestablishment).
一对密钥只做一个用途,要么用作非对称加解密,要么用作签名验证,别混着用!一对密钥只做一个用途,要么用作非对称加解密,要么用作签名验证,别混着用!一对密钥只做一个用途,要么用作非对称加解密,要么用作签名验证,别混着用!
这个要求,决定了一个协议的 PFS(前向安全性),在斯诺登曝光 NSA 的“今日捕获,明日破解”政策后,越发重要。
https://news.ycombinator.com/item?id=5942534
http://news.netcraft.com/archives/2013/06/25/ssl-intercepted-today-decrypted-tomorrow.html
https://lwn.net/Articles/572926/
https://www.eff.org/deeplinks/2014/04/why-web-needs-perfect-forward-secrecy
http://www.wired.com/2013/10/lavabit_unsealed
PFS 反映到密钥协商过程中,就是:
不要使用 RSA 做密钥协商,一定只用 RSA 做数字签名。
不要把 ECDH 的公钥固定内置在客户端做密钥协商
后文可以看到这一原则在 TLS 1.3, QUIC,Apple 的 iMessage 等协议中一再贯彻。
非对称 RSA/ECC 这个话题比较大了,后面有空再写文章吧,读者可以先看一下参考资料,里面有清晰的介绍。
插播结束,继续 TLS。
由于设计的时候,就要考虑兼容性,而且实际历史悠久,所以 TLS 协议 90 年代曾经使用的一些算法,现在已经被破解了,例如有的被发现漏洞(rc4),有的密钥长度过短(例如曾经美帝有出口限制,限制 RSA 在 512 比特以下,对称加密密钥限制 40 比特以下,后来 2005 年限制被取消),但是考虑到兼容,现在的 TLS 实现中,还是包含了这种已经被破解的老算法的代码。这样,如果攻击者可以干扰握手过程,诱使 client 和 server 使用这种已经被破解的算法,就会威胁 TLS 协议的安全,这被称为“降级攻击”。
为了在握手协议解决降级攻击的问题,TLS 协议规定:client 发送 ClientHello 消息,server 必须回复 ServerHello 消息,否则就是 fatal error,当成连接失败处理。ClientHello 和 ServerHello 消息用于建立 client 和 server 之间的安全增强能力,ClientHello 和 ServerHello 消息建立如下属性:
Protocol Version
Session ID
Cipher Suite
Compression Method.
另外,产生并交换两个 random 值 ClientHello.random 和 ServerHello.random
密钥协商使用四条: server 的 Certificate,ServerKeyExchange,client 的 Certificate,ClientKeyExchange 。TLS 规定以后如果要新增密钥协商方法,可以订制这 4 条消息的数据格式,并且指定这 4 条消息的使用方法。密钥协商得出的共享密钥必须足够长,当前定义的密钥协商算法生成的密钥长度必须大于 46 字节。
在 hello 消息之后,server 会把自己的证书在一条 Certificate 消息里面发给客户端(如果需要做服务器端认证的话,例如 https)。 并且,如果需要的话,server 会发送一条 ServerKeyExchange 消息,(例如如果服务器的证书只用做签名,不用做密钥交换,或者服务器没有证书)。client 对 server 的认证完成后,server 可以要求 client 发送 client 的证书,如果这是协商出来的 CipherSuite 允许的。下一步,server 会发送 ServerHelloDone 消息,表示握手的 hello 消息部分已经结束。然后 server 会等待一个 client 的响应。如果 server 已经发过了 CertificateRequest 消息,client 必须发送 Certificate 消息。然后发送 ClientKeyExchange 消息,并且这条消息的内容取决于 ClientHello 和 ServerHello 消息协商的算法。如果 client 发送了有签名能力的证书,就显式发送一个经过数字签名的 CertificateVerify 消息,来证明自己拥有证书私钥。
然后,client 发送一个 ChangeCipherSpec 消息,并且 client 拷贝待定的 Cipher Spec 到当前的 Cipher Spec。然后 client 立即用新算法+新 key+新密钥 发送 Finished 消息。收到后,server 发送自己的 ChangeCipherSpec 消息,作为响应,并且拷贝待定的 Cipher Spec 到当前的 Cipher Spec。此时,握手就完成了,client 和 server 可以开始交换应用层数据(如下图所示)。应用层数据不得在握手完成前发送。
引用一个来自网络的图片:
注:为了帮助解决管道阻塞的问题,ChangeCipherSpec 是一个独立的 TLS protocol content type,并不是一个握手消息。
TLS 的完整握手过程,要进行 RSA/ECDH/ECDSA 等非对称计算,非对称计算是很慢的。关于非对称的性能:例如在 2015 年的服务器 cpu: Intel(R) Xeon(R) CPU E3-1230 V2 @ 3.30GHz 上,使用如下命令测试:
结果如下表:
注:非对称的单位是 次/秒,这是由于非对称一般只用于处理一个 block,对称的单位是 K/秒,因为对称一般用于处理大量数据流,所以单位和流量一样。可以给非对称的 次/秒 乘以 block size ,就可以和对称做比较了。例如 rsa-2048,723.7*2048/8/1024=185.2672 K/秒 ,故 RSA-2048 私钥运算性能 是 aes-128-cbc 的 1.5/1000。是 aesni 的 2.6/10000。
如上,性能数据惨不忍睹, 简直不能忍!!!
有鉴于此,TLS 从设计之初,就采用了万能手段—加 cache,有 2 种 cache 手段:session id,和 session ticket。把握手的结果直接 cache 起来,绕过握手运算。
当 client 和 server 决定恢复一个之前的 session,或复用一个已有的 session 时(可以不用协商一个新的 SecurityParameters),消息流程如下:
客户端使用要被恢复的 session,发送一个 ClientHello,把 Session ID 包含在其中。server 在自己的 session cache 中,查找客户端发来的 Session ID,如果找到,sever 把找到的 session 状态恢复到当前连接,然后发送一个 ServerHello,在 ServerHello 中把 Session ID 带回去。然后,client 和 server 都必须 ChangeCipherSpec 消息,并紧跟着发送 Finished 消息。这几步完成后,client 和 server 开始交换应用层数据(如下图所示)。如果 server 在 session cache 中没有找到 Session ID,那 server 就生成一个新的 session ID 在 ServerHello 里给客户端,并且 client 和 server 进行完整的握手。
流程图如下:
5.2. handshake 协议外层结构
从消息格式来看,TLS Handshake Protocol 在 TLS Record Protocol 的上层. 这个协议用于协商一个 session 的安全参数。 Handshake 消息(例如 ClientHello,ServerHello 等) 被包装进 TLSPlaintext 结构里面,传入 TLS record 层,根据当前 session 状态做处理,然后传输。
如下:
TLS 协议规定,handshake 协议的消息必须按照规定的顺序发,收到不按顺序来的消息,当成 fatal error 处理。也就是说,TLS 协议可以当成状态机来建模编码。
下面按照消息发送必须遵循的顺序,逐个解释每一条握手消息。
handshake 协议的外层字段,见这个抓包:
5.3. handshake — ClientHello,ServerHello,HelloRequest
Hello 消息有 3 个:ClientHello, ServerHello,HellloRequest 逐个说明:
5.3.1 Client Hello
当客户端第一次连接到服务器时,第一条 message 必须发送 ClientHello。另外,rfc 里规定,如果客户端和服务器支持重协商,在客户端收到服务器发来的 HelloRequest 后,也可以回一条 ClientHello,在一条已经建立的连接上开始重协商。(重协商是个很少用到的特性。)
消息结构:
Random 其中:gmt_unix_time 是 unix epoch 时间戳。random_bytes 是 28 字节的,用密码学安全随机数生成器 生成出来的随机数。
密码学安全的随机数生成,这是个很大的话题,也是一个大陷阱,目前最好的做法就是用 /dev/urandom,或者 openssl 库的 RAND_bytes()
历史上,恰好就在 SSL 的 random_bytes 这个字段,NetScape 浏览器早期版本被爆出过随机数生成器漏洞。被爆菊的随机数生成器使用 pid + 时间戳 来初始化一个 seed,并用 MD5(seed)得出结果。见 http://www.cs.berkeley.edu/~daw/papers/ddj-netscape.html,建议读者检查一下自己的随机数生成器。
client_version: 客户端支持的最高版本号。
random: 客户端生成的 random。
ClientHello.session_id 唯一标识一个 session,用来做 session cache。如果为空,表示不做复用,要求服务器生成新的 session。session_id 的来源有:
之前的协商好的连接的 session_id
当前连接的 session_id
当前也在使用中的另一条连接的 session_id
其中第三种允许不做重新握手,就同时建立多条独立的安全连接。这些独立的连接可能顺序创建,也可以同时创建。一个 SessionID 当握手协商的 Finished 消息完成后,就合法可用了。存活直到太旧被移除,或者 session 关联的某个连接发生 fatal error。SessionID 的内容由服务器端生成。
注:由于 SessionID 的传输是不加密,不做 MAC 保护的,服务器不允许把私密信息发在里面,不能允许伪造的 SessionID 在服务器造成安全问题。(握手过程中的数据,整体是受 Finished 消息的保护的)
ClientHello.cipher_suites 字段,包含了客户端支持的 CipherSuite 的列表,按照客户端希望的优先级排序,每个 CipherSuite 有 2 个字节,每个 CipherSuite 由:一个密钥交换算法,一个大量数据加密算法(需要制定 key length 参数),一个 MAC 算法,一个 PRF 构成。服务器会从客户端发过来的列表中选择一个;如果没有可以接受的选择,就返回一个 handshake failure 的 alert,并关闭连接。如果列表包含服务器不认识,不支持,或者禁用的 CipherSuite,服务器必须忽略。如果 SessionID 不为空,则 cipher_suites 里面起码要包含客户端 cache 的 session 里面的那个 CipherSuite
compression_methods,类似地,ClientHello 里面包含压缩算法的列表,按照客户端优先级排序。当然,如前介绍,服务器一般禁用 TLS 的压缩。
compression_methods 后面可以跟一组扩展(extensions), extensions 都是可选的,比较有用的扩展如: SNI, session ticket,ALPN,OCSP 等,后文介绍。
客户端发送了 ClientHello 后,服务器端必须回复 ServerHello 消息,回复其他消息都会导致 fatal error 关闭连接。
5.3.2 Server Hello
当收到客户端发来的 ClientHello 后,正常处理完后,服务器必须回复 ServerHello。
消息结构:
server_version: 服务器选择 ClientHello.client_version 和 服务器支持的版本号 中的最小的。
random: 服务器生成的 random,必须确保和客户端生成的 random 没有关联。
session_id: 服务器为本连接分配的 SessionID。如果 ClientHello.session_id 不为空,服务器会在自己的本地做查找。
如果找到了匹配,并且服务器决定复用找到的 session 建立连接,服务器应该把 ClientHello.session_id 同样的 session id 填入 ServerHello.session_id,这表示恢复了一个 session,并且双方会立即发送 Finished 消息。
否则,回复一个和 ClientHello.random_id 不同的 Serverhello.session_id,来标识新 session。服务器可以回复一个空的 session_id,来告诉客户端这个 session 不要 cache,不能恢复。如果一个 session 被恢复了,那必须恢复成之前协商的 session 里面的 CipherSuite。要注意的是,并不要求服务器一定要恢复 session, 服务器可以不做恢复。
在实践中,session cache 在服务器端要求 key-value 形式的存储,如果 tls 服务器不止一台的话,就有一个存储怎么共享的问题,要么存储同步到所有 TLS 服务器的内存里,要么专门搞服务来支持存储,并使用 rpc 访问,无论如何,都是很麻烦的事情,相比之下,后文要介绍的 session ticket 就简单多了,所以一般优先使用 session ticket。
cipher_suite: 服务器选定的一个 CipherSuite。如果是恢复的 session,那就是 session 里的 CipherSuite。
compression_method: 跟上面类似。
extensions: 扩展列表。要注意的是,ServerHello.extensions 必须是 ClientHello.extensions 的子集。
5.3.3 Hello Extensions
The extension 的格式是:
其中:
“extension_type” 标识是哪一个扩展类型。
“extension_data” 一坨二进制的 buffer,扩展的数据体,各个扩展自己做解析。
extension_type 只能出现一次,ExtensionType 之间不指定顺序。
extensions 可能在新连接创建时被发送,也可能在要求 session 恢复的时候被发送。所以各个 extension 都需要规定自己再完整握手和 session 恢复情况下的行为。这些情况比较琐碎而微妙,具体案例要具体分析。
5.3.4 Hello Request
服务器任何时候都可以发送 HelloRequest 消息。
HelloRequest 的意思是,客户端应该开始协商过程。客户端应该在方便的时候发送 ClientHello。服务器不应该在客户端刚创建好连接后,就发送 HelloRequest,此时应该让客户端发送 ClientHello。
客户端收到这个消息后,可以直接忽略这条消息。服务器发现客户端没有响应 HelloRequest 后,可以发送 fatal error alert。
消息结构:
HelloRequest 不包含在握手消息的 hash 计算范围内。
本文转自微信后台团队,如有侵犯,请联系我们立即删除
OpenIMgithub 开源地址:
https://github.com/OpenIMSDK/Open-IM-Server
OpenIM 官网 : https://www.rentsoft.cn
**OpenIM 官方论坛: ** https://forum.rentsoft.cn/
更多技术文章:
开源 OpenIM:高性能、可伸缩、易扩展的即时通讯架构https://forum.rentsoft.cn/thread/3
【OpenIM 原创】简单轻松入门 一文讲解 WebRTC 实现 1 对 1 音视频通信原理https://forum.rentsoft.cn/thread/4
【OpenIM 原创】开源 OpenIM:轻量、高效、实时、可靠、低成本的消息模型https://forum.rentsoft.cn/thread/1
评论