HTTPS、TLS 与服务器身份
连接加密、密钥协商、证书验证与实际暴露面。
本页沿 HTTP 的具体接口和通信行为展开。沿用归档中的消息与代码示例,归档所称的实测仅作为原作者历史记录;2026-10-05 整理未向外部站点发送实验请求,也未操作浏览器。命令输出随服务、网络、版本和代理条件变化。
前几章里,HTTP 报文从头到尾摆在明面上。本章回答一个更根本的问题:这些话在去服务器的路上,别人听得见吗?改得了吗?对面装得了吗? HTTPS 就是 HTTP 对这三个问题的回答。归档保留了作者 2026-10-04 的实验描述;本次未重跑。
明信片的麻烦:明文 HTTP 的三类风险
寄明信片方便,但字迹全暴露在卡片上:每个经手的邮递员都能读,甚至能拿笔加上两行。HTTP 报文在网络里就是这张明信片——curl 一看便知:
$ curl -sv http://example.com/
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/8.7.1
> Accept: */*
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
< Server: cloudflare请求行、头部、响应一字不落。公共 Wi-Fi 这类不安全信道上,免费软件就能「嗅探(sniffing)」到这些包(Cloudflare)。归纳成三类风险:
- 窃听(eavesdropping):链路上任何节点都能读到通信内容。
- 篡改(tampering):web.dev 提醒,「入侵者」不只是恶意攻击者,还包括「合法但侵入性的公司,比如向页面注入广告的 ISP」;图片、cookie、脚本、HTML 都可能被改,入侵可发生在网络任何节点(用户电脑、Wi-Fi 热点、被攻陷的 ISP)。
- 冒充(impersonation):中间人攻击(man-in-the-middle,MITM)——攻击者插进浏览器与服务器之间,读取并修改来回的流量,比如公共 Wi-Fi 上的钓鱼热点。
还有个容易被低估的玩法:聚合分析——汇总你访问过哪些未加密页面,推断身份与意图(去匿名化,deanonymization)。web.dev 的例子:即使只是浏览未加密的医疗文章,也可能向雇主泄露健康信息。
误解澄清:「只有银行、电商才需要 HTTPS。」这是 web.dev 官方点名的常见误解:未加密请求都在泄露行为与身份,浏览记录可被聚合分析。所有网站都该上 HTTPS。
HTTPS:通过 TLS 保护 HTTP 通信
MDN 的定义一句话讲清:HTTPS 是 HTTP 的加密版本——用 SSL 或 TLS 协议加密客户端与服务器之间的全部通信;本质仍是 HTTP 报文,只是传输时被加密,所以又叫 HTTP over TLS。
误解澄清:「HTTPS 是取代 HTTP 的新协议,报文格式完全不同。」错。握手结束后跑的仍是普通 HTTP(GET/POST、Host、Content-Type 都在),只是内容被会话密钥加密后再交给网络。
同一命令换成 https,画风全变:
$ curl -sv https://example.com/
* (OUT), TLS handshake, Client hello (1):
* (IN), TLS handshake, Server hello (2):
* (IN), TLS handshake, Certificate (11): ← 服务器证书
* (IN), TLS handshake, CERT verify (15):
* (IN), TLS handshake, Finished (20):
* (OUT), TLS handshake, Finished (20): ← 1 个往返完成 TLS 1.3 握手
* SSL connection using TLSv1.3
* ALPN: server accepted h2小注:括号里的数字是 TLS 消息类型的编号——ClientHello=1、Certificate=11、Finished=20——不是步骤序号;本机复现时可能还会多出一行 Unknown (8),那是 TLS 1.3 新增的 EncryptedExtensions 消息,curl 没为它显示友好名字。
握手消息可见,HTTP 内容一字不见——中间人抓包(curl -v / Wireshark)视角同理:只剩密文与握手元数据。再做个反向实验,不握手、直接向 443 端口说明文 HTTP:
$ printf 'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n' | nc example.com 443
(无任何输出——服务器在等 TLS 握手,明文对它形同乱语)(这里 printf 手写了一段最小化的明文请求,\r\n 是报文格式规定的回车换行;nc 是把字节直接发往指定端口的简易工具。)
端口:RFC 9110 规定,URI 未写端口时 http 默认走 TCP 80、https 默认走 TCP 443——但那只是默认值,显式写 https://example.com:8443/ 就用别的端口。「见 443 必是 HTTPS」不严谨,判断依据应是协议内容而非端口号。
名称沿革:SSL(Secure Sockets Layer,1994–1996 年由 Netscape 设计)是 TLS 的前身,TLS 1.0 于 1999 年接替 SSL 3.0 标准化。如今所有 SSL 版本均已弃用,全网实际用的是 TLS(主流 1.2 和 1.3);「SSL 证书」「SSL 加密」只是沿袭旧名的习惯叫法。
一张对比表:HTTP vs HTTPS
| 特征 | HTTP | HTTPS |
|---|---|---|
| 默认端口(URI 未写时) | TCP 80 | TCP 443 |
| 报文可见性(抓包视角) | 请求行、头部、正文全明文 | 只见握手元数据与密文 |
| 连接建立 | TCP 建好后直接发 HTTP | TCP 建好后先 TLS 握手,再发 HTTP |
| 窃听 / 篡改 / 冒充 | 无防护 | 加密 + 完整性校验 + 证书身份验证 |
TLS 的三大保护:对三类风险逐一拆招
TLS(Transport Layer Security,传输层安全)从三个维度保护连接,恰好与三类风险一一对应(MDN):
- 加密(Encryption):传输数据被加密,窃听者读不到 → 防窃听;
- 完整性(Integrity):传输中数据的任何篡改都会被察觉 → 防篡改;
- 身份验证(Authentication):双方能证明自己是所声称的实体 → 防冒充。
方向上:Web 上通常是服务器向浏览器证明身份,浏览器一般不向服务器证明——这就是为什么普通人不需要给浏览器装证书。
两把钥匙的分工:非对称认门,对称干活
要加密,先得有钥匙。TLS 同时用两类加密(Cloudflare):
- 对称加密(symmetric encryption):加解密用同一把钥匙。像密码箱——快,但「怎么把钥匙安全送到对方手里」是个难题。
- 非对称加密(asymmetric encryption):公钥(public key)/私钥(private key)成对。像一批公开散发的挂锁:谁都能拿一把扣上箱子,只有私钥主人打得开。它解决了送钥匙的难题,但慢几个数量级。
于是 TLS 的分工是:握手期间用非对称加密认身份、协商密钥;握手完成后,双方改用同一把对称的会话密钥(session key)加密全部内容——因为对称远快于非对称。会话密钥随机生成、仅本次会话有效、用完即弃;启用后,公私钥对就不再参与数据加密。
误解澄清:「非对称更安全,应该全程用它加密网页。」不必也不能:它慢几个数量级,只在握手阶段出场;内容全程用每次连接随机新生成的对称会话密钥。
握手:钥匙怎么安全地商量出来
先澄清一个反直觉的事实:握手必须从明文开始——双方得先「喊话」协商好用什么算法,才谈得上加密。TLS 1.2 及之前,ClientHello、ServerHello、证书都明文传输;TLS 1.3 的一大改进,是把 ServerHello 之后的握手消息全部加密。
TLS 1.2 的经典握手(RFC 5246,2008):
客户端 服务器
│ ── ClientHello ─────────────────────────→ │ 协议版本、客户端随机数、支持的密码套件列表
│ ←─ ServerHello ────────────────────────── │ 选定的密码套件(cipher suite)、服务器随机数
│ ←─ Certificate + ServerHelloDone ──────── │ 服务器证书(内含公钥)
│ ── ClientKeyExchange ───────────────────→ │ 密钥交换材料
│ ── ChangeCipherSpec + Finished ─────────→ │
│ ←─ ChangeCipherSpec + Finished ─────────── │
│ ════════ 此后发送加密的应用数据 ════════════ │在图上数往返(RTT,round-trip time):第一轮从客户端发 ClientHello 到服务器回完 ServerHelloDone,第二轮从客户端的 ClientKeyExchange 到双方互换 Finished——边界正好两次,所以完整握手要 2 个 RTT,之后才能发第一个 HTTP 请求。
会话密钥怎么算出来(RSA 密钥交换的简化讲法,参考阮一峰《图解 SSL/TLS 协议》的思路):客户端生成一个 pre-master secret,用证书里的服务器公钥加密后发过去;两边各拿「pre-master secret + 客户端随机数 + 服务器随机数」算出同一把会话密钥。三个随机数凑在一起,保证每条连接的钥匙都不一样。
TLS 1.3(RFC 8446,2018):更快,也更安全。
- 1-RTT 握手:客户端在第一个 ClientHello 里就带上 key_share(密钥交换材料),服务器一口气回完 ServerHello 与(已加密的)证书、Finished;客户端回 Finished 即可发数据。Cloudflare:「TLS 1.3 相比早期版本的最大优势之一是建立连接只需一个往返」。算上 DNS 与 TCP 的总账:TLS 1.2 新建 4 RTT + DNS,TLS 1.3 新建 3 RTT + DNS,0-RTT 恢复只要 2 RTT + DNS(Cloudflare 实测约 40% 的 HTTPS 连接属于会话恢复(session resumption)——即复用上次握手的成果、跳过大部分流程快速重连)。
- 0-RTT 会话恢复(zero round-trip time):客户端用上次的 PSK(pre-shared key,预共享密钥),在第一个包里就带上加密的应用数据(early data)。好比老顾客的「见券即付」早餐券:不用重新排队握手,点单随券同发。
但这张券是纸面的,可以被复印。RFC 8446 明确「TLS 不为 0-RTT 数据提供内在的重放保护」:攻击者原样复制重放,就可能让「转账这类交易被重复执行」;因此客户端「绝不能(MUST NOT)」在不安全可重放的场景发送 early data——只适合「加载页面」这类**幂等(idempotent)**请求:幂等=重复执行结果不变,页面加载两遍还是那个页面,转账来两遍可就扣两次钱了。
安全上也做了重构(RFC 8446 第 1.2 节官方清单):
- 删除静态 RSA 与静态 DH 密钥交换,所有公钥密钥交换都具备前向保密(forward secrecy):密钥交换用临时(ephemeral)密钥,即使服务器私钥日后泄露,之前录制的加密会话也无法解密——好比每次对话都用当场随机配的钥匙,丢了主钥匙也开不了过去的锁。
- 对称算法只保留 AEAD(认证加密,同时保证保密与完整)。
- ServerHello 之后握手消息全部加密(新增 EncryptedExtensions)。
- 一批「减法」:砍掉压缩、DSA 签名、自定义 DHE 组等老旧或少用的机制;从握手材料推导出密钥的步骤(密钥派生,key derivation)改用更现代的 HKDF 算法;会话恢复统一为单一的新 PSK 交换。
- 密钥交换整体只保留三种基本模式:(EC)DHE(配合证书认证,普通新建连接用)、纯 PSK、PSK+(EC)DHE——注意这是 TLS 1.3 协议层面的全部选项(RFC 8446 第 2 节),其中后两种依托预共享密钥,会话恢复用的就是它们。
这份清单记不全没关系,抓住两个词即可:更快(1-RTT),以及更安全(删旧机制、全面前向保密)。
误解澄清:「TLS 1.3 只是更快的 1.2,安全上没区别。」不对——上面这份清单全是安全变化。
原调研记录的站点已在用 TLS 1.3:
$ echo | openssl s_client -connect cloudflare.com:443 -servername cloudflare.com -brief
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN=cloudflare.com
Verification: OK顺带一提:前面 curl 的输出里有 ALPN(应用层协议协商)——客户端在握手里提供 h2,http/1.1,服务器接受了 h2。也就是说 HTTP/2 是在 TLS 握手内顺带协商的,不多花一次往返。
数字证书:网站的护照
身份验证靠数字证书(certificate):网站要支持服务器认证就必须持证,证书内含经数字签名的服务器公钥(与服务器私钥配对),把密钥绑定到域名,浏览器由此确认「对面的真是这个域名」(MDN)。它像一本护照:写明「持有人(域名)是谁、公钥是哪把、有效期到哪天」,并盖着签发机构的章。
凭什么信这个章?靠信任链(chain of trust):叶子证书(leaf,服务器证书)必须能逐级追溯到受信任 CA 的根证书(root),中间由中间证书(intermediate)衔接。关键设计是:根信任来自客户端配置的信任库,通常由操作系统或浏览器维护;它们可以随软件更新或管理员配置变化。握手中服务器发送的链不能自行赋予新根信任。好比公证处的公章样本早就印在你手里的册子上,见面先对章。原调研记录了一条真实证书链(openssl s_client -showcerts 连接 www.cloudflare.com):
0 s:CN=www.cloudflare.com (叶子/服务器证书)
i:C=US, O=Google Trust Services, CN=WE1
1 s:C=US, O=Google Trust Services, CN=WE1 (中间 CA)
i:C=US, O=Google Trust Services LLC, CN=GTS Root R4
2 s:C=US, O=Google Trust Services LLC, CN=GTS Root R4
i:C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA(交叉签名)浏览器沿这条链逐级验签,直到某个根能在信任库里找到。注意末级:GTS Root R4 还被更老的 GlobalSign Root CA 交叉签名(cross-signing)——新 CA 借老 CA 的章先赢得信任,等自己的根被广泛预装后再独立门户。
证书从哪来:Let's Encrypt 是广泛使用的非营利证书颁发机构(CA,Certificate Authority),免费签发证书,配合 ACME 协议可全自动申请续期;现代托管服务默认或稍加配置就支持 HTTPS。顺带:curl 归档实验记录里 example.com 的证书只有约 90 天有效期——短周期 + 自动续期是如今的常态。
验证失败会怎样:自签、过期、域名不匹配、签发 CA 不在信任库——任何一种,客户端都无法确认服务器身份,于是拒绝并断连:
$ curl https://self-signed.badssl.com/
curl: (60) SSL certificate problem: self signed certificate
$ curl https://expired.badssl.com/
curl: (60) SSL certificate problem: certificate has expired(verbose 模式下还能看到客户端发出的 TLS alert:certificate expired。)
自签证书的信任条件:自签描述签发关系;客户端仍需通过预配信任、证书固定或私有 CA 等方式建立信任,不能仅凭服务器传来的证书接受其身份。企业内网常用自签或私有 CA 证书,把根手动装入信任库即可。
误解澄清二:「证书告警是小题大做,点『高级→继续访问』就行。」告警意味着无法确认服务器身份——这正是中间人攻击(如公共 Wi-Fi 上的钓鱼热点)的典型特征。在不可信网络下强行继续,等于亲手关掉三大保护中的身份验证。
地址栏的锁、混合内容与浏览器能力
锁图标的准确含义:这条连接经 HTTPS 加密、且服务器证书通过了验证。它不表示网站可信或无害——钓鱼网站也能申请到正规证书。Chromium 团队的官方博客《An Update on the Lock Icon》直言「锁图标并不表示网站可信」,并指出如今几乎所有钓鱼网站都在用 HTTPS;同样是这篇文章提到,2021 年的研究发现仅 11% 的受试者正确理解锁的含义(很多人以为锁 = 网站可信),于是 Chrome 117(2023 年 9 月)起把锁换成中性的「调节器(tune)」图标;同时 Chrome 继续把明文 HTTP 页面标记为「不安全」。点开图标可查看证书详情。
混合内容(mixed content):主文档经 HTTPS 加载,页面里的子资源(图片、脚本、样式表、字体等)却仍用 HTTP 拉取。这些资源可被窃听、可被篡改,而脚本最危险——脚本能修改页面的一切。现代浏览器分两档处理:
- 可升级内容(upgradable content):图片、视频、音频——HTTP 请求自动升级为 HTTPS,升级失败则不加载;
- 应阻断内容(blockable content):脚本、样式表、XHR/fetch 等——直接阻断。(旧规范称「被动/主动混合内容(passive/active mixed content)」。)
Mixed Content: The page at 'https://example.com' was loaded over HTTPS,
but requested an insecure element 'http://example.com/logo.svg'.
This request was automatically upgraded to HTTPS为什么下手这么重?HTTPS 页面的安全承诺会被任何一个 HTTP 子资源破坏:攻击者只需篡改那条未加密的响应(比如换掉一段脚本)就能控制整页。城墙再高,旁边留一扇没锁的小门等于白修——「页面一半安全」就是不安全。修复办法也只有一个:把子资源的链接统统改成 HTTPS,或写成 //example.com/logo.svg 这种省略协议的「协议相对引用」,让浏览器自动跟随当前页面的协议——别让浏览器替你兜底。
另一面是推力:HTTPS 页面构成安全上下文(secure context),Geolocation(定位)、getUserMedia(摄像头/麦克风)、service worker 等强大 API 只在其中可用——PWA 等现代能力全数以此为门槛。
误解澄清:「HTTPS 页面混进一个 HTTP 图片/脚本没关系,主体还是加密的。」这就是混合内容;浏览器会自动升级或直接阻断——一个走 HTTP 的子资源就是整页的短板。
进阶选读:加密不等于隐身
误解澄清:「传输加密了,任何人都看不到我访问了什么。」加密挡住的是链路上的窃听者;你主动连接的服务器(以及显式配置的代理)看到的仍是完整明文。此外,暴露面还不止链路——
- SNI(Server Name Indication):TLS 握手会明文携带目标域名,DNS 查询同样有暴露面;ECH(Encrypted Client Hello)与 DNS-over-HTTPS 是对应改进。
- HSTS(HTTP Strict Transport Security,HTTP 严格传输安全):指示浏览器今后只走 HTTPS,防降级;证书固定(certificate pinning):只认特定证书,防假 CA。
- 即便内容加密,流量元数据(metadata)——目标 IP、包长、收发时间——仍可被观测分析。
一句话收束本章:HTTPS 保护的是「路上」,端点依然看得见一切。它给 HTTP 穿上了盔甲,但没有把 HTTP 变成隐身衣。
最后更新于