TLS 与安全 入门

curl https://api.example.com/user/42 的 TCP 握手刚一完成, 客户端和服务器立刻跑第二次握手 —— TLS 1.3 握手 —— 来商定密码套件、 交换密钥、证明服务器身份、把连接切到加密记录。然后真正的 HTTP 请求才上线。 4 个 section 把画面搭起来:为什么有 TLS(机密性、完整性、真实性);密码学积木(对称、非对称、AEAD、ECDHE、HKDF);可交互的 TLS 1.3 握手走读; 以及 PKI —— 证书、链、CA、mTLS。最后是速查表

01

为什么有 TLS —— 机密性、完整性、真实性

没 TLS,curl 到服务器路径上的任何人 —— 咖啡店 AP、楼里的路由器、 每个 ISP 跳点、机房交换机 —— 都能读和改每个字节。 TLS 一次性解 3 个问题,把它们搞混是最常见的安全错误。

2026 年 curl https://api.example.com/user/42 跑的时候, 从你笔记本出去的字节对任何抓线的人来说都是噪声。把 HTTPS 拔了 —— 改成普通http:// —— 同一个请求就变成明文抓包:URL、headers、cookies、 JSON body、响应。TLS 就是为防这个存在,但「加密」这个框架把它干的事说小了。

3 个保证

  • 机密性 —— 加密对路径上任何被动观察者藏住 payload。 每连接一把密钥的 AES-128-GCM 意味着从线上抓字节的攻击者拿到的密文 在计算意义上与随机不可区分。
  • 完整性 —— 主动攻击者不能在传输中翻转 bit 而不被发现。 AEAD(Authenticated Encryption with Associated Data)在密文边上产出每记录的 MAC; 翻一个 bit 解密就拒绝整条记录。这就是干掉敌对网络上「往响应里注入脚本」攻击的东西。
  • 真实性 —— 客户端知道自己实际是在跟 api.example.com说话,不是冒充它的中间人。这是证书链的活:服务器证明自己持有客户端已经信任的某根 CA (直接或经中间证书)签过、且写着 api.example.com 的那张证书的私钥。

任何一个掉了,其他两个就意义不大。没完整性的机密性让攻击者改写加密字节去破坏明文 (padding-oracle 攻击的基础)。机密性 + 完整性没真实性让中间人能跟自己建一条完全加密的通道、 解密、转发、重加密 —— 「完美」加密给了错的端点。

TLS 在栈里的位置

TLS 层在 TCP 之上(或者 QUIC 里,等价物焊进传输层本身)。TCP 三次握手之后, 紧接着的包就是 TLS 握手,然后加密记录承载应用协议要送的任何东西 —— HTTP、IMAP、SMTP、AMQP、MQTT、gRPC。TLS 不知道也不在乎里面是什么; 它就是把双向字节流包进加密、完整性保护的记录里。

+-----------------------------+
| HTTP / IMAP / AMQP / gRPC   |  应用协议(在这层明文)
+-----------------------------+
| TLS record layer            |  5 字节头 + AEAD 加密 payload
+-----------------------------+
| TCP                         |  按序字节流
+-----------------------------+
| IP                          |  尽力而为的包
+-----------------------------+

记录层是 TLS 的下半:握手定好会话密钥后, 应用发的每个字节都被切成 ≤16 KB 的记录,小头(type + version + length)+ AEAD 加密 payload。序列号每连接隐式持有、喂进 nonce, 所以同一条记录发两次密文不同,接收方能发现重放。

「TLS 在哪里终止?」

终止 = 字节在哪解密。典型生产配置里,api.example.com解析到一个负载均衡器(Cloudflare、AWS ALB、Envoy 队列), 持有证书、跑 TLS 握手、解密请求,然后用以下 3 种协议之一跟实际应用服务器说话:

  1. 私网上明文 HTTP —— VPC 内常见;靠网络级隔离做安全。 一个错路由的包或一个被攻破的主机,你就全输了。
  2. 用宽松证书的重加密 TLS —— 内部 CA、长期证书。 LB 重加密到后端,所以字节在网上从不明文。
  3. 带服务身份的 mTLS —— 两侧通过证书互相认证;Section 04 讲。 现代 service mesh 的默认。

知道终止点重要,因为那是证书配的地方、TLS 版本策略强制的地方、 以及大多数 TLS 相关故障住的地方(「LB 上证书过期」是反复出现的事故主题)。

要点。「TLS 给 3 个保证:机密性(AEAD 加密藏住 payload)、 完整性(AEAD MAC 检测任何在途篡改)、真实性(证书链证明你在跟命名主机说话, 不是中间人)。记录层把任何 TCP 字节流 —— HTTP、IMAP、gRPC —— 包进 ≤16 KB 的加密记录。 TLS 通常在负载均衡器终止;后端流量根据信任模型是明文 HTTP、重加密 TLS 或 mTLS。」

02

密码学积木 —— 对称、非对称、AEAD、ECDHE

4 个想法,按序:对称密码(便宜、两侧要同一把密钥);非对称(贵、不需要共享秘密); AEAD(加密 + 完整性一个原语);密钥交换(在窃听者看不见的情况下你怎么拿到对称密钥)。 TLS 1.3 每样挑一个。

对称密码 —— 快速批量加密

对称密码用同一把密钥加密和解密。AES 是主力:128 位或 256 位密钥, 每个现代 CPU 都硬件加速(Intel AES-NI、ARM 密码扩展),每核轻松 5+ GB/s 吞吐。 TLS 1.3 的密码套件:

  • TLS_AES_128_GCM_SHA256 —— AES-128 GCM 模式、HKDF 用 SHA-256。 带密码扩展的 x86 / ARM 上几乎到处的默认。
  • TLS_AES_256_GCM_SHA384 —— AES-256、同形状。 某些合规体系要;比 AES-128 慢 ~30%,实际安全提升为零。
  • TLS_CHACHA20_POLY1305_SHA256 —— ChaCha20 流密码 + Poly1305 认证器。 没 AES-NI 的 CPU(大多数旧手机、一些嵌入式)上比 AES 快。手机客户端经常偏好。

基本就是全名单。TLS 1.2 有 ~300 个密码套件;TLS 1.3 砍到一小撮经过审计的组合, 全部锁定 AEAD + ECDHE + HKDF。

非对称密码 —— 慢、公钥

非对称方案有密钥对:任何人都可以知道的公钥、只有主人有的私钥。 一个做的操作另一个撤销。用于签名(私钥签、公钥验)和密钥协商。它们比对称慢 ~1000 倍:

  • RSA-2048 —— 证书签名和老部署里仍常见。RSA-1024 原则上破了(2030 左右)。 大多数情况下 RSA-4096 是杀鸡用牛刀。
  • ECDSA P-256 —— 椭圆曲线签名,256 位密钥下大约 RSA-3000 位的安全。 小证书、快速验证,新服务器证书的现代默认。
  • Ed25519 —— Edwards 曲线签名,还要更快,没参数歧义、没 nonce 重用陷阱。 SSH host key 用,一些根 CA 在采用。
  • X25519 —— TLS 1.3 密钥交换用的椭圆曲线 Diffie-Hellman 变体 (注意:签名是 Ed25519、密钥交换是 X25519 —— 不同曲线)。

非对称操作只在握手时用 —— 每连接一次 RSA 解密或一次 ECDSA 签。 共享秘密一旦建好,之后每个字节都是对称的。

AEAD —— 加密 + 完整性一个原语

AEAD 之前的构造把加密和完整性分开做:encrypt-then-MAC、MAC-then-encrypt、encrypt-and-MAC。 实现不小心 3 种都有讨厌的失败模式:

# AEAD 之前启用的漏洞:
BEAST(2011)    —— TLS 1.0 CBC 模式 IV 可预测 → 恢复 cookie
POODLE(2014)   —— SSLv3 padding oracle;降级攻击暴露数据
LUCKY13(2013)  —— MAC-then-encrypt CBC 里的时序侧信道
CRIME(2012)    —— TLS 压缩 + 选择明文 → 恢复 headers

AEAD 把加密和完整性打包到一个原语里:一次调用接 (key、nonce、plaintext、associated data)、产出(ciphertext、tag)。 解密接(key、nonce、ciphertext、tag、associated data)、要么返回明文要么失败。 没「解密然后查 MAC」的序列可以搞砸。AES-GCM 和 ChaCha20-Poly1305 都是 AEAD。 TLS 1.3 把 AEAD 定为强制。

密钥交换 —— 对称密钥怎么诞生

两侧需要同一对称密钥,窃听者必须不能从线上看到的任何东西推出它。3 个历史方法:

  • 静态 RSA 密钥交换(TLS 1.2 及更早,1.3 砍掉): 客户端挑一个随机 pre-master secret、用证书里服务器长期 RSA 公钥加密、送过去。 服务器用 RSA 私钥解密。没前向保密:任何之后拿到服务器 RSA 私钥的人, 都能解密用那张证书录下来的每次 TLS 会话。政府对手积极囤积加密流量正是为这个 —— 「现在收割、以后解密」。
  • DHE —— Diffie-Hellman Ephemeral:两侧每连接挑一个随机秘密、 交换公开值、算共享秘密。服务器的长期密钥签名握手,但从不当作密钥交换密钥用 —— 所以之后偷它不能解密过去流量。前向保密。数学用大素数(2048+ 位),所以慢。 实现用太小素数有 Logjam 攻击漏洞。
  • ECDHE —— 椭圆曲线 Diffie-Hellman Ephemeral:同想法,但在椭圆曲线上。 ~256 位密钥以远低代价给出等效于 3000 位 DH 的强度。TLS 1.3 强制 ECDHE; X25519 是通用默认曲线。

前向保密是关键属性:今天录密文、之后拿到服务器证书私钥的攻击者, 仍然不能解密录下的会话,因为临时密钥从未在任何地方写下来。 没前向保密,加密流量的耐久性只跟长期证书密钥的保密性一样。

HKDF —— 一个秘密变多个

ECDHE 共享秘密是 32 字节。TLS 1.3 需要很多密钥:握手加密密钥(server-write、client-write)、 应用流量密钥(server-write、client-write)、exporter secret(给 token binding 等)、 resumption secret(下一会话的 PSK)。HKDF(HMAC-based Key Derivation Function) 是标准原语:HKDF-Extract(salt, secret) 把熵压成固定大小 PRK、 然后 HKDF-Expand(PRK, info, len) 能被不同 info 字符串调用多次, 产出任意长度的独立密钥。输出密钥计算意义上独立 —— 攻破一个不暴露其他。

# TLS 1.3 实际派生的(简写):
shared_secret = ECDH(client_x25519, server_x25519)
handshake_secret = HKDF-Extract(salt = early_secret, secret = shared_secret)
client_hs_traffic = HKDF-Expand-Label(handshake_secret, "c hs traffic", transcript_hash)
server_hs_traffic = HKDF-Expand-Label(handshake_secret, "s hs traffic", transcript_hash)
# ……继续到 master_secret → application keys → resumption_secret

要点。「对称密码(AES-128-GCM、ChaCha20-Poly1305)做批量数据加密 —— 快、两侧要同一把密钥。非对称(ECDSA、Ed25519、X25519)慢,只在握手时用于签名和密钥交换。 AEAD 把加密 + 完整性塞进一个原语;TLS 1.3 把它定为强制是因为 AEAD 之前的构造不断产生 padding-oracle 攻击。ECDHE 给前向保密:录密文 + 之后偷证书密钥仍然不能解密。 HKDF 把一个 ECDH 秘密拉伸成协议需要的所有独立密钥。」

03

TLS 1.3 握手 —— 一次往返到加密记录

TLS 1.2 需要 2 个 RTT 才能流第一个 HTTP 字节。TLS 1.3 只要 1 个。 客户端在 ClientHello 里投机性给出密码和 key share; 服务器确认并立刻加密剩余。省下的 —— 每连接 1 个 RTT —— 就是整个重写的全部动机。

我们 api.example.com 连接的 TCP 握手刚完成, 两侧有按序字节流但没加密。TLS 握手是线上下一件事, TLS 1.3 里它恰好装在一次往返里 —— 下面的步骤一条消息一条消息走它。

TLS 1.3 握手 —— 一次往返、然后加密记录Step 1 —— ClientHello:密码 + key share(X25519)+ SNI客户端(curl)服务器(api.example.com)ClientHello [TLS_AES_128_GCM_SHA256, ...] + X25519 pub + sni=api.example.com明文握手加密应用加密(AEAD 记录)PSK / 会话票
客户端列出支持的密码套件(TLS 1.3 里只剩 ~5 个 —— RC4/CBC/MD5/SHA1 全砍了)。X25519 公钥是客户端那一半的临时 ECDH;server-name-indication(SNI)告诉服务器它要连的是哪个虚拟主机(否则在 TLS 终止之前服务器挑不出正确证书)。
1 / 8
握手在 1 RTT 里装得下,因为客户端在 ClientHello 里投机性地送一份 key share。 服务器挑密码套件、送自己的 share、用 ECDH 算共享秘密, 立刻把握手剩余部分用握手流量密钥加密 —— 证书、CertificateVerify (对整段记录签名,所以降级攻击藏不住)、Finished。 应用记录走另一套通过 HKDF 派生的应用流量密钥。

为什么 TLS 1.3 是 1 RTT、1.2 是 2 RTT

TLS 1.2 里,客户端发 ClientHello(提议密码)、 等 ServerHello + Certificate + KeyExchange + ServerHelloDone、 然后发自己的 ClientKeyExchange + Finished、 等服务器的 Finished —— 任何应用数据之前 2 个 RTT。 TLS 1.3 做了一个关键改动:客户端在第一条消息里就发 key share, 投机性挑一个它期望服务器也支持的密码。服务器同意的话,立刻派生共享秘密, ServerHello 回复能包含别的一切,已经加密。总握手:1 RTT。

TLS 1.2(2 RTT):                  TLS 1.3(1 RTT):
client -> ClientHello             client -> ClientHello + key_share
server -> ServerHello             server -> ServerHello + key_share +
       -> Certificate                       {EE, Cert, CertVerify, Finished}
       -> ServerKeyExchange       client -> {Finished}
       -> ServerHelloDone                -> [Application Data]
client -> ClientKeyExchange
       -> ChangeCipherSpec
       -> Finished
server -> ChangeCipherSpec
       -> Finished
client -> [Application Data]

0-RTT —— 请求就搭第一个包

客户端如果之前连过这个服务器并保留了 resumption secret,能比 1 RTT 还快: 跟 ClientHello 同一个包里,客户端能在 PSK 派生密钥下夹带加密的「early data」。 总:0 RTT。HTTP 请求字面上就搭这个连接的第一个 IP 包。

坑:0-RTT 易重放。抓到 early-data 包的攻击者能之后重放 —— TLS 1.3 给 0-RTT 没有内建反重放(它需要服务器端状态,在 CDN 上扩展性差)。 所以 0-RTT 只对幂等请求安全。CDN 典型上对 GET 允许 0-RTT、 但对 POST/PUT/DELETE 强制 1-RTT 握手。

TLS 1.3 砍了什么(以及为什么)

最大的设计选择是负面的 —— 砍什么。TLS 1.3 删了曾产生过 CVE 的每个算法:

  • 静态 RSA 密钥交换 —— 没前向保密(看 Section 02)。所有密钥交换必须是 (EC)DHE。
  • MD5 和 SHA-1 在任何握手角色 —— 两个都有实用碰撞攻击。
  • RC4 —— 偏的密钥流,够样本后能利用恢复明文。
  • CBC 模式分组密码 —— BEAST、POODLE、LUCKY13 的来源。现在全是 AEAD。
  • TLS 层压缩 —— 启用了 CRIME。应用层压缩(gzip)仍然没事, 因为攻击者不能像往 TLS 记录里那样往 HTTP body 里注入选择明文。
  • 重协商 —— 被独立的 KeyUpdate 替代。重协商是 2009 年一个漏洞的来源, 那时服务器搞混两个客户端的数据。

净效果:TLS 1.2 的 ~300 个密码套件在 TLS 1.3 变成 ~5 个 —— 全部按构造安全。 不再有「在配置里禁用坏的」—— 坏的根本不存在。

记录完整性干掉降级攻击

CertificateVerify 消息是个签名,用服务器长期私钥, 签到目前为止每条握手消息的 SHA-256 哈希 —— 「transcript hash」。 攻击者要是篡改 ClientHello 强迫弱密码,服务器签的记录会跟客户端算的不同, 签名不会验过。这是对整族降级攻击(Logjam、FREAK)的防御。

也值得知道:Finished 消息是对记录的 MAC,用握手密钥, 所以两侧交叉检查算出了同样的 transcript hash。任何在途篡改打破 MAC、 在任何应用数据被送之前连接中止。

openssl 能看到的

$ openssl s_client -connect api.example.com:443 -tls1_3 -msg
CONNECTED(00000005)
>>> TLS 1.3, Handshake [length 00ff], ClientHello
<<< TLS 1.2, Handshake [length 007a], ServerHello
<<< TLS 1.3, Handshake [length 0024], EncryptedExtensions
<<< TLS 1.3, Handshake [length 092e], Certificate
<<< TLS 1.3, Handshake [length 0114], CertificateVerify
<<< TLS 1.3, Handshake [length 0034], Finished
>>> TLS 1.3, Handshake [length 0034], Finished
---
Server certificate
subject=CN = api.example.com
issuer=C = US, O = Let's Encrypt, CN = R3
Cipher    : TLS_AES_128_GCM_SHA256
Protocol  : TLSv1.3
Verification: OK

要点。「TLS 1.3 是 1 RTT,因为客户端在 ClientHello 里投机性发 key share; 服务器挑密码、派生共享秘密、把证书 + CertificateVerify + Finished 都加密在 ServerHello 回复里送出。 客户端发 Finished;HTTP 能搭下一个包。0-RTT 用 PSK 跳过握手但易重放 —— 只用于幂等请求。 CertificateVerify 签整段记录,所以降级攻击(Logjam、FREAK)藏不住。」

04

PKI —— 证书、链、CA、mTLS

Section 02 的密码学建一条安全隧道;PKI 是告诉客户端隧道另一端是不是它要的主机的东西。 整座建筑 —— X.509 证书、信任根、ACME、撤销 —— 都为正确回答那一个问题存在。

X.509 —— 证书格式

证书是一个二进制结构(DER 编码的 ASN.1,通常显示为 PEM 包装的 Base64), 把公钥绑到一个或多个名字,由签发者签名。TLS 客户端实际看的字段:

  • Subject —— 遗产「主人」名;Common Name(CN)字段 以前装主机名。现代客户端(Chrome 2017 起)完全忽略 CN。
  • Subject Alternative Name (SAN) —— 主机名的现代字段。 单张证书能列许多 SAN(api.example.comwww.example.com*.example.com 通配)。客户端把请求的主机名跟这列表匹配。
  • 公钥 —— 服务器的公钥(RSA-2048、ECDSA P-256 等)。 服务器用匹配的私钥签握手以证明所有权。
  • 有效期 —— Not Before / Not After 时间戳。公共 CA(Let's Encrypt 等) 发 90 天证书;续签必须自动化。
  • Issuer —— 链里上一级证书按名字。
  • 扩展 —— Key Usage(digitalSignature、keyEncipherment)、 Extended Key Usage(serverAuth、clientAuth)、Basic Constraints(这张证书是 CA 吗?)、 CRL Distribution Points、OCSP responder URL、SCT 列表(Certificate Transparency)。
  • 签名 —— 签发者对上面所有东西的签名,用签发者的私钥算。
$ openssl x509 -in cert.pem -noout -text | head -25
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 04:c5:ab:...
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: C = US, O = Let's Encrypt, CN = R3
        Validity
            Not Before: Mar  1 00:00:00 2026 GMT
            Not After : May 30 23:59:59 2026 GMT
        Subject: CN = api.example.com
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
            EC Public Key: (256 bit)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature
            X509v3 Extended Key Usage:
                TLS Web Server Authentication
            X509v3 Subject Alternative Name:
                DNS:api.example.com, DNS:www.example.com

链 —— 为什么我们需要中间证书

典型的链深 3 张:

Leaf:         CN = api.example.com    (R3 签发,有效 90 天)
              |
              | R3 的私钥签
              v
Intermediate: CN = Let's Encrypt R3   (ISRG Root X1 签发,有效 ~5 年)
              |
              | ISRG Root X1 的私钥签
              v
Root:         CN = ISRG Root X1       (自签名,有效 ~30 年)
              ^
              | 每个浏览器 / OS 信任仓预装

为什么要中间?风险隔离。根 CA 的私钥是那个 CA 世界上最珍贵的密钥 —— 离线保管,在 HSM,在安全设施。它只签中间证书,从不离开。中间证书的密钥被攻破时, CA 撤销那个中间证书(传播要花力气)、发新的 —— 但根保持安全。 根被攻破会要每个浏览器和 OS 装新信任仓。根每 10-20 年轮一次;中间证书更频繁。

握手时,服务器送 leaf 证书和它知道的任何中间证书。客户端走链, 用下一张证书的公钥验每个签名,直到到达一个签发者在本地信任仓里的证书。

ACME 和 Let's Encrypt

2016 之前,拿证书意味着在 CA 网站填表、付 100 美元+/年、手动装文件。ACME(Automated Certificate Management Environment、RFC 8555) 是把这一切自动化的协议。Let's Encrypt 是规模化跑 ACME 的免费 CA。流程:

  1. 你的 certbot / acme.sh / Caddy / Traefik 生成新密钥对。
  2. 它请 Let's Encrypt 给 api.example.com 发证书。
  3. Let's Encrypt 挑战客户端证明对域名的控制:在http://api.example.com/.well-known/acme-challenge/xyz 提供特定文件 (HTTP-01),或设 DNS TXT 记录(DNS-01)。
  4. Let's Encrypt 取挑战来验证,然后签发并返回证书(90 天有效)。
  5. 你的工具装证书并在 ~60 天时安排续签任务。

对行业的影响:HTTPS 部署从 2014 年页面加载的 ~30% 到 2026 年的 ~95%。 EV(Extended Validation、「绿条」)和 OV(Organization Validation)证书基本死了, 因为 Chrome 2019 年起不再显示绿条 —— 用户反正不能可靠区分 DV 和 EV、 EV 没实际安全好处。

撤销 —— 证书需要被杀死时

证书能发 90 天但第 7 天被攻破。客户端怎么知道?3 个机制,都不完美:

  • CRL(Certificate Revocation List)—— CA 发布一份签了名的撤销序号列表。 原始方法;列表变巨大(MB 级),下载代价占主导。浏览器基本不再检查。
  • OCSP(Online Certificate Status Protocol)—— 客户端给 CA 的 OCSP responder 发实时 HTTP 请求问「序号 X 还有效吗?」每证书加一个 RTT、把浏览历史漏给 CA、 responder 挂了浏览器要么 soft-fail(不安全)要么 hard-fail(拒绝)。 实际中基本 soft-fail。
  • OCSP stapling —— 服务器周期性从 CA 取新鲜 OCSP 响应、 在 TLS 握手时跟证书一起提供。客户端拿到新鲜性保证、没额外 RTT、没隐私泄漏。 现代默认。

还有:Certificate Transparency (CT) —— 每张公共信任证书签发前必须记录到 append-only CT 日志里。浏览器拒绝没足够 SCT(Signed Certificate Timestamp)的证书。 这让误发公开:CA 给你不要的域名发证书,你能在日志里看到并投诉。 CT 干掉了 2015 年 Symantec 误发假 google 证书的丑闻循环。

mTLS —— 客户端也有证书时

标准 TLS 里,只有服务器出示证书。双向 TLS(mTLS)要求客户端也出示一张 —— 两侧互相认证。证书链验证两个方向都跑。

用例:

  • 服务网格里的服务到服务 —— Istio、Linkerd、Consul Connect 给每个内部 RPC 默认 mTLS。代理 sidecar 处理证书签发、轮换、验证;应用看到的就是普通 HTTP 或 gRPC 调用。SPIFFE / SPIRE 把身份模型标准化:每个工作负载拿到一个 SPIFFE ID, 像 spiffe://prod/payment-service,嵌在其证书里。
  • 高安全端点的 API 认证 —— 银行、政府、B2B 集成。 客户端的证书比任何 API key 更强地标识它(不能像 API key 那样通过日志文件外泄)。
  • VPN 替代(零信任网络) —— Cloudflare Access、Tailscale、Google BeyondCorp。 每台设备拿证书;访问由证书验证而不是网络位置门禁。

证书 pinning —— 连 PKI 都不够时

高价值手机 app(银行、即时通讯),标准信任仓太宽 —— 几百个 CA、任何一个都能给你的域名发欺诈证书 (CT 让这个公开但不阻止它)。Pinning 在 app 里硬编码期望的证书 (或其公钥 SPKI 哈希);握手时出示的证书不匹配,无论信任仓说什么,连接都被拒绝。

浏览器用 HPKP(HTTP Public Key Pinning)试过这个但 2018 年弃用 —— 丢了 pin 的密钥太容易把自己网站砖死。手机 pinning 仍常见,因为 app 厂商控制更新渠道、 能下发新 pin。警告:漏轮换能把用户锁外,要紧急 app 更新。

要点。「证书把公钥绑到主机名(在 SAN 字段),由中间 CA 签名、 由客户端信任仓里的根 CA 签名。leaf 通过 ACME / Let's Encrypt 每 90 天轮一次; 根每 10 年轮一次。实际撤销是 OCSP stapling + Certificate Transparency。 mTLS 加客户端证书认证 —— 内部 RPC 的服务网格默认、零信任 VPN 替代。 Pinning 给高价值手机 app 硬编码期望的证书。」

05

速查表

6 道值得能冷讲清楚的核心问题、5 个 code review 时一眼看出来的红旗。

TLS 提供哪 3 个保证、各自怎么提供?

机密性 —— AEAD 加密(AES-128-GCM 或 ChaCha20-Poly1305)对任何被动观察者藏住 payload。 完整性 —— 同一个 AEAD 原语产出每记录的 MAC;任何在途篡改让解密失败。 真实性 —— 证书链证明服务器持有命名所请求主机的证书的私钥、 那张证书(传递地)由客户端信任仓里的根签名。任一掉了其他基本没用: 没真实性让 MITM 跑一条对自己的完美加密通道。

为什么 RSA 密钥交换(vs ECDHE)被认为是遗产?

没前向保密。静态 RSA 密钥交换里,客户端用服务器长期 RSA 公钥加密 pre-master secret。 任何之后拿到 RSA 私钥的人能解密用那张证书录过的每次 TLS 会话 —— 这就是促使国家级对手抓流量的「现在收割、以后解密」威胁模型。 ECDHE 每连接生成临时密钥用完即丢;之后偷长期证书密钥不暴露它。 TLS 1.3 完全砍掉静态 RSA。

把 TLS 1.3 握手走一遍 —— 每条消息里有什么?

ClientHello:支持的密码、X25519 公钥、SNI = 主机名。ServerHello:选定的密码、 服务器 X25519 公钥(现在两侧都能算共享 ECDH 秘密、通过 HKDF 派生握手密钥)。 然后,在握手密钥下加密:EncryptedExtensions、Certificate(leaf + 中间证书)、 CertificateVerify(对整段握手记录的签名 —— 干掉降级攻击)、Finished(记录的 MAC)。 客户端验链、加密发送自己的 Finished。两侧切到应用流量密钥。HTTP 搭下一个包。 总:app data 之前 1 RTT。

「验证证书链」是什么意思 —— 浏览器实际在查什么?

按序 5 件事:(1)Subject Alternative Name 列表包含被连主机名。 (2)当前时间在 Not Before / Not After 之间。(3)Extended Key Usage 包含 serverAuth。 (4)链里每张证书,上一级证书的公钥成功验它的签名;最顶层证书的签发者匹配本地信任仓里的某证书。 (5)通过 OCSP staple(或 soft-fail OCSP)的撤销检查、CT 日志的足够 SCT 在场。 任一步失败硬错误中止握手 —— 没「忽略并继续」。

0-RTT 是什么、为什么危险?

0-RTT(early data)让复用前会话 PSK 的客户端在第一个包里跟 ClientHello 一起送加密的应用数据 —— 完全跳过握手。危险:数据易重放。抓到 0-RTT 包的攻击者能之后重放; TLS 1.3 给 early data 没内建反重放(它要服务器端状态、在 CDN 上扩展性差)。 只对幂等请求安全;CDN 典型上对 GET 允许 0-RTT、POST / PUT / DELETE 强制 1-RTT。

mTLS 是什么、你在哪用它?

双向 TLS —— 客户端也出示证书,服务器像客户端验服务器一样验客户端的链。主要用法: 服务网格里的服务到服务认证(Istio / Linkerd / Consul Connect、常通过 SPIFFE 身份)、 API key 太弱的高安全 API 认证(银行、B2B 集成)、以及替代 VPN 的零信任网络访问 (Cloudflare Access、Tailscale)。网格用例最常见 —— mTLS 是任何现代 Kubernetes 部署里 东西流量的默认。

Code review 红旗

  • 生产 HTTP 客户端禁用证书验证。curl -krequests.get(..., verify=False)InsecureSkipVerify: true。 这把 TLS 变成「无 mTLS 的 mTLS」:加密给某个服务器、没办法发现 MITM。 基本总是有人撞到证书错误、绕过去而不是修它的信号。
  • HTTP + 应用级 AES「加密」而不用 TLS。自己卷密码学意味着自己卷 padding-oracle / 重放 / nonce 重用 bug。 TLS 是 10 年专业密码学家的产出;用它,把你的应用逻辑放上面而不是旁边。
  • 公共服务上启用旧 TLS 版本(1.0 / 1.1)。BEAST、POODLE、LUCKY13 都住那。现代浏览器默认拒绝 1.0 / 1.1, 但服务器端兼容标志经常仍允许。显式禁用:nginx 里 ssl_protocols TLSv1.2 TLSv1.3
  • 自签名证书没内部 PKI / 信任分发。意味着每个客户端要么禁了验证跑(看红旗 1)、要么在代码里 pin 一份证书副本(轮换噩梦)。 架个小内部 CA(smallstep、Vault PKI)、通过配置管理分发根证书。
  • 非幂等端点启用 0-RTT。允许 0-RTT POST 让攻击者能重放之前抓的购买、转账、DB 改动请求。 大多数 CDN 让你按路由白名单 0-RTT —— 限在 GET 和 HEAD、绝不要改动动词。