准入中的TLS 温故&知新
TLS 握手流程直观
目前需要写文档给售前排查问题,顺便把TLS记录一下
转载自:https://halfrost.com/https_tls1-2_handshake/#toc-16
1、TLS安全与网络延迟
PEAP协议增加了TLS,类似与http上增加了TLS,
以TLS1.2为例:
新增了加了如下流程
如果以http访问www.qq.com为例:那就是如下流程了
对于6、7、8 如果浏览器本地有缓存,或者客户端有缓存,则可以省略掉。
上面这 10 步是最最完整的流程,一般有各种缓存不会经历上面每一步。如果有各种缓存,并且有 HSTS 策略,所以用户每次访问网页都必须要经历的流程如下:
流程 | 消耗时间 | 总计 |
---|---|---|
1. 访问 HTTPS 网页 TCP 握手 | 1-RTT | |
2. TLS 握手第一阶段 Say Hello | 1-RTT | |
3. TLS 握手第二阶段 加密 | 1-RTT | |
4. 第一个 HTTPS 请求 | 1-RTT | |
4-RTT |
目前 TLS 1.2 完整一次握手需要 2-RTT, TLS1.3 可以减少到0-RTT !!!
2、TLS 1.2 首次握手流程
- ClientHello 和 ServerHello 用于在 Client 和 Server 之间建立安全性增强的能力。ClientHello 和 ServerHello 建立了如下的属性: 协议版本,会话 ID,密码套件,压缩算法。此外,产生并交换两个随机数: ClientHello.random 和 ServerHello.random。
- 密钥交换中使用的最多 4 个消息: Server Certificate, ServerKeyExchange, Client Certificate 和 ClientKeyExchange。新的密钥交换方法可以通过这些方法产生:为这些消息指定一个格式, 并定义这些消息的用法以允许 Client 和 Server 就一个共享密钥达成一致。这个密钥必须很长;当前定义的密钥交换方法交换的密钥大于 46 字节。
- 在 Server hello 消息之后, Server 会在 Certificate 消息中发送它自己的证书,如果它即将被认证。如果 Server 发送了一个 CertificateRequest 消息,Client 必须发送 Certificate 消息。现在 ClientKeyExchange 消息需要发送, 这个消息的内容取决于 ClientHello 和 ServerHello 之间选择的公钥算法。如果 Client 发送了一个带签名能力的证书, 则需要发送以一个数字签名的 CertificateVerify 消息,以显式验证证书中私钥的所有权。
- ServerKeyExchange消息的内容取决于所使用的密钥交换算法。例如,临时参数交换:在使用非RSA密钥交换算法(例如Diffie-Hellman)时,在Diffie-Hellman密钥交换中,ServerKeyExchange消息将包含服务器生成的临时Diffie-Hellman参数。RSA密钥交换:当服务器的证书包含RSA公钥但不包含临时的Diffie-Hellman参数时,服务器会发送ServerKeyExchange消息,其中包含RSA公钥,用于密钥协商和加密通信。
- Client Key Exchange 紧跟在 Client Certificate 消息之后发送。如果不存在Client Certificate 消息的话,它必须是在 Client 收到 ServerHelloDone 后发送的第一个消息。这个消息的含义是,在这个消息中设置了预备主密钥,或者通过 RSA 加密后直接传输,或者通过传输 Diffie-Hellman 参数来允许双方协商出一致的预备主密钥
- change_cipher_spec 协议,是 TLS 记录层对应用数据是否进行加密的分界线。客户端或者服务端一旦收到对端发来的 CCS 协议,就表明接下来传输数据过程中可以对应用数据协议进行加密了
目前预备主密钥计算加密解密涉及到 RSA/ECDSA 加密预备主密钥 静态 DH 公钥算出预备主密钥 动态 DH 公钥算出预备主密钥。就不详细看了
目前radius peap 流程抓包没有涉及到client Certificate 交互
Client Server
ClientHello(1) ----(1)---->
ServerHello
Certificate
ServerKeyExchange
<-----(2)--- ServerHelloDone
ClientKeyExchange
[ChangeCipherSpec]
Finished ----(3)---->
[ChangeCipherSpec]
<----(4)--- Finished
Application Data <-------> Application Data
第一步:
-
gmt_unix_time:
依据发送者内部时钟以标准 UNIX 32 位格式表示的当前时间和日期(从1970年1月1日UTC午夜开始的秒数, 忽略闰秒)。基本 TLS 协议不要求时钟被正确设置;更高层或应用层协议可以定义额外的需求. 需要注意的是,出于历史原因,该字段使用格林尼治时间命名,而不是 UTC 时间。 -
random_bytes:
由一个安全的随机数生成器产生的 28 个字节数据。 -
client_version:
Client 愿意在本次会话中使用的 TLS 协议的版本. 这个应当是 Client 所能支持的最新版本(值最大),TLS 1.2 是 3.3,TLS 1.3 是 3.4。 -
random:
一个 Client 所产生的随机数结构 Random。随机数的结构体 Random 在上面展示出来了。客户端的随机数,这个值非常有用,生成预备主密钥的时候,在使用 PRF 算法计算导出主密钥和密钥块的时候,校验完整的消息都会用到,随机数主要是避免重放攻击。 -
session_id:
Client 希望在本次连接中所使用的会话 ID。如果没有 session_id 或 Client 想生成新的安全参数,则这个字段为空。这个字段主要用在会话恢复中。 - cipher_suites:
Client 所支持的密码套件列表,Client最倾向使用的排在最在最前面。如果 session_id 字段不空(意味着是一个会话恢复请求),这个向量必须至少包含那条会话中的 cipher_suite -
compression_methods:
这是 Client 所支持的压缩算法的列表,按照 Client所倾向的顺序排列。如果 session_id 字段不空(意味着是一个会话恢复请求),它必须包含那条会话中的 compression_method。这个向量中必须包含, 所有的实现也必须支持 CompressionMethod.null。因此,一个 Client 和 Server 将能就压缩算法协商打成一致。
第二步:
-
server_version:
这个字段将包含 Client 在 Client hello 消息中建议的较低版本和 Server 所能支持的最高版本。TLS 1.2 版本是 3.3,TLS 1.3 是 3.4 。 -
random:
这个结构由 Server 产生并且必须独立于 ClientHello.random 。这个随机数值和 Client 的随机数一样,这个值非常有用,生成预备主密钥的时候,在使用 PRF 算法计算导出主密钥和密钥块的时候,校验完整的消息都会用到,随机数主要是避免重放攻击。 -
session_id:
这是与本次连接相对应的会话的标识。如果 ClientHello.session_id 非空,Server 将在它的会话缓存中进行匹配查询。如果匹配项被找到,且 Server 愿意使用指定的会话状态建立新的连接,Server 会将与 Client 所提供的相同的值返回回去。这意味着恢复了一个会话并且规定双方必须在 Finished 消息之后继续进行通信。否则这个字段会包含一个不同的值以标识新会话。Server 会返回一个空的 session_id 以标识会话将不再被缓存从而不会被恢复。如果一个会话被恢复了,它必须使用原来所协商的密码套件。需要注意的是没有要求 Server 有义务恢复任何会话,即使它之前提供了一个 session_id。Client 必须准备好在任意一次握手中进行一次完整的协商,包括协商新的密码套件。 -
cipher_suite:
由 Server 在 ClientHello.cipher_suites 中所选择的单个密码套件。对于被恢复的会话, 这个字段的值来自于被恢复的会话状态。从安全性考虑,应该以服务器配置为准。 -
compression_method:
由 Server 在 ClientHello.compression_methods 所选择的单个压缩算法。对于被恢复的会话,这个字段的值来自于被恢复的会话状态。 -
extensions:
扩展的列表. 需要注意的是只有由 Client 给出的扩展才能出现在 Server 的列表中。
第三步:
- Client Key Exchange Messag
这个消息中设置了预备主密钥,或者通过 RSA 加密后直接传输,或者通过传输 Diffie-Hellman 参数来允许双方协商出一致的预备主密钥。
- Change Cipher Spec
在收到对端的 ChangeCipherSpec 之前,所有的 TLS 握手消息都是明文处理的,没有安全性和完整性的保护。一旦所有的加密参数都准备好,就会转换成可读可写状态,进入到了可读可写状态以后就会开始加密和完整性保护了。 ChangeCipherSpec 在官方 TLS 1.3 的规范中已经去掉了 - cipher_suite:
由 Server 在 ClientHello.cipher_suites 中所选择的单个密码套件。对于被恢复的会话, 这个字段的值来自于被恢复的会话状态。从安全性考虑,应该以服务器配置为准。
第四步:
- Change Cipher Spec
在收到对端的 ChangeCipherSpec 之前,所有的 TLS 握手消息都是明文处理的,没有安全性和完整性的保护。一旦所有的加密参数都准备好,就会转换成可读可写状态,进入到了可读可写状态以后就会开始加密和完整性保护了。 ChangeCipherSpec 在官方 TLS 1.3 的规范中已经去掉了 - encrypted handshake Message 也就是 Finished 校验
TLS 记录层协议结构
enum {
invalid(0),
change_cipher_spec(20),
alert(21),
handshake(22),
application_data(23),
heartbeat(24), /* RFC 6520 */
(255)
} ContentType;
TLS 握手协议结构
enum {
hello_request(0),
client_hello(1),
server_hello(2),
certificate(11),
server_key_exchange (12),
certificate_request(13),
server_hello_done(14),
certificate_verify(15),
client_key_exchange(16),
finished(20)
(255)
} HandshakeType;
struct {
HandshakeType msg_type;
uint24 length;
select (HandshakeType) {
case hello_request: HelloRequest;
case client_hello: ClientHello;
case server_hello: ServerHello;
case certificate: Certificate;
case server_key_exchange: ServerKeyExchange;
case certificate_request: CertificateRequest;
case server_hello_done: ServerHelloDone;
case certificate_verify: CertificateVerify;
case client_key_exchange: ClientKeyExchange;
case finished: Finished;
} body;
} Handshake;
TLS 应用数据格式
TLS 记录层会根据加密模式的不同在应用数据的末尾加上 MAC 校验数据
TLS 1.2 中的密钥
在 TLS 1.2 中,有 3 种密钥:预备主密钥、主密钥和会话密钥(密钥块),这几个密钥都是有联系的
struct {
uint32 gmt_unix_time;
opaque random_bytes[28];
} Random;
struct {
ProtocolVersion client_version;
opaque random[46];
} PreMasterSecret;
struct {
uint8 major;
uint8 minor;
} ProtocolVersion;
对于 RSA 握手协商算法来说,Client 会生成的一个 48 字节的预备主密钥,其中前 2 个字节是 ProtocolVersion,后 46 字节是随机数,用 Server 的公钥加密之后通过 Client Key Exchange 子消息发给 Server,Server 用私钥来解密。对于 (EC)DHE 来说,预备主密钥是双方通过椭圆曲线算法生成的,双方各自生成临时公私钥对,保留私钥,将公钥发给对方,然后就可以用自己的私钥以及对方的公钥通过椭圆曲线算法来生成预备主密钥,预备主密钥长度取决于 DH/ECDH 算法公钥。预备主密钥长度是 48 字节或者 X 字节。
主密钥是由预备主密钥、ClientHello random 和 ServerHello random 通过 PRF 函数生成的。主密钥长度是 48 字节。可以看出,只要我们知道预备主密钥或者主密钥便可以解密抓包数据,所以 TLS 1.2 中抓包解密调试只需要一个主密钥即可,SSLKEYLOG 就是将主密钥导出来,在 Wireshark 里面导入就可以解密相应的抓包数据。
会话密钥(密钥块)是由主密钥、SecurityParameters.server_random 和 SecurityParameters.client_random 数通过 PRF 函数来生成,会话密钥里面包含对称加密密钥、消息认证和 CBC 模式的初始化向量,对于非 CBC 模式的加密算法来说,就没有用到这个初始化向
Session ID 缓存和 Session Ticket 里面保存的也是主密钥,而不是会话密钥,这样每次会话复用的时候再用双方的随机数和主密钥导出会话密钥,从而实现每次加密通信的会话密钥不一样
TLS 1.2 中的 HMAC 和伪随机函数
TLS 记录层使用一个有密钥的信息验证码(MAC)来保护信息的完整性。密码算法族使用了一个被称为HMAC(在[HMAC]中描述)的 MAC 算法,它基于一个 hash 函数。如果必要的话其它密码算法族可以定义它们自己的 MAC 算法。
TLS 1.2 中的密钥计算
主密钥计算方法:
master_secret = PRF(pre_master_secret,ClientHello.random + ServerHello.random)[0..47];
会话密钥计算方法
key_block = PRF(SecurityParameters.master_secret,
SecurityParameters.server_random +
SecurityParameters.client_random);
TLS 1.2 Finished 校验
在 TLS 1.2 握手的最后,会发送 Finished 子消息,这条消息是加密的第一条消息,Finished 消息的接收者必须要验证这条消息的内容是否正确。验证的内容是通过 PRF 算法计算出来的。
Finished 子消息的存在的意义是什么呢?
在所有的握手协议中,所有的子消息都没有加密和完整性保护,消息很容易篡改,改掉以后如果不检验,就会出现不安全的攻击。为了避免握手期间存在消息被篡改的情况,所以 Client 和 Server 都需要校验一下对方的 Finished 子消息。
Finished 消息的结构:
struct {
opaque verify_data[verify_data_length];
} Finished;
verify_data =
PRF(master_secret, finished_label, Hash(handshake_messages))
[0..verify_data_length-1];
在计算 verify_data 的时候,PRF(secret, label, seed)
中 secret 是主密钥,label 是 finished_label,Client 是 "client finished",Server 是 "server finished",seed 是所有握手消息的 hash 值。对于 Client 来说,handshake_messages 内容包含所有发送的消息和接收的消息,但是不包括自己发送的 Finished 消息。对于 Server 来说,handshake_messages 内容包含从 ClientHello 消息开始截止到 Finished 消息之前的所有消息,也包括 Client 的 Finished 子消息。
handshake_messages 中只包含握手子消息,不包括 ChangeCipherSpec 子消息、 Alert 子消息、HelloRequest 消息。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
2022-12-29 netstat -st输出解析
2022-12-29 ip ss 网络相关命令