9747 字
49 分钟
HTTPS/TLS 加密体系技术文档:从握手链路到算法安全本质

版本:2026-07-02 最终整合版
目标:围绕现代 HTTPS/TLS 仍在主流使用的加密技术,建立一份“链路完整、算法细粒度、数学原理清楚、单向/陷门安全本质融入每个算法”的技术文档。
主线协议:TLS 1.3
兼容视角:TLS 1.2 中仍安全的 ECDHE + AEAD 组合
不主讲:SSL、TLS 1.0/1.1、RSA Key Exchange、静态 DH、RC4、3DES、CBC 套件、MD5/SHA-1 签名、DSA、EXPORT 套件等历史/弃用技术。


目录#

  1. 总览:现代 HTTPS 到底由哪些安全能力组成
  2. 现代 HTTPS/TLS 1.3 完整握手链路
  3. ECDHE:现代 HTTPS 的密钥协商核心
  4. HKDF + HMAC + Transcript Hash:共享秘密如何派生成通信密钥
  5. X.509 / CA 证书链:服务器公钥如何变得可信
  6. 数字签名体系:RSA-PSS、ECDSA、EdDSA
  7. AEAD 记录层加密:AES-GCM 与 ChaCha20-Poly1305
  8. RSA-OAEP:现代可用的公钥加密方案,但不是 TLS 1.3 主线
  9. 现代推荐与弃用技术边界
  10. 最终总结
  11. 参考文献

1. 总览:现代 HTTPS 到底由哪些安全能力组成#

HTTPS 可以简单写成:

HTTPS = HTTP + TLS

HTTP 负责业务语义:

GET /user/profile
POST /login
Cookie
JSON
HTML
图片
视频

TLS 负责通信安全:

身份认证
密钥协商
密钥派生
数据加密
完整性校验
防篡改
防伪造
前向安全性

现代 TLS 1.3 的主线不是“用服务器公钥加密所有 HTTP 内容”,也不是“浏览器生成会话密钥 K 后用 RSA 公钥加密 K 发过去”。

更准确的现代链路是:

1. 浏览器和服务器通过 ClientHello / ServerHello 交换能力和 ECDHE 临时公钥
2. 双方用 ECDHE 各自算出同一个共享秘密 shared secret
3. 服务器发送 X.509 证书链,浏览器验证服务器公钥是否可信
4. 服务器用证书对应私钥签名握手 transcript,证明自己持有私钥
5. 双方用 HKDF 从 shared secret 派生握手密钥、应用数据密钥、IV、finished_key
6. 双方用 Finished 验证整个握手 transcript 没有被篡改
7. 后续 HTTP 数据用 AES-GCM 或 ChaCha20-Poly1305 做 AEAD 认证加密

可以把现代 HTTPS 拆成五类密码学能力:

能力代表算法/机制解决的问题
身份认证X.509 证书链、CA 签名、CertificateVerify确认连接的是目标服务器,不是中间人
密钥协商ECDHE / X25519 / P-256双方在不安全网络上得到同一个共享秘密
密钥派生HKDF + HMAC + SHA-256/SHA-384从共享秘密派生出多把隔离密钥
数据保护AES-GCM、ChaCha20-Poly1305后续 HTTP 数据加密、防篡改、认证
安全绑定Transcript Hash、Finished把握手消息、密钥、身份绑定到同一个上下文

1.1 这份文档的算法讲解方法#

每个算法不再只讲“流程”,而是按以下结构展开:

1. 它在 HTTPS 里负责什么
2. 输入是什么
3. 输出是什么
4. 正向计算为什么容易
5. 反向求解、伪造或攻击为什么困难
6. 它依赖哪个数学困难问题或安全假设
7. 它在 TLS 1.3 中如何落地
8. 工程实现要注意什么

这样你能真正理解:

为什么公钥可以公开
为什么私钥不能推出来
为什么 aG 不能反推出 a
为什么签名能验证身份
为什么证书链不能被伪造
为什么对称密钥泄露后才危险
为什么 nonce 复用会出大问题

2. 现代 HTTPS/TLS 1.3 完整握手链路#

下面以用户访问:

https://www.example.com

为例,从通信开始讲到后续 HTTP 数据加密传输。


2.1 DNS 解析#

浏览器首先需要知道目标域名对应的 IP:

www.example.com → 203.0.113.10

这一步不是 TLS 本身,但它发生在连接之前。后续 TLS 证书验证会检查:

证书里的域名是否匹配 www.example.com

2.2 TCP 连接建立#

HTTPS over TCP 场景下,浏览器先和服务器建立 TCP 连接:

Client → Server:SYN
Server → Client:SYN + ACK
Client → Server:ACK

TCP 只解决可靠传输,不解决加密和身份认证。

TLS 在 TCP 连接之上工作。


2.3 ClientHello:客户端声明能力并发送临时公钥#

浏览器发送 TLS 1.3 ClientHello。

典型内容:

ClientHello {
supported_versions = [TLS 1.3, TLS 1.2]
cipher_suites = [
TLS_AES_128_GCM_SHA256,
TLS_AES_256_GCM_SHA384,
TLS_CHACHA20_POLY1305_SHA256
]
client_random
server_name = "www.example.com"
supported_groups = [x25519, secp256r1, secp384r1]
signature_algorithms = [
ecdsa_secp256r1_sha256,
rsa_pss_rsae_sha256,
rsa_pss_pss_sha256,
ed25519
]
key_share = {
group = x25519,
key_exchange = A
}
alpn = ["h2", "http/1.1"]
}

这里最核心的是:

key_share = A

客户端本地生成临时私钥:

a

然后计算临时公钥:

A = aG

发送的是:

A

不发送的是:

a

a 是本次连接的临时私钥,连接结束后会丢弃。


2.4 ServerHello:服务器选择参数并发送临时公钥#

服务器收到 ClientHello 后,选择双方都支持的参数。

典型 ServerHello:

ServerHello {
selected_version = TLS 1.3
selected_cipher_suite = TLS_AES_128_GCM_SHA256
server_random
key_share = {
group = x25519,
key_exchange = B
}
}

服务器本地生成临时私钥:

b

计算临时公钥:

B = bG

发送的是:

B

不发送的是:

b

2.5 双方计算 ECDHE 共享秘密#

客户端拿到服务器临时公钥:

B = bG

客户端自己知道:

a

所以客户端计算:

S = aB
= a(bG)
= abG

服务器拿到客户端临时公钥:

A = aG

服务器自己知道:

b

所以服务器计算:

S = bA
= b(aG)
= abG

双方得到同一个共享秘密:

S = abG

网络攻击者能看到:

G
A = aG
B = bG

但不知道:

a
b
S = abG

这就是 ECDHE 密钥协商。

注意,这一步只解决:

双方算出同一个秘密

还没有单独解决:

对方到底是不是 www.example.com

身份认证要靠证书链和 CertificateVerify。


2.6 ServerHello 后派生握手密钥#

TLS 1.3 在 ServerHello 后,客户端和服务器都已经有:

ECDHE shared secret S

然后双方可以用 HKDF 派生握手阶段密钥:

S → handshake_secret → handshake_traffic_keys

因此 TLS 1.3 中 ServerHello 之后的部分握手消息通常已经受到加密保护,例如:

EncryptedExtensions
Certificate
CertificateVerify
Finished

这和 TLS 1.2 不同,TLS 1.3 尽早加密更多握手内容。


2.7 Certificate:服务器发送证书链#

服务器发送:

Certificate {
Server Certificate
Intermediate CA Certificate
...
}

服务器证书包含:

域名 / SAN:www.example.com
服务器长期公钥 server_public_key
有效期
用途:TLS Web Server Authentication
签发者:Intermediate CA
Intermediate CA 对服务器证书内容的签名

中间 CA 证书包含:

主体:Intermediate CA
中间 CA 公钥 intermediate_public_key
签发者:Root CA
Root CA 对中间 CA 证书内容的签名

浏览器本地已经预装一批可信根证书:

Root CA Certificate
Root CA Public Key

浏览器本地没有 Root CA 私钥。


2.8 浏览器验证证书链#

证书链不是嵌套解密,而是逐层验签。

整体路径:

Root CA 公钥
↓ 验证签名
Intermediate CA 证书可信
↓ 取出 Intermediate CA 公钥
Intermediate CA 公钥
↓ 验证签名
Server 证书可信
↓ 取出 Server 公钥

浏览器还会检查:

证书域名是否匹配当前访问的 www.example.com
证书是否过期
证书用途是否允许 serverAuth
中间 CA 是否具备 CA=true
中间 CA 是否具备 keyCertSign
证书路径长度是否满足
关键扩展是否可理解
证书是否被吊销

证书链验证通过后,浏览器才认为:

server_public_key 确实属于 www.example.com

2.9 CertificateVerify:服务器证明自己持有私钥#

证书是公开的,攻击者可以复制证书内容。

所以服务器还必须证明:

我持有 server_public_key 对应的 server_private_key

服务器会对当前握手 transcript 做签名:

transcript_hash = Hash(ClientHello || ServerHello || EncryptedExtensions || Certificate || ...)
signature = Sign(server_private_key, transcript_hash)

浏览器使用服务器证书里的公钥验签:

Verify(server_public_key, transcript_hash, signature)

如果通过,说明:

服务器确实持有证书对应私钥
服务器临时公钥 B 被绑定到真实服务器身份上
中间人不能替换 B 后还能通过签名验证

2.10 Finished:双方确认握手未被篡改#

双方根据握手密钥派生出 finished_key,然后生成 Finished:

verify_data = HMAC(finished_key, Hash(handshake_transcript))

如果握手过程中任何消息被中间人修改:

handshake_transcript 不同
transcript_hash 不同
verify_data 不同
Finished 校验失败

Finished 让双方确认:

我们看到的是同一组握手消息
我们派生出的是同一组密钥
这个握手没有被中途篡改

2.11 后续应用数据加密#

握手完成后,HTTP 数据会被 TLS Record Layer 加密。

例如:

GET /api/user HTTP/2
Cookie: session=...

进入 TLS 后:

plaintext = HTTP data
ciphertext, tag = AEAD_Encrypt(
key = client_application_write_key,
nonce = per_record_nonce,
plaintext = HTTP data,
aad = TLS record header
)

服务器解密:

plaintext = AEAD_Decrypt(
key,
nonce,
ciphertext,
aad,
tag
)

tag 验证失败则直接丢弃。


3. ECDHE:现代 HTTPS 的密钥协商核心#

ECDHE 是现代 HTTPS/TLS 1.3 的关键算法之一。它不是加密 HTTP 数据,也不是数字签名,而是:

密钥协商算法

它解决的问题是:

客户端和服务器如何在不安全网络上协商出同一个共享秘密?

3.1 ECDHE 在 TLS 中负责什么#

ECDHE 输出一个共享秘密:

shared_secret = S

这个 S 不直接用来加密 HTTP 数据,而是交给 HKDF 派生正式密钥。

在 TLS 1.3 中:

ECDHE shared secret
HKDF
handshake traffic secret
application traffic secret
write key
write iv
finished key

ECDHE 负责:

产生只有客户端和服务器知道的原始共享秘密

HKDF 负责:

把共享秘密变成可用于 TLS 各阶段的密钥

AEAD 负责:

用派生出的密钥加密 HTTP 数据

3.2 ECDHE 的输入与输出#

公开参数:

椭圆曲线参数
基点 G
曲线阶 n

客户端输入:

临时私钥 a

客户端输出:

临时公钥 A = aG

服务器输入:

临时私钥 b

服务器输出:

临时公钥 B = bG

交换后:

客户端计算 S = aB
服务器计算 S = bA

最终双方得到:

S = abG

3.3 aG 到底是什么#

aG 不是普通乘法。

在椭圆曲线群里:

G 是曲线上的一个公开基点
a 是一个大整数
aG 表示把 G 重复加 a 次

也就是:

aG = G + G + G + ... + G

一共加 a 次。

这个操作叫:

椭圆曲线标量乘法

它和普通整数乘法不同。

点加法的结果仍然是曲线上的点:

G = (x_G, y_G)
A = aG = (x_A, y_A)

3.4 正向为什么容易#

虽然数学定义是“加 a 次”,但实现不会真的循环 a 次。

实现会用类似快速幂的算法:

double-and-add
Montgomery ladder
window method

例如计算:

13G

因为:

13 = 8 + 4 + 1

可以先计算:

2G
4G
8G

然后:

13G = 8G + 4G + G

复杂度大约是:

O(log a)

所以:

给定 a 和 G,计算 A = aG 很快

X25519 采用 Montgomery ladder 风格的标量乘法,既高效,也有利于常数时间实现,从而降低侧信道风险。


3.5 反向为什么困难:ECDLP#

攻击者看到:

G
A = aG

想求:

a

这个问题叫:

椭圆曲线离散对数问题
Elliptic Curve Discrete Logarithm Problem, ECDLP

形式化:

已知 G 和 A,且 A = aG,求 a。

为什么不能像普通数字那样:

a = A / G

因为:

A 和 G 是椭圆曲线上的点
aG 是重复点加法
椭圆曲线群里没有高效的“除以 G”操作可以恢复 a

可以类比为:

正向:从起点 G 按规则跳 a 步,得到 A
反向:只知道起点 G 和终点 A,要猜到底跳了多少步

由于群非常大,无法逐个试。


3.6 暴力破解为什么不可行#

以 X25519 对应的安全级别粗略理解,私钥空间非常大。

如果群阶接近:

2^252

普通暴力枚举需要接近:

2^252

次尝试。

通用攻击算法 Pollard rho 复杂度大约是:

O(√n)

如果:

n ≈ 2^252

那么:

√n ≈ 2^126

这仍然是现实不可行的计算量。

所以 X25519 常被认为提供约 128-bit 安全级别。


3.7 为什么双方都能算出 abG#

客户端:

S_client = aB
= a(bG)
= abG

服务器:

S_server = bA
= b(aG)
= baG
= abG

这里依赖椭圆曲线群的标量乘法结合性:

a(bG) = (ab)G = b(aG)

攻击者没有 ab,只有:

G
aG
bG

想直接求:

abG

这个问题叫:

Computational Diffie-Hellman Problem, CDH

形式化:

给定 G, aG, bG,求 abG。

在合适曲线参数下,这被认为不可高效求解。


3.8 ECDHE 的安全本质嵌入总结#

ECDHE 的安全不是来自“服务器藏着一个能反解 aG 的陷门”。

它的核心是:

正向 a → aG 容易
反向 aG → a 困难
给定 G, aG, bG 求 abG 困难

因此 ECDHE 更准确说是:

基于椭圆曲线离散对数困难和 Diffie-Hellman 困难的密钥协商

而不是典型的:

陷门加密函数

RSA 才是典型陷门函数。


3.9 ECDHE 在 TLS 中为什么还需要签名#

如果只有 ECDHE,没有证书和签名,会出现中间人攻击。

中间人可以分别和客户端、服务器做两次 ECDHE:

客户端 ↔ 中间人
中间人 ↔ 服务器

客户端以为共享秘密是和服务器协商的,其实是和中间人协商的。

所以 TLS 里需要:

证书链:证明服务器长期公钥可信
CertificateVerify:服务器用长期私钥签名握手 transcript

这样服务器临时公钥 B 会被签名绑定到服务器身份上。


4. HKDF + HMAC + Transcript Hash:共享秘密如何派生成通信密钥#

HKDF 不是非对称算法,也不是陷门函数。

它是:

基于 HMAC 的密钥派生函数

在 TLS 1.3 中,它把 ECDHE 的原始共享秘密变成多把有明确用途的密钥。


4.1 HKDF 在 TLS 中负责什么#

ECDHE 产生的是:

shared_secret = S

这个值不能直接作为 AES-GCM 或 ChaCha20-Poly1305 的 key。

原因:

1. 原始共享秘密格式不适合直接当对称密钥
2. 握手阶段和应用数据阶段需要不同密钥
3. 客户端发送方向和服务器发送方向需要不同密钥
4. Finished、Record Encryption、Key Update 等用途需要隔离
5. 密钥应绑定到握手 transcript,防止上下文混淆

HKDF 负责:

提取 entropy
扩展为多把用途隔离的密钥
绑定上下文信息

4.2 HMAC 的基本结构#

HKDF 基于 HMAC。

HMAC 可以粗略理解为:

HMAC(K, m) = Hash((K XOR opad) || Hash((K XOR ipad) || m))

它把哈希函数变成带密钥的消息认证函数。

HMAC 的安全本质是:

不知道 K,就无法预测或伪造 HMAC 输出

HMAC 不是可逆加密,也不是陷门函数。

它是:

伪随机函数 / 消息认证码

4.3 HKDF-Extract:提纯#

HKDF 第一步:

PRK = HKDF-Extract(salt, IKM)

具体形式:

PRK = HMAC(salt, IKM)

其中:

IKM = input keying material,例如 ECDHE shared secret
salt = 盐值,TLS key schedule 中通常是前一级 secret
PRK = pseudo-random key

它的作用:

把可能不完全均匀的输入密钥材料提炼成伪随机密钥

可以理解成:

原始共享秘密 → 提纯后的密钥种子

4.4 HKDF-Expand:扩展#

第二步:

OKM = HKDF-Expand(PRK, info, L)

其中:

PRK:提纯后的伪随机密钥
info:上下文标签
L:输出长度
OKM:输出密钥材料

info 非常重要。

如果没有上下文标签,不同用途的密钥可能混用。

TLS 1.3 用:

HKDF-Expand-Label(secret, label, context, length)

实现强制域分离。

例如:

"c hs traffic":客户端握手流量密钥
"s hs traffic":服务器握手流量密钥
"c ap traffic":客户端应用数据密钥
"s ap traffic":服务器应用数据密钥
"finished":Finished 校验密钥
"key":实际 AEAD key
"iv":实际 AEAD IV

4.5 TLS 1.3 Key Schedule 细化#

简化但贴近标准的结构:

early_secret =
HKDF-Extract(0, PSK or 0)
derived_secret_1 =
Derive-Secret(early_secret, "derived", "")
handshake_secret =
HKDF-Extract(derived_secret_1, ECDHE_shared_secret)
client_handshake_traffic_secret =
Derive-Secret(handshake_secret, "c hs traffic", transcript_hash)
server_handshake_traffic_secret =
Derive-Secret(handshake_secret, "s hs traffic", transcript_hash)
derived_secret_2 =
Derive-Secret(handshake_secret, "derived", "")
master_secret =
HKDF-Extract(derived_secret_2, 0)
client_application_traffic_secret =
Derive-Secret(master_secret, "c ap traffic", transcript_hash)
server_application_traffic_secret =
Derive-Secret(master_secret, "s ap traffic", transcript_hash)

然后从 traffic secret 派生真正记录层使用的 key 和 IV:

write_key =
HKDF-Expand-Label(traffic_secret, "key", "", key_length)
write_iv =
HKDF-Expand-Label(traffic_secret, "iv", "", iv_length)

Finished key:

finished_key =
HKDF-Expand-Label(base_key, "finished", "", hash_length)

4.6 Transcript Hash 为什么关键#

TLS 1.3 派生 traffic secret 时,会把握手 transcript 的哈希放进去:

traffic_secret = Derive-Secret(secret, label, Hash(handshake_messages))

这意味着密钥不仅依赖:

ECDHE shared secret

还依赖:

双方实际看到的握手消息

如果中间人修改了:

ClientHello
ServerHello
Certificate
CertificateVerify

任意一项,双方 transcript_hash 就会不同,派生出的密钥或 Finished 校验就会失败。

因此 transcript_hash 把:

算法协商结果
临时公钥
服务器证书
服务器签名

绑定到同一个握手上下文里。


4.7 HKDF 的安全本质嵌入总结#

HKDF 不是靠“反向困难”来加密数据。

它的安全本质是:

如果输入 secret 有足够熵
如果 HMAC/SHA-256 具备伪随机性
那么攻击者无法预测派生出的密钥

它解决的是:

共享秘密如何变成多把上下文隔离的高质量密钥

而不是:

公钥如何解密
签名如何验签

5. X.509 / CA 证书链:服务器公钥如何变得可信#

证书链是 HTTPS 身份认证的核心。

它解决的问题不是:

如何加密数据

而是:

浏览器如何确认这个服务器公钥确实属于 www.example.com

5.1 证书不是“加密嵌套包”#

一个常见误区是:

根公钥解密外层证书
暴露中间层公钥
再解密内层服务器证书

这是不准确的。

证书链是:

签名链

不是:

嵌套加密链

每张证书本身都是可读结构,里面明文包含主体公钥:

中间 CA 公钥在中间 CA 证书里
服务器公钥在服务器证书里

但浏览器不会直接信任它们。

浏览器通过上一级公钥验签,确认这张证书没有被篡改且确实由上一级签发。


5.2 X.509 证书结构#

证书大体结构:

Certificate {
tbsCertificate
signatureAlgorithm
signatureValue
}

tbsCertificate 是 “to be signed certificate”,即被签名的主体内容。

常见字段:

version
serialNumber
issuer
validity
subject
subjectPublicKeyInfo
extensions

重要扩展:

Subject Alternative Name:域名列表
Basic Constraints:是否是 CA
Key Usage:digitalSignature、keyCertSign 等
Extended Key Usage:serverAuth 等
Subject Key Identifier
Authority Key Identifier
Name Constraints
Certificate Policies
CRL Distribution Points
Authority Information Access / OCSP

5.3 CA 签发服务器证书#

服务器先生成密钥对:

server_private_key
server_public_key

然后生成 CSR:

CSR {
域名
服务器公钥
申请者信息
申请者签名
}

CA 验证域名控制权,例如:

DNS TXT 验证
HTTP 文件验证
TLS-ALPN 验证

验证通过后,CA 构造服务器证书内容:

tbsCertificate {
subject = www.example.com
subjectPublicKeyInfo = server_public_key
issuer = Intermediate CA
validity = ...
extensions = SAN, KeyUsage, EKU, ...
}

CA 对 tbsCertificate 签名:

signatureValue = Sign(CA_private_key, Hash(tbsCertificate))

最终证书:

Certificate = tbsCertificate + signatureAlgorithm + signatureValue

5.4 浏览器逐层验证证书链#

假设证书链是:

Root CA
Intermediate CA
Server Certificate

浏览器本地信任 Root CA 公钥。

第一步,验证中间 CA 证书:

Verify(
publicKey = Root CA 公钥,
message = Intermediate.tbsCertificate,
signature = Intermediate.signatureValue
)

验签成功后:

Intermediate CA 证书内容可信
Intermediate CA 公钥可信

第二步,验证服务器证书:

Verify(
publicKey = Intermediate CA 公钥,
message = Server.tbsCertificate,
signature = Server.signatureValue
)

验签成功后:

Server 证书内容可信
Server 公钥可信

这里的安全本质是:

攻击者可以复制证书
但不能修改证书内容后重新生成合法 CA 签名
因为攻击者没有 CA 私钥

5.5 路径构建与路径验证#

浏览器并不是简单按服务器发来的顺序盲目验证。

它通常要先做路径构建:

Server Cert
← Intermediate CA Cert
← Root CA Trust Anchor

匹配依据包括:

Issuer
Subject
Authority Key Identifier
Subject Key Identifier

路径构建后,再做路径验证:

签名是否正确
有效期是否有效
用途是否允许
Basic Constraints 是否正确
Key Usage 是否允许签发
Name Constraints 是否满足
路径长度是否满足
是否吊销

5.6 域名匹配#

现代证书主要看:

Subject Alternative Name / SAN

例如:

DNS:www.example.com
DNS:*.example.com

如果用户访问:

https://www.example.com

则证书 SAN 必须匹配:

www.example.com

通配符匹配有边界:

*.example.com 可以匹配 a.example.com
通常不能匹配 a.b.example.com

如果证书链验签通过,但域名不匹配,浏览器仍然不能信任它。


5.7 Basic Constraints 与 Key Usage#

中间 CA 证书必须说明自己是 CA:

Basic Constraints: CA = TRUE

并且必须允许签发证书:

Key Usage: keyCertSign

服务器证书通常需要:

Key Usage: digitalSignature
Extended Key Usage: serverAuth

否则它不能被用作 TLS 服务器认证。


5.8 吊销检查#

证书可能在有效期内被撤销,例如:

私钥泄露
证书误签发
域名控制权变化
CA 发现错误

常见机制:

CRL:证书吊销列表
OCSP:在线证书状态协议
OCSP Stapling:服务器在握手中附带 OCSP 响应

现实浏览器对吊销检查有工程权衡,不同浏览器策略可能不同,但概念上吊销属于证书验证链的一部分。


5.9 证书链的安全本质嵌入总结#

证书链依赖底层签名算法的不可伪造性。

整体逻辑是:

Root CA 私钥签 Intermediate CA 证书
Intermediate CA 私钥签 Server 证书
Server 私钥签 TLS 握手 transcript

浏览器只需要持有公钥就能逐层验证:

Root CA 公钥验 Intermediate
Intermediate CA 公钥验 Server
Server 公钥验 CertificateVerify

攻击者失败的原因是:

没有 CA 私钥,不能伪造下级证书签名
没有 Server 私钥,不能伪造 CertificateVerify

这就是 HTTPS 身份认证链路的核心。


6. 数字签名体系:RSA-PSS、ECDSA、EdDSA#

数字签名在 HTTPS 中主要出现在两个地方:

1. CA 给证书签名
2. 服务器在 CertificateVerify 中给握手 transcript 签名

签名解决的问题:

身份认证
防篡改
不可伪造

签名不是为了隐藏内容。

证书内容、握手 transcript 的大部分结构并不是靠签名隐藏,而是靠签名保证:

这确实是某个私钥持有者签过的
内容没有被改过

6.1 RSA-PSS:基于 RSA 陷门函数的现代签名#

RSA-PSS 是现代推荐的 RSA 签名方案之一。

RSA 的核心是典型陷门函数:

正向容易:M → M^e mod n
反向困难:没有 d 很难从 C 求 M
陷门:p/q/φ(n)/d

RSA-PSS 把这个 RSA 陷门能力用于签名。


6.1.1 RSA 密钥生成#

选择两个大素数:

p, q

计算:

n = p × q
φ(n) = (p - 1)(q - 1)

选择公钥指数:

e

常见:

e = 65537

计算私钥指数:

d = e^{-1} mod φ(n)

也就是:

e × d ≡ 1 mod φ(n)

公钥:

(n, e)

私钥:

(n, d)

工程私钥通常还保存:

p, q, dp, dq, qInv

用于 CRT 加速。


6.1.2 RSA 为什么是陷门函数#

公开信息:

n, e

秘密信息:

p, q, φ(n), d

正向计算:

Y = X^e mod n

很容易,用快速模幂即可。

反向求解:

给定 Y,求 X

如果没有 d,就相当困难。

但如果知道 d

X = Y^d mod n

可以高效恢复。

为什么 d 难得到?

因为:

d = e^{-1} mod φ(n)

而:

φ(n) = (p - 1)(q - 1)

要知道 φ(n),通常需要知道 pq

而从:

n = p × q

反推出:

p, q

是大整数分解问题,在足够大参数下不可行。

所以 RSA 的陷门就是:

p/q 或 d

6.1.3 RSA 正确性公式#

RSA 的基本数学关系:

e × d ≡ 1 mod φ(n)

所以存在整数 k

ed = 1 + kφ(n)

对消息代表的整数 M

(M^e)^d
= M^(ed)
= M^(1 + kφ(n))
= M × (M^φ(n))^k

根据欧拉定理:

M^φ(n) ≡ 1 mod n

所以:

M^(ed) ≡ M mod n

这就是为什么:

公钥指数 e 做过的事情
私钥指数 d 能在模 n 世界里配套还原

签名方向也是同一个数学关系:

S = EM^d mod n
EM = S^e mod n

6.1.4 RSA-PSS 签名不是裸 RSA#

真实签名不能直接:

S = Hash(message)^d mod n

因为裸 RSA 具有代数结构,容易被构造攻击。

RSA-PSS 会先做安全编码:

输入:

message
hash algorithm,例如 SHA-256
salt
MGF1
RSA private key

步骤:

mHash = Hash(message)
salt = random bytes
M' = 0x00 00 00 00 00 00 00 00 || mHash || salt
H = Hash(M')
DB = PS || 0x01 || salt
dbMask = MGF1(H)
maskedDB = DB XOR dbMask
EM = maskedDB || H || 0xbc
S = EM^d mod n

验签:

EM = S^e mod n
检查:
1. 最后字节是否 0xbc
2. maskedDB 是否能正确恢复 DB
3. DB 中 PS || 0x01 || salt 格式是否正确
4. 用恢复出的 salt 和 message hash 重新计算 H
5. H 是否一致

6.1.5 RSA-PSS 的安全本质嵌入总结#

RSA-PSS 的安全来自两层:

1. RSA 陷门函数:
没有私钥 d,无法生成能被公钥 e 验证的 RSA 结果。
2. PSS 编码:
salt + Hash + MGF1 + 严格格式,让签名不再是裸 RSA 的简单代数结构。

它在 TLS 中常用于:

证书签名
CertificateVerify

6.2 ECDSA:基于椭圆曲线离散对数的签名#

ECDSA 是现代 TLS 证书和服务器签名中常见的算法之一。

它和 ECDHE 一样基于椭圆曲线离散对数困难。


6.2.1 ECDSA 的密钥关系#

公开基点:

G

曲线阶:

n

私钥:

d

公钥:

Q = dG

安全本质:

知道 d 和 G,算 Q = dG 容易
知道 Q 和 G,求 d 困难

这仍然是 ECDLP。


6.2.2 ECDSA 签名流程#

对消息:

message

先哈希:

z = Hash(message)

生成一次性随机数:

k

计算曲线点:

R = kG

取:

r = x_R mod n

如果 r = 0,重新选择 k

计算:

s = k^{-1}(z + r d) mod n

如果 s = 0,重新选择 k

签名:

(r, s)

6.2.3 ECDSA 验签流程#

输入:

message
signature = (r, s)
public key Q

先检查:

1 <= r <= n-1
1 <= s <= n-1

计算:

z = Hash(message)
w = s^{-1} mod n
u1 = z w mod n
u2 = r w mod n
R' = u1G + u2Q

验签条件:

r == x_{R'} mod n

6.2.4 ECDSA 为什么能验签#

签名时:

s = k^{-1}(z + rd) mod n

两边整理:

k = s^{-1}(z + rd) mod n

验签时:

w = s^{-1}
u1 = zw
u2 = rw

所以:

u1G + u2Q
= zwG + rwQ

因为:

Q = dG

所以:

u1G + u2Q
= zwG + rw(dG)
= w(z + rd)G
= s^{-1}(z + rd)G
= kG
= R

所以验签方重构出了签名时的 R,只需检查:

r 是否等于 R 的 x 坐标 mod n

6.2.5 ECDSA 为什么不泄露私钥#

验签方知道:

Q = dG
r
s
z

但不知道:

d
k

要从 Q = dGd,仍然是 ECDLP。

因此,验签可以确认代数关系成立,却不能推出私钥。


6.2.6 ECDSA 的关键风险:k 不能复用#

如果两个消息复用了同一个 k

s1 = k^{-1}(z1 + rd) mod n
s2 = k^{-1}(z2 + rd) mod n

相减:

s1 - s2 = k^{-1}(z1 - z2) mod n

推出:

k = (z1 - z2)(s1 - s2)^{-1} mod n

一旦得到 k,由:

s = k^{-1}(z + rd)

可得:

sk = z + rd

所以:

d = (sk - z)r^{-1} mod n

这意味着:

ECDSA nonce k 一旦复用或泄露,私钥 d 可能直接暴露。

这也是为什么 ECDSA 实现必须极其重视随机数或确定性 nonce。


6.2.7 ECDSA 的安全本质嵌入总结#

ECDSA 的安全来自:

1. 私钥 d 到公钥 Q = dG 正向容易
2. 公钥 Q 到私钥 d 反向困难
3. 签名公式把 d 隐藏在模运算关系中
4. 验签方能验证关系成立,但不能从关系中求出 d
5. k 必须安全,否则私钥可能泄露

6.3 EdDSA / Ed25519:更现代的 Edwards 曲线签名#

EdDSA 是 Edwards-curve Digital Signature Algorithm,常见实例是 Ed25519。

它也基于椭圆曲线离散对数困难,但相比 ECDSA,在工程实现上更不容易踩随机数坑。


6.3.1 EdDSA 密钥关系#

基点:

B

私钥派生出的标量:

a

公钥:

A = aB

安全本质仍然是:

知道 a 和 B,算 A = aB 容易
知道 A 和 B,求 a 困难

6.3.2 EdDSA 签名高层流程#

EdDSA 的具体细节较多,这里给出核心逻辑。

对消息:

message

生成确定性 nonce:

r = Hash(prefix || message)

计算:

R = rB

计算挑战:

h = Hash(R || A || message)

计算:

S = r + h a mod L

签名:

signature = (R, S)

6.3.3 EdDSA 验签关系#

验签检查:

SB == R + hA

为什么成立?

因为:

S = r + ha

所以:

SB
= (r + ha)B
= rB + h(aB)
= R + hA

验证者知道:

B
A
R
S
message

可以计算:

h = Hash(R || A || message)

并检查:

SB 是否等于 R + hA

但不能从:

A = aB

反推出:

a

因为这仍然是椭圆曲线离散对数问题。


6.3.4 EdDSA 的工程优势#

EdDSA 常见优势:

签名 nonce 通常确定性生成
减少传统 ECDSA 随机 k 出错风险
性能好
实现相对规整
适合现代协议生态

但在公开 Web PKI/TLS 证书生态中,RSA 和 ECDSA 仍然非常常见;Ed25519 的实际可用性取决于 CA、浏览器、TLS 栈支持。


7. AEAD 记录层加密:AES-GCM 与 ChaCha20-Poly1305#

TLS 握手完成后,真正的 HTTP 数据不再用非对称算法处理,而是用对称 AEAD。

AEAD 是:

Authenticated Encryption with Associated Data

它同时提供:

保密性
完整性
认证性

7.1 AEAD 在 TLS 中负责什么#

TLS 应用数据加密输入:

plaintext:HTTP 数据
key:application write key
nonce:每条记录唯一的 nonce
aad:TLS record header 等关联数据

输出:

ciphertext
tag

接收方:

用同样 key 和 nonce 验证 tag
tag 正确才释放 plaintext
tag 错误直接失败

AEAD 的核心工程原则:

1. 同一个 key 下 nonce 绝不能重复
2. 必须先验证 tag,再使用 plaintext
3. AAD 虽不加密,但被认证

7.2 TLS 1.3 记录 nonce 构造#

TLS 1.3 通常不是每条记录随机生成 nonce。

它使用:

per_record_nonce = static_write_iv XOR padded_sequence_number

其中:

static_write_iv:HKDF 派生
sequence_number:每个方向从 0 递增

这样保证:

同一个连接、同一个方向、同一个 key 下,nonce 不重复

7.3 AES-GCM:AES-CTR 加密 + GHASH 认证#

AES-GCM 是现代 TLS 最常见 AEAD 之一。

它由两部分组成:

AES-CTR:负责加密
GHASH:负责认证

7.3.1 AES 是什么#

AES 是对称分组密码。

NIST FIPS 197 定义了:

AES-128
AES-192
AES-256

共同点:

分组大小:128 bit
密钥长度:128 / 192 / 256 bit
轮数:10 / 12 / 14

AES 对一个 128-bit block 做多轮变换:

SubBytes
ShiftRows
MixColumns
AddRoundKey

其中:

SubBytes:提供非线性
ShiftRows:改变字节位置
MixColumns:扩散列内影响
AddRoundKey:混入轮密钥

AES 的安全本质不是“单向陷门函数”。

它是:

密钥控制的伪随机置换

知道 key:

可以加密
可以解密

不知道 key:

无法预测输出
无法反推出明文

7.3.2 AES-CTR 如何加密#

GCM 中的加密部分本质是 CTR。

给定:

key K
nonce
counter

生成计数器块:

counter_1
counter_2
counter_3
...

计算密钥流:

stream_1 = AES_K(counter_1)
stream_2 = AES_K(counter_2)
stream_3 = AES_K(counter_3)

明文分块:

P1, P2, P3

加密:

C1 = P1 XOR stream_1
C2 = P2 XOR stream_2
C3 = P3 XOR stream_3

解密:

P1 = C1 XOR stream_1

因为:

X XOR Y XOR Y = X

7.3.3 GHASH 如何认证#

GCM 的认证部分 GHASH 在有限域:

GF(2^128)

上计算。

先生成认证子密钥:

H = AES_K(0^128)

然后对:

AAD
ciphertext
长度信息

做有限域乘法累积:

GHASH_H(AAD, ciphertext)

最终 tag 概念上是:

tag = AES_K(J0) XOR GHASH_H(AAD, ciphertext)

如果攻击者修改:

AAD
ciphertext
length
tag

接收方重新计算 tag 会不一致。


7.3.4 AES-GCM 为什么 nonce 不能复用#

如果同一个 key 下复用 nonce,CTR 会生成相同密钥流:

C1 = P1 XOR KS
C2 = P2 XOR KS

两者 XOR:

C1 XOR C2 = P1 XOR P2

这会泄露明文关系。

对 GCM 来说,nonce 复用还可能破坏认证安全,导致 tag 可伪造风险。

所以 TLS 通过 sequence number 构造 nonce,避免重复。


7.3.5 AES-GCM 的安全本质嵌入总结#

AES-GCM 的安全来自:

1. AES 在未知 key 下像伪随机置换
2. CTR 生成不可预测密钥流
3. GHASH 绑定 AAD、ciphertext 和长度
4. tag 阻止篡改
5. nonce 不复用是必要条件

它不是公钥陷门函数,而是:

对称密钥控制的认证加密

7.4 ChaCha20-Poly1305:ARX 流加密 + Poly1305 认证#

ChaCha20-Poly1305 是 TLS 1.3 的另一种主流 AEAD。

它特别适合没有 AES 硬件加速的环境。


7.4.1 ChaCha20 输入#

ChaCha20 输入:

256-bit key
96-bit nonce
32-bit counter

构造 4×4 的 32-bit word 状态矩阵:

constant | constant | constant | constant
key | key | key | key
key | key | key | key
counter | nonce | nonce | nonce

7.4.2 Quarter Round#

ChaCha20 的核心是 quarter round,对四个 32-bit word:

a, b, c, d

执行:

a += b; d ^= a; d <<<= 16;
c += d; b ^= c; b <<<= 12;
a += b; d ^= a; d <<<= 8;
c += d; b ^= c; b <<<= 7;

其中:

+=:32-bit 加法
^=:XOR
<<<=:循环左移

ChaCha20 属于 ARX 结构:

Addition
Rotation
XOR

它通过多轮 ARX 混合实现扩散和伪随机性。


7.4.3 ChaCha20 如何加密#

ChaCha20 经过 20 轮后输出密钥流块:

keystream_block = ChaCha20_Block(key, nonce, counter)

加密:

ciphertext = plaintext XOR keystream

解密:

plaintext = ciphertext XOR keystream

不知道 key,就无法生成同样的 keystream。


7.4.4 Poly1305 如何认证#

Poly1305 是一次性消息认证码。

在 ChaCha20-Poly1305 中:

poly1305_key = ChaCha20_Block(key, nonce, counter=0) 的前 256 bit

然后用这个 one-time key 对:

AAD
ciphertext
lengths

计算 tag。

发送:

ciphertext || tag

接收方重新计算 tag,只有 tag 一致才接受。


7.4.5 ChaCha20-Poly1305 为什么 nonce 也不能复用#

如果同一 key 下 nonce 复用,ChaCha20 会生成相同密钥流:

C1 = P1 XOR KS
C2 = P2 XOR KS

所以:

C1 XOR C2 = P1 XOR P2

这直接泄露明文关系。

同时 Poly1305 one-time key 也会复用,破坏认证安全。


7.4.6 ChaCha20-Poly1305 的安全本质嵌入总结#

ChaCha20-Poly1305 的安全来自:

1. ChaCha20 在未知 key 下输出不可预测 keystream
2. Poly1305 在 one-time key 下提供消息认证
3. nonce 不复用保证 keystream 和 one-time MAC key 不重复
4. tag 防止密文和 AAD 被篡改

它也是:

对称 AEAD

不是非对称陷门函数。


8. RSA-OAEP:现代可用的公钥加密方案,但不是 TLS 1.3 主线#

RSA-OAEP 仍是现代安全的 RSA 公钥加密方案之一。

但它不是 TLS 1.3 中协商会话密钥的主线。

TLS 1.3 主线是:

ECDHE + HKDF

RSA-OAEP 可作为理解 RSA 公钥加密/私钥解密的现代方案,也可用于某些应用层密钥封装场景。


8.1 RSA-OAEP 解决什么问题#

它解决:

发送方想把一个短明文 K 安全发给持有 RSA 私钥的一方

例如:

K = 随机对称密钥

公钥加密:

C = Encrypt(publicKey, K)

私钥解密:

K = Decrypt(privateKey, C)

8.2 RSA-OAEP 中的陷门原理#

RSA 公钥:

(n, e)

RSA 私钥:

(n, d)

加密核心:

C = M^e mod n

解密核心:

M = C^d mod n

陷门是:

d

d 的来源是:

p, q → φ(n) → d

公开 ne 不足以高效得到 d,因为需要分解:

n = p × q

这就是 RSA 的单向陷门本质。


8.3 为什么需要 OAEP,不直接加密 K#

裸 RSA 是确定性的。

如果直接:

C = K^e mod n

同一个 K 每次加密结果相同。

这会带来安全问题。

OAEP 会先把 K 编码成带随机性的块 M

K + seed + padding + hash → M

然后 RSA 加密的是:

M

而不是裸 K


8.4 OAEP 编码结构#

OAEP 大致流程:

DB = lHash || PS || 0x01 || K
seed = random bytes
dbMask = MGF(seed)
maskedDB = DB XOR dbMask
seedMask = MGF(maskedDB)
maskedSeed = seed XOR seedMask
M = 0x00 || maskedSeed || maskedDB

加密:

C = M^e mod n

解密:

M = C^d mod n

然后反向恢复:

maskedSeed, maskedDB
seed
DB
K

8.5 OAEP 的安全本质嵌入总结#

RSA-OAEP 的安全来自:

1. RSA 陷门函数:
无 d 难以从 C 恢复 M。
2. OAEP 随机编码:
同一个 K 每次生成不同 M,避免裸 RSA 确定性问题。
3. Hash + MGF:
让编码块结构可校验,防止构造攻击。

它仍是现代可用的 RSA 加密方案,但在现代 HTTPS/TLS 1.3 里,它不是密钥交换主线。


9. 现代推荐与弃用技术边界#


9.1 当前主讲/推荐关注#

模块当前主流/推荐
协议版本TLS 1.3,兼容 TLS 1.2 安全套件
密钥协商ECDHE,X25519、P-256、P-384
密钥派生HKDF + SHA-256/SHA-384
对称 AEADAES-GCM、ChaCha20-Poly1305
签名RSA-PSS、ECDSA、EdDSA/Ed25519 视生态支持
证书X.509、SAN、CA 证书链、OCSP/CRL
哈希SHA-256、SHA-384

9.2 不推荐/弃用/不作为主线#

技术状态原因
SSLv2 / SSLv3废弃存在严重协议安全问题
TLS 1.0 / TLS 1.1废弃已被正式弃用,缺少现代算法机制
RSA Key ExchangeTLS 1.3 移除不具备前向安全性
静态 DH / 静态 ECDH不推荐不具备临时密钥带来的前向安全性
RC4禁用存在严重偏差和攻击
3DES淘汰64-bit block 风险,性能和安全性落后
CBC TLS 套件不推荐历史上多次 padding oracle 类攻击
MD5 / SHA-1 签名不推荐碰撞风险
DSA基本退出主流 Web TLS现代生态由 RSA/ECDSA/EdDSA 主导
EXPORT 套件禁用人为弱化加密,历史攻击严重
TLS 压缩不推荐CRIME 类压缩侧信道风险

10. 最终总结#

现代 HTTPS 的安全不是靠某一个算法,而是一整套密码学结构:

ECDHE:
正向 a → aG 容易
反向 aG → a 困难
双方利用 a(bG)=b(aG)=abG 协商共享秘密
证书链:
上级 CA 私钥签下级证书
浏览器用上级公钥逐层验签
最终确认服务器公钥属于目标域名
CertificateVerify:
服务器用证书对应私钥签握手 transcript
浏览器用服务器公钥验签
防止中间人替换 ECDHE 参数
HKDF:
从 ECDHE shared secret 和 transcript_hash 派生多把隔离密钥
把密钥和握手上下文绑定
AES-GCM / ChaCha20-Poly1305:
用对称密钥加密 HTTP 数据
用 tag 防篡改
nonce 不能复用
RSA-PSS / ECDSA / EdDSA:
用于证书签名和服务器签名
依赖 RSA 陷门或椭圆曲线离散对数困难

一句话:

现代 HTTPS = 椭圆曲线单向困难问题协商密钥 + CA 签名链认证身份 + HKDF 派生密钥 + AEAD 保护数据。

再压缩一点:

ECDHE 解决“怎么安全得到同一个秘密”
证书链解决“这个服务器公钥是不是真的”
签名解决“服务器是否真的持有私钥”
HKDF 解决“怎么从秘密变出多把密钥”
AEAD 解决“后续数据怎么加密且防篡改”

11. 参考文献#

  1. Rescorla, E. RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3. IETF, 2018.
    https://datatracker.ietf.org/doc/html/rfc8446

  2. Sheffer, Y., Saint-Andre, P., Fossati, T. RFC 9325: Recommendations for Secure Use of Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS). IETF, 2022.
    https://datatracker.ietf.org/doc/html/rfc9325

  3. Langley, A., Hamburg, M., Turner, S. RFC 7748: Elliptic Curves for Security. IETF, 2016.
    https://datatracker.ietf.org/doc/html/rfc7748

  4. Barker, E. et al. NIST SP 800-56A Rev. 3: Recommendation for Pair-Wise Key-Establishment Schemes Using Discrete Logarithm Cryptography. NIST, 2018.
    https://csrc.nist.gov/pubs/sp/800/56/a/r3/final

  5. National Institute of Standards and Technology. FIPS 186-5: Digital Signature Standard (DSS). NIST, 2023.
    https://csrc.nist.gov/pubs/fips/186-5/final

  6. Cooper, D. et al. RFC 5280: Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List Profile. IETF, 2008.
    https://datatracker.ietf.org/doc/html/rfc5280

  7. Moriarty, K. et al. RFC 8017: PKCS #1: RSA Cryptography Specifications Version 2.2. IETF, 2016.
    https://datatracker.ietf.org/doc/html/rfc8017

  8. Krawczyk, H., Eronen, P. RFC 5869: HMAC-based Extract-and-Expand Key Derivation Function (HKDF). IETF, 2010.
    https://datatracker.ietf.org/doc/html/rfc5869

  9. National Institute of Standards and Technology. FIPS 197: Advanced Encryption Standard (AES). NIST, updated 2023.
    https://csrc.nist.gov/pubs/fips/197/final

  10. McGrew, D. RFC 5116: An Interface and Algorithms for Authenticated Encryption. IETF, 2008.
    https://datatracker.ietf.org/doc/html/rfc5116

  11. Nir, Y., Langley, A. RFC 8439: ChaCha20 and Poly1305 for IETF Protocols. IETF, 2018.
    https://datatracker.ietf.org/doc/html/rfc8439

  12. Josefsson, S., Liusvaara, I. RFC 8032: Edwards-Curve Digital Signature Algorithm (EdDSA). IETF, 2017.
    https://datatracker.ietf.org/doc/html/rfc8032

  13. Turner, S., Polk, T. RFC 8996: Deprecating TLS 1.0 and TLS 1.1. IETF, 2021.
    https://datatracker.ietf.org/doc/html/rfc8996

  14. CA/Browser Forum. Baseline Requirements for the Issuance and Management of Publicly-Trusted TLS Server Certificates.
    https://cabforum.org/working-groups/server/baseline-requirements/requirements/

HTTPS/TLS 加密体系技术文档:从握手链路到算法安全本质
https://jupiter-ws.cn/posts/cs-basics/https-tls-encryption/
作者
Jupiter
发布于
2026-07-02
许可协议
CC BY-NC-SA 4.0