🔒 第01章 · HTTPS / TLS 原理

非对称加密/握手/证书链

01 | HTTPS / TLS 原理

你每天打开网页都在用 HTTPS,但地址栏那把"小锁"背后到底发生了什么?这一章讲清楚:为什么需要 HTTPS、非对称加密解决的难题、TLS 握手一步步在做什么、以及证书链到底是怎么被信任的。 概念先立住,后面几章的命令和配置才有根。

为什么需要 HTTPS

HTTP 是明文协议。你输入密码、转账、发邮件,数据就像写在明信片上,经过的每个路由器、每个共享 WiFi 都能原样读出。三个问题:

  • 窃听——中间人能看到你的内容;
  • 篡改——中间人能在途中改写数据;
  • 假冒——你连的服务器可能根本不是目标站点。

HTTPS 在 HTTP 和 TCP 之间加了一层 TLS(Transport Layer Security,前身是 SSL),一次性解决以上三个问题:加密、完整性校验、服务器身份认证。

# 对比一次明文 vs 加密请求,注意协议和端口
curl -v http://bangdream.xyz/ -o /dev/null 2>&1 | grep -E "Connected|SSL"
# 上面是 80 端口明文。
curl -v https://bangdream.xyz/ -o /dev/null 2>&1 | grep -E "Connected|SSL|subject"

一句话记忆:HTTP 只负责"传输逻辑",TLS 负责"传输安全"。两者是同一层面的不同实现,所以端口分明(80 / 443)。

对称加密 vs 非对称加密

加密分两类,各说一句话:

加密方式 原理 用途 例子
对称加密 加解密用同一把钥匙 真正加密大量数据(AES) AES-128-GCM,快速高效
非对称加密 一对密钥,公钥加密、私钥解密(或反向签名) 交换密钥、签名认证 RSA、ECDSA

对称加密快,但问题在怎么把钥匙安全给对方——主节点约定密钥的过程本身就是明文,会被偷听。

这就引出非对称加密的高明之处:公钥可以公开,别人用你的公钥加密,只有你的私钥能解开。所以"交换对称密钥"这件事本身可以放在非对称加密的"安全信封"里完成。

一个接地气的类比:

你把一个上锁的箱子公开给人,钥匙只有你一个人有。别人把东西放进箱子,锁上,寄给你。箱子路上被偷也打不开。TLS 握手就是"先送箱子、交换箱子里的密钥、然后改用更快的对称加密继续通信"。

TLS 握手(TLS 1.2 经典流程)

握手的目的:在公开通道上安全地协商出双方共用的对称密钥,并确认对端身份。 以 TLS 1.2 为例:

Client                           Server
  | 1. ClientHello(支持算法/随机数) -> |
  | <- 2. ServerHello(选定算法/随机数)、Server Certificate(证书)、
  |         ServerKeyExchange、ServerHelloDone
  | 3. ClientKeyExchange(用服务器公钥加密"预主密钥") -> |
  |    双方用随机数 + 预主密钥各自算出"主密钥"
  | 4. 各自发送 Finished(校验握手完整性)
  | 5. 握手完成,开始对称加密的应用数据(AES)

关键点:

  1. ClientHello / ServerHello:协商版本(TLS1.2 或 1.3)、密码套件、交换随机数。
  2. 服务器出示证书:把权威 CA 签的名片(含公钥)发过来,客户端验证。
  3. 交换预主密钥:客户端用服务器公钥加密一个随机会话秘密发给服务器——这就是非对称加密的真实用武之地。
  4. 派生主密钥:双方各用两个随机数 + 预主密钥算出主密钥,再用主密钥派生出会话密钥。
  5. Finished:用协商好的密钥加密一条收尾消息,握手成功说明双方算法一致、密钥相同,之后一切数据都在加密通道里。

TLS 1.3:更快的握手

TLS 1.3 把往返从 2 个降到 1 个(RTT),简化了加密套件(不再用 RSA 密钥交换,改用 ECDHE 临时密钥协商),并支持 0-RTT 让重复访问更快。

# 看当前站点支持的 TLS 版本(注意是 TLS 1.3)
openssl s_client -connect bangdream.xyz:443 -tls1_3 </dev/null 2>/dev/null \
  | grep -E "Protocol|Cipher"

易错点:老版本浏览器不支持 TLS 1.3 没关系——服务端会降级协商到 TLS 1.2。但别再开 TLS 1.0 / 1.1,它们有严重漏洞(POODLE / BEAST),2011 年前的协议标准早已废弃。

证书与非对称加密的关系

证书到底存了什么?一句话:证书 = 你的公钥 + 你的身份信息 + CA 的签名。它解决的问题是"公钥虽然可以公开,但怎么保证这把公钥真是目标网站 bangdream.xyz 的,而不是黑客伪造的?"

证书字段 含义 实例(本机证书)
subject 证书持有者(域名) CN = bangdream.xyz
issuer 签发它的 CA O = Let's Encrypt, CN = YE1
subjectAltName 真正校验的域名列表 DNS:bangdream.xyz, DNS:www.bangdream.xyz
公钥 网站的加密公钥 ECDSA 公钥
有效期 notBefore / notAfter 90 天
签名 CA 对以上内容的签名 用 CA 私钥生成

注意现代证书校验证书用的是 subjectAltName(SAN),而不是证书的 CN 字段。早年间浏览器还看 CN,现在 CN 已被人为支持 SAN 取代。签发多个域名时就是往 SAN 里塞,所以一个证书能同时覆盖 bangdream.xyz 和 www.bangdream.xyz。

证书链是怎么被信任的

浏览器/系统里预装了一批根 CA(Root CA)和信任锚(Trust Anchor)。验证一个证书时,把证书往上"追根":

叶证书(Leaf:C N = bangdream.xyz)
     ↑ 由 中间 CA(CN = YE1) 签名
中间证书(Intermediate:CN = YE1)
     ↑ 由 根证书(ISRG Root) 签名
根证书(ISRG Root X1) —— 已预装在浏览器/系统里 → 最终信任

现实中的浏览器并没有把每个中间 CA 都装进去,所以你必须把叶证书 + 中间证书一起发给客户端,这就是 fullchain.pem 存在的意义——它把"叶子→中间"拼成一条链。根证书已经在系统里了,不需要发。

# 本机真实证书链(从叶到根),直观看出四级
openssl crl2pkcs7 -nocrl -certfile /etc/letsencrypt/live/bangdream.xyz/fullchain.pem \
  | openssl pkcs7 -print_certs -noout

易错点:很多人只发 cert.pem(叶证书)而不带中间证书,结果客户端报 unable to get local issuer certificate。必须发 fullchain.pem,你才能把"叶子 + 中间"整条链递出去。这正是本机 Nginx 配置里 ssl_certificate 指向 fullchain.pem 的原因。

场景实战:用 curl 看一次真实握手

# -v 能看到握手细节和不安全告警
curl -v https://bangdream.xyz/ -o /dev/null 2>&1 | grep -E "SSL_connect|subject:|issuer:|TLS 1|ALPN"

# 用 openssl s_client 手动握手、只看证书链
echo | openssl s_client -connect bangdream.xyz:443 -servername bangdream.xyz 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates -ext subjectAltName

一次典型的输出里你能看到:证书 subject、issuer、SAN 域名列表、有效期范围——这些就是上面讲的字段在真实世界的投影。

延伸:为什么证书只能覆盖"域名"

证书签名的是域名(SAN),不是 IP。因为公钥到底对应谁,必须由一个能证明"这个域名归属你"的机构来背书。Let's Encrypt 通过 ACME 协议验证你对域名拥有控制权(详见下一章),然后才给你签发。你拿不到别的域名证书,拿到了浏览器也会因为域名不匹配而报警。

# 域名不匹配的经典报错,本地测试可故意用错 servername
echo | openssl s_client -connect bangdream.xyz:443 -servername wrong.com 2>/dev/null \
  | grep -E "Verify|verify" 

安全提示:一旦发现证书是"自签名"(self-signed)或 issuer 不是公认 CA,浏览器会弹大红警告。别一路"继续访问",除非是你自己环境里明确信任的内网测试证书。


下一章:02-certbot → 用 Let's Encrypt 免费给域名签发、并让 certbot 自动续期。