Files
Knowledge/sunyard/7 信安/openssl中如何获得会话密钥?.md
T

369 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
published:
created: 2025-10-18
tags:
- clippings
---
## 背景
[OpenSSL](https://zhida.zhihu.com/search?content_id=751981217&content_type=Answer&match_order=1&q=OpenSSL&zhida_source=entity) 有2大核心:一个是实现 TLS 协议,一个是加密解密库。
接下来,不得不开始讲解极为复杂的加密解密了。
这里有许多数学知识,本人也不懂,只是作为调包侠,知道输入输出而已。
今天,先讲一个大体框架。
---
## 引子
现在主流浏览器和大多数 Web 服务器,都采用 [TLS1.3](https://zhida.zhihu.com/search?content_id=751981217&content_type=Answer&match_order=1&q=TLS1.3&zhida_source=entity) 。
以下是一个典型的通讯过程(TLS1.3):
![](https://pic3.zhimg.com/v2-837ffc4578f86fee62efb0065d420fa8_1440w.jpg)
第1个包:客户向服务器发送 [Client Hello](https://zhida.zhihu.com/search?content_id=751981217&content_type=Answer&match_order=1&q=Client+Hello&zhida_source=entity)。向服务器打招呼,我要和你建立 TLS 连接。
第2个包:服务器回应 [Server Hello](https://zhida.zhihu.com/search?content_id=751981217&content_type=Answer&match_order=1&q=Server+Hello&zhida_source=entity)。服务器回应:好吧,现在开始建立TLS 连接。
第2个包,一共有6个记录层 [Record Layer](https://zhida.zhihu.com/search?content_id=751981217&content_type=Answer&match_order=1&q=Record+Layer&zhida_source=entity)。
前2个记录层,明文传输:分别是握手协议;Change Cipher(开始加密的信号)
后4个记录层,都是加密的,需要客户端解密。
以下是客户端和服务器在前2个包中沟通的内容。
```
packet 1: length is 1759
IP header length is 20
TCP header length is 20
Source Port: 11128
Dest Port: 443
TLS: client to server
TLS part: len is 1719
record layer: handshake
handleshake layer: ClientHello
Random: 3df8b4e7ae0d778d09a45721d5dfbc064e6dc6b368a1e7c1767ae849eb996824
Session ID: 56c1d0ef0363be4943c0fa9014229a150d18a8ee7e00e271cb18a37ec449cb99
total 16 cipher suites
Extensions length: 1605
extension_type=UNKNOWN(27242), length=0
extension_type=server_name(0), length=14
server_name = localhost
extension_type=supported_versions(43), length=7
extension_type=session_ticket(35), length=0
extension_type=UNKNOWN(65037), length=186
extension_type=key_share(51), length=1263
group: UNKNOWN(27242), len = 1
group: X25519MLKEM768(4588), len = 1216
group: ecdh_x25519(29), len = 32
extension_type=signed_certificate_timestamps(18), length=0
extension_type=supported_groups(10), length=12
extension_type=application_layer_protocol_negotiation(16), length=14
extension_type=UNKNOWN(17613), length=5
extension_type=extended_master_secret(23), length=0
extension_type=psk_key_exchange_modes(45), length=2
extension_type=status_request(5), length=5
extension_type=signature_algorithms(13), length=18
extension_type=ec_point_formats(11), length=2
extension_type=compress_certificate(27), length=3
extension_type=renegotiate(65281), length=1
extension_type=UNKNOWN(39578), length=1
========================
packet 2: length is 1474
IP header length is 20
TCP header length is 20
Source Port: 443
Dest Port: 11128
TLS: server to client
TLS part: len is 1434
record layer: handshake
handleshake layer: ServerHello
Random: a80ae047daa2f5d800b479277f5543408c7127061af40f7cbe8296218632aa81
Session ID: 56c1d0ef0363be4943c0fa9014229a150d18a8ee7e00e271cb18a37ec449cb99
Cipher Suite: TLS_AES_256_GCM_SHA384(0x1302)
Extensions length: 46
extension_type=supported_versions(43), length=2
extension_type=key_share(51), length=36
group: ecdh_x25519(29), len = 32
record layer: change cipher spec
record layer: app data
record layer: app data
record layer: app data
record layer: app data
```
经过以上沟通,双方获取以下参数。
### 已知参数
加密套件:Cipher Suite:TLS\_AES\_256\_GCM\_SHA384
[ECDHE](https://zhida.zhihu.com/search?content_id=751981217&content_type=Answer&match_order=1&q=ECDHE&zhida_source=entity) 方法:X25519
| 名称 | 长度 | 来源 |
| --- | --- | --- |
| 客户端随机数 | 32字节 | TLS 通讯: client hello |
| 服务器随机数 | 32字节 | TLS 通讯: server hello |
| 客户端ECDHE公钥 | 32字节 | TLS 通讯: client hello, key share, x25519, key exchange |
| 服务器ECDHE公钥 | 32字节 | TLS 通讯: Server hello, key share, x25519, key exchange |
| TLS 版本 V1.3 | NA | TLS 通讯: server hello,supported version |
| 加密算法 | NA | TLS 通讯: server hello, cipher suite: AES\_256\_GCM |
| 散列算法 | NA | TLS 通讯: server hello, cipher suite: SHA384 |
| ECDHE 密钥交换协议 | NA | TLS 通讯: server hello,key share, group: x25519 |
### TLS\_AES\_256\_GCM\_SHA384
TLS\_AES\_256\_GCM\_SHA384 :就是服务器和客户端沟通后,决定采取的加密方法。
简单解释一下。
**`TLS_AES_256_GCM_SHA384` = 协议 + 密钥加密算法 + 消息认证算法**
- **密码套件**: `TLS_AES_256_GCM_SHA384`
- **加密算法**: `AES_256_GCM` (256位密钥的AES-GCM模式)
- **哈希算法**: `SHA-384`
### `AES_256_GCM`
- **含义**:**用于批量数据加密的对称加密算法**。
- **拆解**:
- **`AES`**:**高级加密标准**。这是目前全球最流行、最安全的对称加密算法之一。它被美国政府选为保护机密信息的标准。
- **`256`**:**密钥长度**。表示使用 256 位(32字节)的密钥。在 AES 算法中,密钥越长,理论上越难被暴力破解
- **`GCM`**:**伽罗瓦/计数器模式**。这是一种加密模式,它同时提供了**保密性**(加密)和**完整性认证**(确保数据在传输过程中未被篡改)。
**简单来说,`AES_256_GCM` 负责对实际传输的网页内容、数据等所有应用层信息进行快速、高强度的加密和解密。**
### SHA384
- **含义**:**用于“握手阶段”认证的散列算法**。
- **拆解**:
- **`SHA`**:**安全散列算法**。这是一种密码学散列函数,能将任意长度的数据映射为固定长度的、唯一的“指纹”(散列值)。
- **`384`**:**输出长度**。表示产生 384 位的散列值。
**在 TLS 1.3 中,`SHA384` 的作用与 TLS 1.2 不同:**
- **在 TLS 1.2 中**,SHA 系列算法可能用于计算消息认证码(如 HMAC-SHA256),以确保每条记录的完整性。
- **在 TLS 1.3 中**,数据完整性主要由上面的 `GCM` 模式负责。`SHA384` 在这里主要用于 **握手过程的认证**。具体来说,它被用来生成“握手消息”的摘要,用于创建**伪随机函数** 和计算 **Finished** 消息的验证码,以确保整个 TLS 握手过程没有被篡改。
### ECDHE
Group: x25519 (29)
`x25519` 是一种**密钥交换算法**,更具体地说,它是**椭圆曲线迪菲-赫尔曼** 密钥交换协议的一种高效、安全的实现。
密钥交换的目的是:**在不安全的网络(如互联网)上,让通信的双方(例如你的浏览器和网站服务器)协商出一个只有他们俩才知道的共享秘密。** 这个共享秘密后续会被用作对称加密(如上面提到的 AES\_256\_GCM)的密钥。
`x25519` 就是用来安全地完成这个协商过程的现代方法。
- **`X`**: 代表“Montgomery X”,指的是它在椭圆曲线上使用的特定坐标系统和计算公式(蒙哥马利曲线)。
- **`25519`**: 指的是它所基于的椭圆曲线,即 **Curve25519**。这条曲线由密码学家 Daniel J. Bernstein 设计,其核心域是一个由素数 2255−192255−19 定义的有限域,故名 Curve25519。
所以,`x25519` 就是 **在 Curve25519 椭圆曲线上执行迪菲-赫尔曼密钥交换的函数**。
ECDHE过程简述
在 TLS 握手过程中:
1. 客户端说:“我支持 `x25519`。”
2. 服务器选择 `x25519`,并生成一个临时的密钥对(私钥 + 公钥),将其公钥发送给客户端。
3. 客户端也生成一个临时的 `x25519` 密钥对,并计算与服务器公钥的共享秘密,然后将自己的公钥发送给服务器。
4. 服务器用自己的私钥和客户端的公钥计算,得到**同一个共享秘密**。Pre-Master secret,预备主密钥
5. 双方随后使用这个共享秘密派生出用于加密数据的会话密钥。
---
## TLS1.3密钥
经过上面的步骤,可以计算出PMS(预备主密钥, Pre-Master Secret),然后用这个密钥加密,解密。
打完收工,文章结束。
想多了。
TLS 要是这么简单,也不至于让我学习了好多天,才搞清楚大概。
![](https://pica.zhimg.com/v2-390b61289c1c4a364ac3b39baaab69aa_1440w.jpg)
TLS 的密钥种类,大约有十几种。
上图是密钥是如何一步一步生成的,看的头大吧,头大是正常的。
具体请看:RFC8446 TLS1.3 协议。
[RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3www.rfc-editor.org/rfc/rfc8446](https://link.zhihu.com/?target=https%3A//www.rfc-editor.org/rfc/rfc8446)
### 密钥
什么是密钥?
其实就是一长串数字,长度可能是32字节,48字节,或64字节。
这个数字是随机产生的,用于加密和解密。
### 密钥分类
在 TLS 1.3 中,按用途划分,密钥主要分为以下四个层次,每一层都有其特定用途:
1. **早期数据传输密钥**:用于 0-RTT 数据加密。只有启用 0-RTT 模式时才会生成。
2. **握手数据传输密钥**:用于加密握手消息(如 Certificate, CertificateVerify, Finished)。保护握手过程本身不被窥探。
3. **应用数据传输密钥**:用于加密和解密上层(如 HTTP)传输的应用数据。这是我们最常说的“通信密钥”。
4. **更新主密钥**:用于在连接过程中主动更新后续的应用密钥,以提供更强的后向安全性。
| 类别 | 方向 | 密钥名称 | 数量 |
| --- | --- | --- | --- |
| 握手流量 | 客户端 → 服务器 | client\_handshake\_key, client\_handshake\_iv | 2 |
| | 服务器 → 客户端 | server\_handshake\_key, server\_handshake\_iv | 2 |
| 应用流量 | 客户端 → 服务器 | client\_application\_key, client\_application\_iv | 2 |
| | 服务器 → 客户端 | server\_application\_key, server\_application\_iv | 2 |
| 0-RTT流量 | 客户端 → 服务器 | client\_early\_traffic\_key, client\_early\_traffic\_iv | 2 |
| Finished验证 | 双向 | client\_finished\_key, server\_finished\_key | 2 |
| PSK | 双向 | resumption\_master\_secret pre-shared key | 2 |
| 总计 | | | 14 |
客户和服务器通讯,读和写用不同的密钥,增加安全性。
那么,刚才协商的 PMS,(预备主密钥, Pre-Master Secret),在哪里?
这个表格指的是最终使用的密钥。
为了产生这些密钥,需要产生一些中间密钥。
```
Early Secret = HKDF-Extract(salt, IKM) = HKDF-Extract(0, PSK) = HKDF-Extract(0, 0)
Handshake Secret = HKDF-Extract(salt, IKM) = HKDF-Extract(Derive-Secret(Early Secret, "derived", ""), (EC)DHE)
Master Secret = HKDF-Extract(salt, IKM) = HKDF-Extract(Derive-Secret(Handshake Secret, "derived", ""), 0)
```
**IKM** 是 **Input Keying Material(输入密钥材料)** 的缩写,这是密钥派生函数的原始输入材料。
这里的IKM,就是指 PMS,预备主密钥, Pre-Master Secret。
就是从 PMS 产生 Early 密钥,
Early 密钥产生握手密钥(Handshake Secret);
再从握手密钥产生主密钥(Master Secret)。
握手密钥产生握手期间的密钥对;
主密钥产生数据传输期间的密钥对。
HKDF-Extract 指的是什么?
Salt:指的是什么?
这里不做说明,请自行查看RFC。
### 先做减法
先去除一些不常用的密钥。
resumption\_master\_secret:用来生成 PSK,可以不用
client\_early\_traffic\_key, client\_early\_traffic\_iv:0-RTT,可以不用
### 必用密钥
以下密钥是一定会用到的密钥。
一切密钥的起点:ECDHE 私钥。
ECDHE 私钥相当于种子,种子确定,后面的密钥都确定了。
当然,后面这些密钥还需要一些材料:比如TLS 协议中的随机数,TLS 数据包本身产生的哈希值等。
| 密钥名称 | 作用 | 谁派生的 |
| --- | --- | --- |
| 临时密钥 | | |
| PMS,预备主密钥 | 一切密钥的基础 | ECDHE私钥和对方的公钥 |
| Early, 早期密钥 | 密钥的基础 | PMS 预备主密钥派生 |
| Handshake,握手密钥 | 产生握手阶段的密钥 | Early 早期密钥派生 |
| Master,主密钥 | 产生数据传输阶段的密钥 | Handshake 握手密钥派生 |
| 中间密钥 | | |
| CLIENT\_HANDSHAKE\_TRAFFIC\_SECRET | 用于产生最终密钥 | Handshake 握手密钥派生 |
| SERVER\_HANDSHAKE\_TRAFFIC\_SECRET | 用于产生最终密钥 | Handshake 握手密钥派生 |
| CLIENT\_TRAFFIC\_SECRET\_0 | 用于产生最终密钥 | Master 主密钥派生 |
| SERVER\_TRAFFIC\_SECRET\_0 | 用于产生最终密钥 | Master 主密钥派生 |
最终密钥的类型:TLS\_AES\_256\_GCM\_SHA384 为例
```
- \`客户端写密钥\` - 用于客户端加密/服务器解密。48字节
- \`服务器写密钥\` - 用于服务器加密/客户端解密。48字节
- \`客户端写 IV\` - 初始化向量。12字节
- \`服务器写 IV\` - 初始化向量。12字节
```
IV(Initialization Vector):初始向量,还有一个名字:nonce,也是一个随机数。
真正做加密解密的,其实是写密钥和初始向量IV。
---
## 总结
啰啰嗦嗦这么多,写得我头大,看的人估计也蒙了。
没办法,TLS 就是这么复杂。
为了写 MITM 代理,不仅要看懂协议,还要在 OpenSSL 开源代码中找到相应的函数,
实现所有的计算。
从最初的 ECDHE 私钥开始,计算十几个密钥,容易吗。
还好,我已经建立了一个项目,专门学习这件事。
如果密钥计算错误,后面的包一定解析错误,没法继续。
破解 https 流量包,真的很难。
谁不难呢?
学习OpenSSL,调用 OpenSSL 库都这么难,写 OpenSSL 库就更难了。
## 后记
[![](https://picx.zhimg.com/v2-a1b5f8ff4e7d54f863be51081b631633.jpg?source=7e7ef6e2&needBackground=1)openssl-5-立项openssl-dec1 赞同 · 0 评论](https://zhuanlan.zhihu.com/p/1960580134147958148) 文章
这个项目,用来学习 TLS协议和 Open SSL。
解密https:用MITM 技术,两边欺骗,用自己的私钥
现在是学习阶段,只要解析抓到的https:破解需要 ECDHE 私钥,从哪里取得私钥?
这里先卖个关子,以后再说。
现在假设已经取得本次https 通讯的ECDHE 私钥,需要破解本次https,怎么做?
就需要计算上面提到的所有密钥,然后用密钥解密。
下一节,先讲 ECDHE:计算预备主密钥 PMS。
再下一节,讲如何从 PMS 生成 Early 密钥,再生成握手密钥,再生成主密钥;
最后,讲最终密钥的诞生。
都是用 c 语言实现,调用OpenSSL 库,实际项目展示。