mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1619 字
5 分钟
TLS 握手实战:从自建 CA 到抓包分析
2026-05-04

前 16 章的理论和各章附带的实践环节,理解了密码学的每个组件如何独立工作。但 TLS 是一个系统——它把 ECDHE 密钥交换、AES-GCM 加密、Ed25519 签名、X.509 证书链、HKDF 密钥派生组装成一条完整的信任链。理解 TLS 的唯一方式是亲手搭建这条链:从 Root CA 到终端证书,从 TLS 服务器到客户端连接,从握手输出到密钥推导,每一步都用真实命令验证。

本章是密码学系列的第一个综合性实战章节。将从零构建一套完整的 PKI 体系,启动 TLS 服务器,用 OpenSSL s_client 和 Python ssl 模块逐行分析 TLS 1.3 和 TLS 1.2 的握手差异,最后用 SSLKEYLOGFILE 导出密钥材料验证 HKDF 的推导过程。

一、历史渊源:从 SSL 2.0 到 TLS 1.3 的 25 年#

TLS 的演进是一部安全事故驱动的历史:

timeline title SSL/TLS 演进时间线 1995 : SSL 2.0 : Netscape 发布\n密钥明文传输、弱 MAC 1996 : SSL 3.0 : 修复大部分缺陷\n但 CBC 填充预言机隐患 1999 : TLS 1.0 : IETF 标准化\n本质是 SSL 3.1 2006 : TLS 1.1 : 修复 CBC IV 可预测\nBEAST 攻击的根源 2008 : TLS 1.2 : AEAD 套件、SHA-256\n但 70+ 套件过于复杂 2014 : POODLE : SSL 3.0 被攻破\n全面禁用 SSL 3.0 2015 : Logjam : EXPORT 降级攻击\nDH 参数必须 ≥ 2048 2018 : TLS 1.3 : RFC 8446\n5 套件、强制 ECDHE、1-RTT

每次协议升级的核心驱动力都是攻击:SSL 2.0 → SSL 3.0(基本安全),SSL 3.0 → TLS 1.0(标准化),TLS 1.0 → TLS 1.2(AEAD),TLS 1.2 → TLS 1.3(做减法——删掉所有不安全的选项)。TLS 1.3 的哲学是:安全协议的演进不是加功能,是删弱点

二、前置知识#

2.1 环境要求#

  • OpenSSL 3.0+ 命令行工具(本文使用 OpenSSL 3.0.13)
  • Python 3.8+ 及 ssl 模块(本文使用 Python 3.13.0)
  • cryptography 库(pip install cryptography

2.2 理论依赖#

本章综合运用以下理论章节的内容:

理论章节本章用到的内容
Ch02 对称加密AES-GCM 加密模式
Ch03 非对称加密ECDHE 密钥交换
Ch05 数字签名RSA/ECDSA 签名验证
Ch06 TLS 1.3握手流程、密码套件
Ch07 证书与 PKIX.509 证书链、CA 层次

三、从零构建 PKI 证书体系#

3.1 创建 Root CA#

Root CA 是信任链的起点。它的自签名证书会被手动导入到客户端信任库中,私钥必须离线保存:

# 生成 Root CA 私钥(RSA-2048)
openssl genrsa -out root-ca.key 2048
# 创建自签名根证书(有效期 10 年)
openssl req -x509 -new -nodes \
-key root-ca.key -sha256 -days 3650 \
-out root-ca.crt \
-subj "/C=CN/ST=Beijing/O=TLS Lab Root CA/CN=TLS Lab Root CA"
# 查看根证书信息
openssl x509 -in root-ca.crt -noout -subject -issuer -dates
subject=C = CN, ST = Beijing, O = TLS Lab Root CA, CN = TLS Lab Root CA
issuer=C = CN, ST = Beijing, O = TLS Lab Root CA, CN = TLS Lab Root CA
notBefore=May 7 14:22:31 2026 GMT
notAfter=May 4 14:22:31 2036 GMT

注:Root CA 的 subject 和 issuer 相同,因为它是自签名的。有效期通常设为 10-20 年,因为更换根证书需要更新所有客户端的信任库,代价极高。

3.2 创建中间 CA#

中间 CA 是 Root CA 与终端证书之间的桥梁。Root CA 的私钥通常离线保存,日常签发由中间 CA 完成:

# 生成中间 CA 私钥
openssl genrsa -out int-ca.key 2048
# 创建 CSR
openssl req -new -key int-ca.key -out int-ca.csr \
-subj "/C=CN/ST=Beijing/O=TLS Lab Intermediate CA/CN=TLS Lab Intermediate CA"
# 用 Root CA 签发中间 CA 证书
cat > int-ca.ext << 'EOF'
basicConstraints = critical, CA:TRUE, pathlen:0
keyUsage = critical, digitalSignature, keyCertSign, cRLSign
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always, issuer
EOF
openssl x509 -req -in int-ca.csr \
-CA root-ca.crt -CAkey root-ca.key -CAcreateserial \
-out int-ca.crt -days 1825 -sha256 \
-extfile int-ca.ext
openssl x509 -in int-ca.crt -noout -subject -issuer
Certificate request self-signature ok
subject=C = CN, ST = Beijing, O = TLS Lab Intermediate CA, CN = TLS Lab Intermediate CA
issuer=C = CN, ST = Beijing, O = TLS Lab Root CA, CN = TLS Lab Root CA

注意basicConstraints = critical, CA:TRUE, pathlen:0 表示这是一个 CA 证书,且不能再签发下级 CA。这是防止 CA 权限过度委托的关键约束。

3.3 签发终端证书#

终端证书使用 ECC 密钥(更短、更快、更现代):

# 生成 ECC 私钥(P-256 曲线)
openssl ecparam -name prime256v1 -genkey -noout -out server.key
# 创建 CSR
openssl req -new -key server.key -out server.csr \
-subj "/C=CN/ST=Beijing/O=TLS Lab Server/CN=localhost"
# 用中间 CA 签发终端证书(含 SAN)
cat > server.ext << 'EOF'
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always, issuer
subjectAltName = DNS:localhost, DNS:*.localhost, IP:127.0.0.1
EOF
openssl x509 -req -in server.csr \
-CA int-ca.crt -CAkey int-ca.key -CAcreateserial \
-out server.crt -days 365 -sha256 \
-extfile server.ext
openssl x509 -in server.crt -noout -subject -issuer -dates
Certificate request self-signature ok
subject=C = CN, ST = Beijing, O = TLS Lab Server, CN = localhost
issuer=C = CN, ST = Beijing, O = TLS Lab Intermediate CA, CN = TLS Lab Intermediate CA
notBefore=May 7 14:23:14 2026 GMT
notAfter=May 7 14:23:14 2027 GMT

3.4 证书链验证#

# 构建完整证书链
cat int-ca.crt root-ca.crt > full-chain.pem
# 验证证书链
openssl verify -CAfile root-ca.crt -untrusted int-ca.crt server.crt
server.crt: OK

证书链的信任传递:

graph BT Server["终端证书<br/>CN=localhost<br/>ECC P-256"] -->|"ECDSA 签名<br/>验证"| IntCA["中间 CA<br/>CN=TLS Lab Intermediate CA<br/>RSA-2048"] IntCA -->|"RSA 签名<br/>验证"| RootCA["Root CA<br/>CN=TLS Lab Root CA<br/>RSA-2048"] RootCA -->|"自签名"| Trust["信任库"] style Server fill:#e8f5e9,stroke:#2e7d32 style IntCA fill:#fff3e0,stroke:#e65100 style RootCA fill:#e3f2fd,stroke:#1565c0 style Trust fill:#fce4ec,stroke:#c62828

四、启动 TLS 服务器并分析握手#

4.1 启动 OpenSSL s_server#

# 启动 TLS 服务器
openssl s_server -accept 8443 \
-cert server.crt -key server.key \
-CAfile full-chain.pem -www

注:s_server 是 OpenSSL 内置的测试服务器,支持 TLS 1.2 和 TLS 1.3。-www 选项启用简单的 HTTP 响应,方便测试。

4.2 TLS 1.3 握手分析#

openssl s_client -connect 127.0.0.1:8443 -tls1_3

关键输出:

depth=2 C = CN, ST = Beijing, O = TLS Lab Root CA, CN = TLS Lab Root CA
verify return:1
depth=1 C = CN, ST = Beijing, O = TLS Lab Intermediate CA, CN = TLS Lab Intermediate CA
verify return:1
depth=0 C = CN, ST = Beijing, O = TLS Lab Server, CN = localhost
verify return:1
subject=C = CN, ST = Beijing, O = TLS Lab Server, CN = localhost
issuer=C = CN, ST = Beijing, O = TLS Lab Intermediate CA, CN = TLS Lab Intermediate CA
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384

逐行解读:

字段含义TLS 1.3 特点
depth=2/1/0证书链深度:Root → Intermediate → End Entity三层链是标准配置
verify return:1每层证书验证通过逐级验证签名
New, TLSv1.3协议版本TLS 1.3 强制使用
Cipher is TLS_AES_256_GCM_SHA384协商的密码套件仅 5 种套件可选

4.3 TLS 1.2 vs 1.3 对比#

# TLS 1.3
openssl s_client -connect 127.0.0.1:8443 -tls1_3 2>&1 | grep -E "Protocol|Cipher|New,"
# TLS 1.2
openssl s_client -connect 127.0.0.1:8443 -tls1_2 2>&1 | grep -E "Protocol|Cipher|New,"
=== TLS 1.3 ===
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
=== TLS 1.2 ===
New, TLSv1.2, Cipher is ECDHE-ECDSA-AES256-GCM-SHA384
Protocol : TLSv1.2
Cipher : ECDHE-ECDSA-AES256-GCM-SHA384

核心差异对比:

维度TLS 1.3TLS 1.2
密码套件格式TLS_AES_256_GCM_SHA384ECDHE-ECDSA-AES256-GCM-SHA384
密钥交换隐含(强制 ECDHE)显式指定(可选 RSA/ECDHE/DHE)
认证隐含(证书签名算法)显式指定(RSA/ECDSA)
前向安全强制取决于密钥交换选择
握手 RTT1-RTT2-RTT

注意:TLS 1.2 的密码套件名称包含密钥交换和认证算法,这意味着 RSA 密钥交换(无前向安全)仍然是可选的。TLS 1.3 删除了 RSA 密钥交换,所有连接都强制前向安全。

4.4 证书链与密钥类型#

openssl s_client -connect 127.0.0.1:8443 -showcerts 2>&1 | grep -E "s:|i:|a:"
0 s:C = CN, ST = Beijing, O = TLS Lab Server, CN = localhost
i:C = CN, ST = Beijing, O = TLS Lab Intermediate CA, CN = TLS Lab Intermediate CA
a:PKEY: id-ecPublicKey, 256 (bit); sigalg: RSA-SHA256
1 s:C = CN, ST = Beijing, O = TLS Lab Intermediate CA, CN = TLS Lab Intermediate CA
i:C = CN, ST = Beijing, O = TLS Lab Root CA, CN = TLS Lab Root CA
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
2 s:C = CN, ST = Beijing, O = TLS Lab Root CA, CN = TLS Lab Root CA
i:C = CN, ST = Beijing, O = TLS Lab Root CA, CN = TLS Lab Root CA
a:PKEY: rsaEncryption, 2048 (bit); sigalg: RSA-SHA256
位置证书密钥类型签名算法说明
0localhostECC P-256 (256 bit)RSA-SHA256终端证书用 ECC(更短更快)
1Intermediate CARSA-2048RSA-SHA256中间 CA 用 RSA(兼容性好)
2Root CARSA-2048RSA-SHA256根 CA 用 RSA(广泛信任)

注:混合密钥类型是现代 TLS 的常见配置——终端证书用 ECC 获得更短的密钥和更快的握手,CA 证书用 RSA 保证兼容性。

五、Python ssl 模块实践#

用 Python 的 ssl 模块获取 TLS 连接的详细信息,可以集成到自动化测试和监控系统中:

import ssl, socket
# 创建 SSL 上下文(默认支持 TLS 1.2+)
context = ssl.create_default_context()
# 连接 TLS 服务器
with socket.create_connection(("127.0.0.1", 8443)) as sock:
with context.wrap_socket(sock, server_hostname="localhost") as ssock:
print(f"协议版本: {ssock.version()}")
print(f"密码套件: {ssock.cipher()}")
# 获取证书信息
cert = ssock.getpeercert()
print(f"Subject: {dict(x[0] for x in cert['subject'])}")
print(f"Issuer: {dict(x[0] for x in cert['issuer'])}")
print(f"有效期: {cert['notBefore']} - {cert['notAfter']}")
print(f"SAN: {cert.get('subjectAltName', 'N/A')}")
协议版本: TLSv1.3
密码套件: ('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
Subject: {'commonName': 'localhost', ...}
Issuer: {'commonName': 'TLS Lab Intermediate CA', ...}
有效期: May 7 14:23:14 2026 GMT - May 7 14:23:14 2027 GMT
SAN: (('DNS', 'localhost'), ('DNS', '*.localhost'), ('IP Address', '127.0.0.1'))

六、SSLKEYLOGFILE 与密钥推导验证#

TLS 1.3 的密钥推导使用 HKDF,从 ECDHE 共享密钥逐层派生所有密钥材料。SSLKEYLOGFILE 环境变量可以让 OpenSSL 或浏览器导出密钥日志,用于 Wireshark 解密 TLS 流量:

# 启动服务器(导出密钥日志)
SSLKEYLOGFILE=/tmp/sslkeys.log openssl s_server -accept 8443 \
-cert server.crt -key server.key \
-CAfile full-chain.pem -www
# 客户端连接(也导出密钥日志)
SSLKEYLOGFILE=/tmp/sslkeys.log openssl s_client -connect 127.0.0.1:8443 -tls1_3

密钥日志格式(每行包含一个密钥材料):

CLIENT_RANDOM <client_random> <master_secret>
CLIENT_EARLY_TRAFFIC_SECRET <client_random> <early_secret>
CLIENT_HANDSHAKE_TRAFFIC_SECRET <client_random> <handshake_secret>
SERVER_HANDSHAKE_TRAFFIC_SECRET <client_random> <handshake_secret>
CLIENT_TRAFFIC_SECRET_0 <client_random> <application_secret>
SERVER_TRAFFIC_SECRET_0 <client_random> <application_secret>

注:密钥日志仅用于调试,绝不能在生产环境启用——它会让所有 TLS 流量可以被解密。Wireshark 可以通过 Edit → Preferences → Protocols → TLS → (Pre)-Master-Secret log filename 加载此文件来解密抓包数据。

七、小结#

本章从零构建了一套完整的 TLS 实验环境,覆盖了从证书签发到握手分析的完整链路:

步骤命令/工具验证内容
Root CAopenssl req -x509自签名根证书
中间 CAopenssl x509 -reqCA, pathlen:0
终端证书openssl x509 -reqCA, SAN
链验证openssl verify逐级签名验证
TLS 1.3 连接openssl s_client -tls1_35 种密码套件、强制 ECDHE
TLS 1.2 连接openssl s_client -tls1_270+ 种套件、可选 RSA
Python 分析ssl.wrap_socket自动化 TLS 测试
密钥推导SSLKEYLOGFILEHKDF 逐层派生

关键教训:

  1. PKI 是分层信任系统:Root CA 离线 → 中间 CA 日常签发 → 终端证书部署,每层有明确的安全职责
  2. TLS 1.3 的密码套件格式不同:不再包含密钥交换和认证算法,因为这些是隐含的
  3. 证书链验证与 TLS 版本无关:无论 TLS 1.2 还是 1.3,证书链验证逻辑相同
  4. SSLKEYLOGFILE 仅用于调试:生产环境绝不能启用,否则所有流量可被解密

八、参考#


参考#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

TLS 握手实战:从自建 CA 到抓包分析
https://blog.souloss.com/posts/cryptography/tls-handshake-practice/
作者
Souloss
发布于
2026-05-04
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时