TLS Keying Material Export(密钥材料导出)是 RFC 5705 定义的一个重要功能,允许从 TLS 会话中导出密钥材料,用于构建更上层的安全协议。本文介绍其原理及 OpenSSL 命令行实战。
什么是 Keying Material Export?#
RFC 5705 定义了从 TLS 握手或连接中导出密钥材料的机制。导出的密钥材料可以用于:
- 额外密钥派生:为应用程序特定用途生成额外的加密密钥
- Channel Binding:绑定 TLS 通道到上层协议(如 TLS-SRP)
- API 认证:某些 API 使用导出的密钥材料进行身份验证
导出公式如下:
1
2
3
4
5
6
|
exporter_label:导出标签(如 "client finished")
context_value:上下文值(可选)
length:导出长度
ExporterMaterial(ExporterLabel, ContextValue, Length) =
HKDF-Expand-Label(DerivedSecret, ExporterLabel, ContextValue, Length)
|
使用 OpenSSL s_client 导出#
OpenSSL s_client 提供了 -keymatexport 和 -keymatexportlen 两个参数:
1
2
3
|
openssl s_client -connect example.com:443 \
-keymatexport "my-app-label" \
-keymatexportlen 32
|
参数说明#
| 参数 |
说明 |
-keymatexport |
导出使用的标签(label),字符串类型 |
-keymatexportlen |
导出密钥材料的长度(字节),默认 20 |
完整示例#
1
2
3
4
5
6
7
8
9
|
# 连接到测试服务器并导出 32 字节密钥材料
$ echo "Q" | openssl s_client -connect cloudflare.com:443 \
-keymatexport "application-key" \
-keymatexportlen 32 2>/dev/null | grep -A3 "Keying material"
Keying material exporter:
Label: 'application-key'
Length: 32 bytes
Keying material: 529152F31C7576568F7946568F1032BCE53AC1D52ED2699A3D4791AE57755748
|
本地测试服务器#
也可以使用 OpenSSL s_server 创建本地测试服务器:
1
2
3
4
5
6
7
8
9
10
|
# 1. 生成测试证书
openssl req -x509 -newkey rsa:2048 -keyout /tmp/test.key -out /tmp/test.crt -days 1 -nodes -subj "/CN=test"
# 2. 启动测试服务器
openssl s_server -key /tmp/test.key -cert /tmp/test.crt -www -accept 4433 &
# 3. 客户端连接并导出密钥材料
echo "Q" | openssl s_client -connect localhost:4433 \
-keymatexport "test-label" \
-keymatexportlen 32
|
输出示例:
1
2
3
4
|
Keying material exporter:
Label: 'test-label'
Length: 32 bytes
Keying material: A1B2C3D4E5F6...(32字节十六进制)
|
在程序中使用 EVP API#
如果需要在 C/C++ 程序中使用 Keying Material Export,OpenSSL 3.x 提供了 EVP API:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
#include <openssl/ssl.h>
#include <openssl/kdf.h>
int export_keying_material(SSL *ssl, const char *label,
unsigned char *out, size_t outlen) {
EVP_KDF *kdf = EVP_KDF_fetch(NULL, "TLS13-KDF", NULL);
if (!kdf) return -1;
EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf);
EVP_KDF_free(kdf);
if (!ctx) return -1;
OSSL_PARAM params[] = {
OSSL_PARAM_construct_utf8_string("mode", "exporter", 0),
OSSL_PARAM_construct_utf8_string("label", label, 0),
OSSL_PARAM_construct_octet_string("secret",
SSL_get_master_key(ssl, NULL), SSL_get_master_key_length(ssl)),
OSSL_PARAM_construct_size_t("length", &outlen),
OSSL_PARAM_construct_octet_string("output", out, outlen),
OSSL_PARAM_END
};
int ret = EVP_KDF_CTX_set_params(ctx, params);
EVP_KDF_CTX_free(ctx);
return ret == 1 ? 0 : -1;
}
|
TLS 1.2 与 TLS 1.3 的差异#
TLS 1.2 和 TLS 1.3 在 Keying Material Export 上有区别:
| 特性 |
TLS 1.2 |
TLS 1.3 |
| 导出时机 |
握手完成后 |
握手完成后 |
| 基础密钥 |
master_secret |
derived_secret |
| API 兼容性 |
SSL_export_keying_material() |
TLS 1.3 HKDF |
TLS 1.3 中,导出使用 HKDF-Expand-Label,公式更简洁。
安全注意事项#
- 标签唯一性:使用应用专属的标签,避免与标准标签(如 “client finished”)冲突
- 上下文值:可使用上下文值进一步区分导出内容
- 密钥长度:根据实际需求选择合适长度,AES-256 需要 32 字节
- TLS 版本:TLS 1.3 优先,其 HKDF 设计更安全
参考来源#
本文示例基于 OpenSSL 3.0.20 测试。