HTTPS 握手:在不安全的网络上安全协商出密钥
一句话结论(30s)
HTTPS 安全的核心不是「加密算法有多强」,而是握手阶段用非对称加密(证书 + ECDHE 临时密钥对)在不安全的网络上安全协商出一个对称会话密钥,之后再用对称加密高速传输数据——因为对称加密快但不安全、非对称加密安全但慢,两者配合才能在安全与性能间取得平衡;而 ECDHE 的临时私钥用完即丢,即使服务器主私钥泄露也无法解密历史会话,这就是前向安全。
核心原理(2min)
- TLS 1.2(2-RTT):客户端 random1 + 服务端 random2 + pre-master 三个随机数,经 PRF 派生出 master_secret 会话密钥,缺一不可。
- 证书链验证:服务器证书 → 中间 CA → 根 CA(浏览器内置的信任锚点),逐级验签名/有效期/域名(CN/SAN)。
- 前向安全:ECDHE 的 E=Ephemeral 临时密钥对每次握手重新生成、用完即弃。
- TLS 1.3(1-RTT):砍掉 RSA,ECDHE 参数在 Client Hello 直接发送,强制前向安全,支持 PSK 0-RTT。
- 中间人无效:攻击者无合法 CA 签发证书,证书链验证必然失败。
思考:为什么非对称加密只用来「交换对称密钥」,而不全程加密数据?因为两者的性能差几个数量级——非对称加密(RSA/ECDH)靠大数运算,慢,但能在不安全网络上安全协商出共享秘密;对称加密(AES)快,但要求双方先有一把相同的密钥。所以 TLS 的聪明之处在于各取所长:用慢的非对称加密完成「密钥协商」这一小步,再用快的对称加密跑「数据加密」这一大步。
底层深入(5-10min)
TLS 1.2 握手(4 次,2-RTT)
客户端 服务端
| |
|── Client Hello ────────────────────────→| (version, cipher suites, random1)
| (支持的加密套件 + 客户端随机数) |
| |
|←── Server Hello ────────────────────────┤ (chosen cipher, random2)
| + Certificate | (证书链)
| + Server Key Exchange (ECDHE key) | (ECDHE临时公钥)
| + Server Hello Done |
| |
|── Client Key Exchange ────────────────→| (ECDHE临时公钥 + pre-master用服务端公钥加密)
| + Change Cipher Spec | (通知:接下来用协商好的密钥加密)
| + Finished (加密) |
| |
|←── Change Cipher Spec ─────────────────┤
| + Finished (加密) |
| |
双方开始加密通信
密钥协商原理
最终的会话密钥由三个随机数生成:
master_secret = PRF(pre_master_secret, "master secret",
random1 + random2)
会话密钥由 master_secret 派生,不再依赖任何一方的单一随机数。
三个随机数缺一不可:即使攻击者破解了服务器私钥获得了 pre_master_secret,没有客户端的 random1 和服务端的 random2(每次握手重新生成),也无法计算历史会话的密钥——这就是前向安全性(Forward Secrecy)。
思考:为什么要用三个随机数,而不是一个?因为单一随机数意味着「单点依赖」——只要那一方被攻破或随机数可预测,密钥就泄露。三个随机数(client + server + pre-master)让 master_secret 同时绑定双方的随机性,任何一方都无法单独决定最终密钥;这也解释了为什么握手要来回交互——双方都要「贡献」自己的随机部分。
ECDHE 的前向安全
ECDHE 中的 E 是 Ephemeral(临时的)。服务端每次握手都生成新的临时密钥对,私钥用完即丢弃,不存盘。即使服务器主私钥泄露,恢复之前的会话密钥也永远不可能——因为临时私钥已经不存在了。
思考:为什么「临时私钥用完即丢」就实现了前向安全?因为前向安全的定义是「历史会话不因长期密钥泄露而被解密」。ECDHE 每次握手生成全新的临时密钥对,加密会话密钥用的是这把临时密钥而非服务器主私钥;临时私钥不落盘、用完即销毁,事后即便主私钥泄露,攻击者也拿不到那把已经消失的临时私钥,自然解不开历史流量。
TLS 1.3 优化:1-RTT
TLS 1.3 砍掉了很多东西:
- 移除旧版不安全加密套件(RSA密钥交换,无前向安全)
- 将所有支持的 ECDHE 曲线 + 参数在 Client Hello 中直接发送,服务端在 Server Hello 中直接返回选中的参数 → 握手从 2-RTT 降到 1-RTT
TLS 1.3:
Client: Client Hello + ECDHE params + cipher suites
Server: Server Hello + ECDHE params + Certificate + Finished
Client: Finished
→ 1-RTT
更进一步,如果客户端之前连接过该服务器(PSK,Pre-Shared Key),可以做到 0-RTT——客户端在第一条消息中直接发送加密的应用数据。
思考:TLS 1.3 凭什么从 2-RTT 砍到 1-RTT?因为它删掉了导致多一轮往返的元凶——RSA 密钥交换(要等服务端公钥到齐才能加密 pre-master)。TLS 1.3 干脆只保留 ECDHE,并把 ECDHE 参数在 Client Hello 里直接发给服务端,服务端一次 Server Hello 就能回全,于是少了一轮往返。代价是牺牲了旧算法兼容,换来更快的握手和强制前向安全。
证书链验证
浏览器收到服务器证书后,沿证书链逐级验证:
服务器证书 → 中间CA证书 → 根CA证书(浏览器内置)
每一步验证:
- 上级证书的签发者 DN 等于本级证书的持有者 DN
- 数字签名验证通过(用上级证书的公钥验证本级证书的签名)
- 证书未过期、未被吊销(CRL/OCSP)
- 域名匹配(CN 或 SAN 包含访问域名)
根 CA 证书是”信任锚点”——浏览器/操作系统出厂内置约 150 个根证书,是整个 HTTPS 信任体系的基石。
中间人攻击为什么对 HTTPS 无效
攻击者在客户端和服务端之间,拦截 Client Hello,用自己的假证书回复。但客户端会验证证书链——攻击者没有合法 CA 签发的证书,证书链验证必然失败,浏览器显示警告。
只有两种办法绕过证书验证:
- 攻击者获得了某个根 CA 的私钥签发假证书(历史上发生过多次 CA 被攻破事件,因此有了 Certificate Transparency)
- 用户在浏览器中手动信任了攻击者的自签名证书
思考:为什么中间人攻击对 HTTPS 无效,根源在哪?根源在信任锚点(根 CA)——客户端信任的是浏览器内置的根证书,而不是「谁说的都信」。攻击者能伪造假证书,却造不出「由根 CA 签名」的合法证书,证书链验证到根就会失败。所以 HTTPS 的安全本质是「把信任集中到少数根 CA 身上」,这也解释了为什么根 CA 一旦被攻破就是灾难。
总结
| TLS 1.2 | TLS 1.3 | |
|---|---|---|
| 握手 RTT | 2-RTT | 1-RTT (0-RTT PSK) |
| 密钥交换 | RSA 或 ECDHE | 仅 ECDHE |
| 前向安全 | ECDHE 有,RSA 无 | 强制前向安全 |
| 加密套件 | 数百种 | 5 种(精简化) |
章末提问
Q1:HTTPS 为什么用非对称加密只交换对称密钥,而不是全程非对称?
结论先行:因为非对称加密太慢,只能承担「密钥协商」这一步;对称加密快,负责「数据加密」这一步。因为 RSA/ECDH 基于大数运算,计算量是 AES 等对称算法的成百上千倍,全程非对称会拖垮吞吐;所以 TLS 的设计是「非对称保安全、对称保速度」,两者取长补短。
Q2:什么是前向安全?ECDHE 怎么实现?
结论先行:前向安全 = 服务器长期私钥泄露后,历史会话仍无法被解密;ECDHE 通过「每次握手生成临时密钥对、用完即丢」实现。因为会话密钥的加密材料来自临时密钥对而非主私钥,临时私钥不落盘、握手结束即销毁;攻击者事后只拿到主私钥、拿不到已销毁的临时私钥,因此解不开历史流量。
Q3:三个随机数(random1 / random2 / pre-master)为什么缺一不可?
结论先行:因为三个随机数让最终密钥同时绑定客户端、服务端和 pre-master 三方随机性,避免任何单一随机源成为单点。因为只要少一个,剩下的随机数来源就可能被预测或被单方控制,master_secret 就失去了「双方共同贡献」的性质;三方各贡献一部分,才能保证任何一方都无法单独决定会话密钥。