TLS1.2协议设计原理
前言
最近对TLS1.2协议处理流程进行了学习及实现,本篇文章对TLS1.2的理论知识和处理流程进行分析,TLS协议的实现建议直接看The Transport Layer Security (TLS) Protocol Version 1.2
为什么需要TLS协议
通常我们使用TCP协议或UDP协议进行网络通信。TCP协议提供一种面向连接的、可靠的字节流服务。但是TCP并不提供数据的加密,也不提供数据的合法性校验。
我们通常可以对数据进行加密、签名、摘要等操作来保证数据的安全。目前常见的加密方式有对称加密和非对称加密。使用对称加密,双方使用共享密钥。
但是对于部署在互联网上的服务,如果我们为每个客户端都使用相同的对称加密密钥,那么任何人都可以将数据解密,那么数据的隐私性将得不到保障。
如果我们使用非对称密钥加密,客户端使用服务端的公钥进行公钥加密,服务端在私钥不泄露的情况下,只有服务端可以使用私钥可以对数据进行解密,从而保障数据的隐私性,但是非对称加密比对称加密的成本高得多。
我们可以采用对称加密和非对称加密相结合的方式实现数据的隐私性的同时性能又不至于太差。
- 首先客户端和服务端通过一个称为密钥交换的流程进行密钥协商及交换密钥所系的信息。
- 通过公钥加密对称密钥保证对称密钥的安全传输。
- 服务端使用私钥解密出对称密钥。
- 最后双方使用协商好的对称密钥对数据进行加解密。
TLS协议就是实现了这一过程安全协议。TLS是在TCP之上,应用层之下实现的网络安全方案。在TCP/IP四层网络模型中属于应用层协议。TLS协议在两个通信应用程序之间提供数据保密性和数据完整性,另外还提供了连接身份可靠性方案。
UDP则使用DTLS协议实现安全传输,和TLS协议类似。
发展历史
TLS协议的前身SSL协议是网景公司设计的主要用于Web的安全传输协议,这种协议在Web上获得了广泛的应用。
- 1994年,网景公司设计了SSL协议的1.0版,因为存在严重的安全漏洞,未公开。
- 2.0版本在1995年2月发布,但因为存在数个严重的安全漏洞(比如使用MD5摘要、握手消息没有保护等),2011年RFC 6176 标准弃用了SSL 2.0。
- 3.0版本在1996年发布,是由网景工程师完全重新设计的,同时写入RFC,较新版本的SSL/TLS基于SSL 3.0。2015年,RFC 7568 标准弃用了SSL 3.0。
- 1999年,IETF将SSL标准化,并将其称为TLS(Transport Layer Security)。从技术上讲,TLS 1.0与SSL 3.0的差异非常微小。
- TLS 1.1在RFC 4346中定义,于2006年4月发表,主要修复了CBC模式的BEAST攻击等漏洞。
微软、Google、苹果、Mozilla四家浏览器业者将在2020年终止支持TLS 1.0及1.1版。
- TLS 1.2在RFC 5246中定义,于2008年8月发表,添加了增加AEAD加密算法,如支持GCM模式的AES。
- TLS 1.3在RFC 8446中定义,于2018年8月发表,砍掉了AEAD之外的加密方式。
协议设计目标
- 加密安全:TLS应用于双方之间建立安全连接,通过加密,签名,数据摘要保障信息安全。
- 互操作性:程序员在不清楚TLS协议的情况下,只要对端代码符合RFC标准的情况下都可以实现互操作。
- 可扩展性:在必要时可以通过扩展机制添加新的公钥和机密方法,避免创建新协议。
- 相对效率:加密需要占用大量CPU,尤其是公钥操作。TLS协议握手完成后,通过对称密钥加密数据。TLS还集成了会话缓存方案,减少需要从头建立连接的情况。
记录协议
TLS协议是一个分层协议,第一层为TLS记录层协议(Record Layer Protocol),该协议用于封装各种高级协议。目前封装了4种协议:握手协议(Handshake Protocol)、改变密码标准协议(Change Cipher Spec Protocol)、应用程序数据协议(Application Data Protocol)和警报协议(Alert Protocol)。
Change Cipher Spec Protocol
在TLS1.3被去除。
记录层包含协议类型、版本号、长度、以及封装的高层协议内容。记录层头部为固定5字节大小。
在TLS协议规定了,如接收到了未定义的协议协议类型,需要发送一个
unexpected_message
警报。
握手步骤
- 当客户端连接到支持TLS协议的服务端要求创建安全连接并列出了受支持的算法套件(包括加密算法、散列算法等),握手开始。
- 服务端从客户端的算法套件列表中指定自己支持的一个算法套件,并通知客户端,若没有则使用一个默认的算法套件。
- 服务端发回其数字证书,此证书通常包含服务端的名称、受信任的证书颁发机构(CA)和服务端的公钥。
- 客户端确认其颁发的证书的有效性。
- 为了生成会话密钥用于安全连接,客户端使用服务端的公钥加密随机生成的密钥,并将其发送到服务端,只有服务端才能使用自己的私钥解密。
- 利用随机数,双方生成用于加密和解密的对称密钥。这就是TLS协议的握手,握手完毕后的连接是安全的,直到连接(被)关闭。如果上述任何一个步骤失败,TLS握手过程就会失败,并且断开所有的连接。
握手协议
TLS 握手协议允许服务端和客户端相互进行身份验证,并在应用程序协议传输或接收其第一个字节数据之前协商协议版本、会话ID、压缩方法、密钥套件、以及加密密钥。
完整的TLS握手流程,流程如下
Client Server
ClientHello -------->
ServerHello
Certificate*
ServerKeyExchange*
CertificateRequest*
<-------- ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished -------->
[ChangeCipherSpec]
<-------- Finished
Application Data <-------> Application Data
- 表示可选步骤或与实际握手情况相关。比如重建已有连接,服务端无需执行Certificate,再比如使用RSA公钥加密时,无需ServerKeyExchange。
握手协议消息必须按上面流程的发送数据进行发送,否则需要以致命错误告知对方并关闭连接。
完整的握手流程有时候也被称为2-RTT流程,即完整的握手流程需要客户端和服务端交互2次才能完成握手。
交互应用请求到响应的交互时间被称为往返时间(Round-trip Time)
握手协议的结构如下,其中协议头的ContentType固定为22
,接下来是TLS版本号,TLS1.2为0303
,最后是用2字节表示长度。
握手协议类型包含以下:
- hello_request:0
- client_hello:1
- server_hello:2
- certificate:3
- server_key_exchange :12
- certificate_request:13
- server_hello_done:14
- certificate_verify:15
- client_key_exchange:16
- finished:20
Hello Message是具体的握手协议类型内容,不同协议内容有所不同。
Hello Request
Hello Request
消息用于客户端与服务端重新协商握手,该消息可能由服务端在任何时刻发送。Hello Request
消息非常简单,没有其他冗余信息。
当客户端收到了服务端的Hello Request
时可以有以下4种行为:
- 当客户端正在协商会话,可以忽略该消息。
- 若客户端未在协商会话但不希望重新协商时,可以忽略该消息。
- 若客户端未在协商会话但不希望重新协商时,可以发送
no_renegotiation
警报。 - 若客户端希望重新协商会话,则需要发送
ClientHello
重新进行TLS握手。
服务端发送完HelloRequest
消息,可以有以下几种行为:
- 服务端发送了
HelloRequest
消息,但未收到ClientHello
时,可以通过致命连接警报关闭连接。 - 服务端发送了
HelloRequest
消息,必须等待握手协商处理完成后才可以继续处理应用数据消息。
Finished和Certificate的握手消息验证不包括该消息的hash。
Client Hello
当客户端首次与服务端建立连接或需要重新协商加密握手会话时,需要将Client Hello
作为第一条消息发送给服务端。
Client Hello
消息包含了许多重要信息,包括客户端版本号、客户端随机数、会话ID、密钥套件、压缩方式、扩展字段等。
- 客户端版本号:客户端支持的最新TLS版本号,服务端会根据该协议号进行协议协商。
- 32位随机数:客户端生成的32位随机数。前4位是Unix时间戳,该时间戳为1970年1月1日0点以来的秒数。不过TLS并没有强制要求校验该时间戳,因此允许定义为其他值。后面28位为一个随机数。
通过前4字节填写时间方式,有效的避免了周期性的出现一样的随机数。使得”随机”更加”随机”。
在TLS握手时,客户端和服务端需要协商数据传输时的加密密钥。为了保证加密密钥的安全性。密钥需要通过客户端和服务端一起生成。客户端和服务端都提供一个32位的随机数,通过该随机数使用基于HMAC的PRF算法生成客户端和服务端的密钥。 - 会话ID:用于表示客户端和服务端之间的会话。实际的会话ID是由服务端定义的,因此即使是新的连接,服务端返回的会话ID可能也会和客户端不一致,由于会话ID是明文传输的,因此不能存放机密信息。
- 若会话ID是新的,则客户端和服务端需要建立完整的TLS握手连接流程。
- 若会话ID是较早连接的会话ID,则服务端可以选择无需执行完整的握手协议。
- 算法套件:客户端将支持的加密算法组合排列后发送给服务端,从而和服务端协商加密算法。服务端根据支持算法在ServerHello返回一个最合适的算法组合。
算法套件的格式为TLS_密钥交换算法_身份认证算法_WITH_对称加密算法_消息摘要算法,比如TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
,密钥交换算法是DHE
,身份认证算法是RSA
,对称加密算法是AES_256_CBC,消息摘要算法是SHA256,由于RSA又可以用于加密也可以用于身份认证,因此密钥交换算法使用RSA时,只写一个RSA,比如TLS_RSA_WITH_AES_256_CBC_SHA256
- 压缩方式:用于和服务端协商数据传输的压缩方式。由于TLS压缩存在安全漏洞,因此在TLS1.3中已经将TLS压缩功能去除,TLS1.2算法也建议不启用压缩功能。
- 扩展字段:可以在不改变底层协议的情况下,添加附加功能。客户端使用扩展请求其他功能,服务端若不提供这些功能,客户端可能会中止握手。对于扩展字段的详细定义可以看Transport Layer Security (TLS) Extensions
客户端发送完
ClientHello
消息后,将等待ServerHello
消息。 服务端返回的任何握手消息(HelloRequest
除外)将被视为致命错误。
Server Hello
当服务端接收到ClientHello
,则开始TLS握手流程, 服务端需要根据客户端提供的加密套件,协商一个合适的算法簇,其中包括对称加密算法、身份验证算法、非对称加密算法以及消息摘要算法。若服务端不能找到一个合适的算法簇匹配项,则会响应握手失败的预警消息。
-
版本号:服务端根据客户端发送的版本号返回一个服务端支持的最高版本号。若客户端不支持服务端选择的版本号,则客户端必须发送
protocol_version
警报消息并关闭连接。若服务端接收到的版本号小于当前支持的最高版本,且服务端希望与旧客户端协商,则返回不大于客户端版本的服务端最高版本。
若服务端仅支持大于client_version的版本,则必须发送protocol_version
警报消息并关闭连接。
若服务端收到的版本号大于服务端支持的最高版本的版本,则必须返回服务端所支持的最高版本。 -
32位随机数:服务端生成的32位随机数,生成方式和客户端一样。服务端生成随机数的可以有效的防范中间人攻击,主要是通过防止重新握手后的重放攻击。
-
会话ID:用于表示客户端和服务端之间的会话。若客户端提供了会话ID,则可以校验是否与历史会话匹配。
-
若不匹配,则服务端可以选择直接使用客户端的会话ID或根据自定义规则生成一个新的会话ID,客户端需要保存服务端返回的会话ID当作本次会话的ID。
-
若匹配,则可以直接执行1-RTT握手流程,返回ServerHello后直接返回
ChangeCipherSpec
和Finished
消息。Client Server ClientHello --------> ServerHello [ChangeCipherSpec] <-------- Finished [ChangeCipherSpec] Finished --------> Application Data <-------> Application Data
在Finished消息中和完整握手一样都需要校验VerifyData。
-
-
算法套件:服务端根据客户端提供的算法套件列表和自己当前支持算法进行匹配,选择一个最合适的算法组合,若没有匹配项,则使用默认的
TLS_RSA_WITH_AES_128_CBC_SHA
。TLS1.2协议要求客户端和服务端都必须实现密码套件
TLS_RSA_WITH_AES_128_CBC_SHA
-
压缩方式:用于和服务端协商数据传输的压缩方式。由于TLS压缩存在安全漏洞,因此在TLS1.3中已经将TLS压缩功能去除,TLS1.2算法也建议不启用压缩功能。
-
扩展字段:服务端需要支持接收具有扩展和没有扩展的ClientHello。服务端响应的扩展类型必须是
ClientHello
出现过才行,否则客户端必须响应unsupported_extension
严重警告并中断握手。
RFC 7568要求客户端和服务端握手时不能发送
{3,0}
版本,任何收到带有协议Hello消息的一方版本设置为{3,0}
必须响应protocol_version
警报消息并关闭连接。
通过ClientHello
和ServerHello
,客户端和服务端就协商好算法套件和用于生成密钥的随机数。
Certificate
假设客户端和服务端使用默认的TLS_RSA_WITH_AES_128_CBC_SHA
算法,在ServerHello
完成后,服务端必须将本地的RSA证书传给客户端,以便客户端和服务端之间可以进行非对称加密保证对称加密密钥的安全性。
RSA的证书有2个作用:
- 客户端可以对服务端的证书进行合法性进行校验。
- 对
Client Key Exchange
生成的pre-master key进行公钥加密,保证只有服务端可以解密,确保对称加密密钥的安全性。
发送给客户端的是一系列证书,服务端的证书必须排列在第一位,排在后面的证书可以认证前面的证书。
当客户端收到了服务端的ServerHello
时,若客户端也有证书需要服务端验证,则通过该握手请求将客户端的证书发送给服务端,若客户端没有证书,则无需发送证书请求到服务端。
证书必须为X.509v3格式。
Server Key Exchange
使用RSA公钥加密,必须要保证服务端私钥的安全。若私钥泄漏,则使用公钥加密的对称密钥就不再安全。同时RSA是基于大数因式分解。密钥位数必须足够大才能避免密钥被暴力破解。
1999年,RSA-155 (512 bits) 被成功分解。
2009年12月12日,RSA-768 (768 bits)也被成功分解。
在2013年的棱镜门事件中,某个CA机构迫于美国政府压力向其提交了CA的私钥,这就是十分危险的。
相比之下,使用DH算法通过双方在不共享密钥的情况下双方就可以协商出共享密钥,避免了密钥的直接传输。DH算法是基于离散对数,计算相对较慢。而基于椭圆曲线密码(ECC)的DH算法计算速度更快,而且用更小的Key就能达到RSA加密的安全级别。ECC密钥长度为224~225位几乎和RSA2048位具有相同的强度。
ECDH:基于ECC的DH算法。
另外在DH算法下引入动态随机数,可以避免密钥直接传输。同时即使密钥泄漏,也无法解密其他消息,因为双方生成的动态随机数无法得知。
在密码学中该特性被称为前向保密
DHE: 通过引入动态随机数,具有前向保密的DH算法。
ECDHE:通过引入动态随机数,具有前保密的ECDH算法。
Certificate Request
当需要TLS双向认证的时候,若服务端需要验证客户端的证书,则向客户端发送Certificate Request
请求获取客户端指定类型的证书。
- 服务端会指定客户端的证书类型。
- 客户端会确定是否有合适的证书。
Server Hello Done
当服务端处理Hello请求结束时,发送Server Hello Done
消息,然后等待接收客户端握手消息。客户端收到服务端该消息,有必要时需要对服务端的证书进行有效性校验。
Client Certificate
当客户端收到了服务端的CertificateRequest
请求时,需要发送Client Certificate
消息,若客户端无法提供证书,则仍要发送此消息,消息内容可以不包含证书。
Client Key Exchange
客户端接收到ServerHelloDone消息后,计算密钥,通过发送Client Key Exchange
消息给服务端。客户端和服务端通过Key Exchange
消息交换密钥,使得双方的主密钥协商达成一致。
以RSA的密钥协商为例。在ClientHello
和ServerHello
分别在客户端和服务端创建了一个32位的随机数。客户端接收到Server Hello Done
消息时,生成最后一个48位的预主密钥。通过服务端提供的证书进行公钥加密,以保证只有服务端的私钥才能解密。
其中预主密钥的前2位要求使用
Client Hello
传输的TLS版本号(存在一些TLS客户端传递的时协商后的TLS版本号,对该版本号检查时可能会造成握手失败)。
需要注意的是,若RSA证书的填空格式不正确,则可能会存在一个漏洞导致客户端发送的PreMasterSecret被中间人解密造成数据加密的对账密钥泄漏。可以看下Attacking RSA-based Sessions in SSL/TLS
Certificate Verify
若服务端要求客户端发送证书,且客户端发送了非0长度的证书,此时客户端想要证明自己拥有该证书,则需要使用客户端私钥签名一段数据发送给服务端继续验证。该数据为客户端收发的所有握手数据的hash值(不包括本次消息)。
Finished
当发送完Change Cipher Spec
消息后必须立即发送该消息。当该消息用于验证密钥交换和身份验证过程是否成功。
Finished
消息是第一个使用协商的算法簇进行加密和防篡改保护的消息。一旦双方都通过了该消息验证,就完成了TLS握手。
VerifyData为客户端收发的所有握手数据的hash值(不包括本次消息)。与Certificate Verify
的hash值可能会不一样。如果发送过Certificate Verify
消息,服务端的握手消息会包含Certificate Verify
握手的数据。
需要注意的是,握手数据不包括协议头的握手协议明文数据(服务端返回
Finished
的验证握手数据是包含接收到客户端的Finished
的明文hash值)。
Finished
消息数据加密和Appilication Data
一致,具体数据加密在Application Data
段进行说明。
改变密码标准协议
改变密码标准协议是为了在密码策略中发出信号转换信号。 该协议由一条消息组成,该消息在当前(不是挂起的)连接状态下进行加密和压缩。 消息由值 1 的单个字节组成。
在接收到该协议后,所有接收到的数据都需要解密。
警报协议
警报消息传达消息的严重性(警告或致命)和警报的说明。具有致命级别的警报消息会导致立即终止连接。
若在改变密码标准协议前接收到警报消息,是明文传输的,无需解密。
与其他消息一样,警报消息按当前连接状态指定进行加密和压缩。在接收到改变密码标准协议后接收到警报协议,则需要进行解密。解密后即为警报协议明文格式。
加密的Alert消息和加密数据一样,都需要递增加密序号,在数据解密时,递增解密序号。
应用程序数据协议
当客户端和服务端Finished
发送完毕并验证通过后,握手就结束了。后续所有数据都会使用握手协商的对称密钥进行数据加密。
TLS协议实现了数据加密和MAC计算。一般来说有3种加密模式,分别为:
- Mac-then-Encrypt:在明文上计算MAC,将其附加到数据,然后加密明和+MAC的完整数据。
- 加密和MAC:在明文上计算MAC,加密明文,然后将MAC附加到密文的末尾
- Encrypt-then-Mac:加密明文,然后在密文上计算MAC,并将其附加到密文。
TLS协议使用的是Mac-then-Encrypt
。首先将加密的序号、ContentType、数据长度、数据进计算HMAC-SHA256摘要。然后将摘要拼接到数据后,通过PKCS7格式对摘要+MAC数据进行填充对其和加密块大小一致。最后摘要+MAC+对其填充块
进行加密。
需要注意的是应用程序数据消息有最大长度限制2^14 + 2048
,当超过长度后,数据需要分段传输。每一段都当作单独的数据段进行单独MAC地址并加密。
结语
TLS1.2版本是目前最常用的TLS协议,TLS1.3版本于2018年发表,目前并没有广泛使用。
使用TLS1.2需要注意以下几点:
- 若使用RSA非对称加密,则需要尽可能使用2048位长度的密钥。
- 尽可能可以使用具有前向安全性的加密算法,如ECDHE算法进行非对称加密。
- 使用AEAD认证加密(GCM)代替CBC块加密。
参考文献
- The Transport Layer Security (TLS) Protocol Version 1.2
- TLS发展历史
- 前向安全性
- TLS/SSL 协议详解 (30) SSL中的RSA、DHE、ECDHE、ECDH流程与区别
- TLS Extensions
- Session会话恢复:两种简短的握手总结SessionID&SessionTicket
- Why using the premaster secret directly would be vulnerable to replay attack?
- Why does the SSL/TLS handshake have a client and server random?
- Transport Layer Security (TLS) Extensions
- 什么是AEAD加密
- Padding Oracle Attacks
- Lucky13攻击
- Padding Oracle Attack
- ssl perfect forward secrecy
- Transport Layer Security (TLS) Session Resumption without Server-Side State
- Elliptic Curve Cryptography (ECC) Cipher Suites for Transport Layer Security (TLS)
- DTLS协议中client/server的认证过程和密钥协商过程