Old-School Networking / 2026 ・ No.08
古法网络入门
第8讲 第 5、6 层与 TLS ── 给申请上锁
发布
到目前为止,申请都是以途中任何人都能读到的状态运送的。这一讲讲的是夹在 TCP 和应用之间、给申请上锁的 TLS,它也是 HTTPS 里那个 S 的真身。
我们搭建练习用的 CA(颁发证书的角色),把证书交给服务器。改成 HTTPS 后,什么看不见了,什么仍然看得见,又慢了多少?我们还会故意制造验证失败的 4 种情况来确认。
表示与会话这两层
新井路线图第 8 站写的是“第 5、6 层”,为什么放在一讲里?
古井在实际的互联网里,第 5 层和第 6 层几乎不是独立的,而是融入在 TLS 和应用里。所以一起讲。
全 12 讲路线图(当前:第 8 讲)
- 第 5、6 层 ── 表示(字符编码、JSON、压缩、加密)与会话管理
- 改成 HTTPS ── 什么看不见了,慢了多少(实测)
- 密钥与证书 ── 共享密钥、证书链、验证失败的 4 种情况(实测)
- 仍然看得见的 ── 名字(SNI)以及隐藏它的 ECH,证书有效期变短的趋势
第 5、6 层位于应用和 TCP 之间
第 6 层是表示层。同样是“交通费”,如果不按和对方相同的规则排列,就读不出来。现在的规则是:文字用 UTF-8,数据用 JSON,压缩用 gzip。
# “交通费”的日文原文「交通費」用 UTF-8 是 9 字节(第 7 讲封面的字节栏里也有)
交通費 → e4 ba a4 e9 80 9a e8 b2 bb
# 用 gzip 压缩 2 MB 的 CSV(同一行重复,所以压缩得极小)
2,097,152 bytes → 6,179 bytes
第 5 层是会话管理层,负责会话的开始、结束以及中断后的恢复。它分散在 TCP 连接、HTTP 的 Cookie、TLS 的会话恢复等功能里。而加密也是表示方式的一种。TLS 做的就是横跨这两层的工作。
一览无余的申请
在第 7 讲的结尾,我们发现申请的 JSON 在 Wireshark 里能原样读出。现在在记录文件里查找申请的文字。
# 在记录文件里查找申请的文字
HTTP '"amount":1280' 找到
HTTP 'POST /api/expenses' 找到
HTTPS '"amount":1280' 找不到
HTTPS 'POST /api/expenses' 找不到
HTTPS 'expenses.example' 找到 ← Client Hello SNI
HTTP 的记录里原样包含 URL 和金额。经过途中的路由器、同一个 Wi-Fi 里的某人、运营商的设备时,都是以可读的状态运送的。能读,就意味着也能改写后再往下传。HTTPS 的记录里,URL 和金额都找不到了。只有 expenses.example 这个名字例外(第七章)。
TLS 的三个承诺
改成 HTTPS
给服务器配上名字为 expenses.example 的证书,用 Node.js 的 https 在 3443 号开设申请 API(.example 是为说明而保留的名字)。证书由笔者制作的练习用 CA“Kiji Practice Root CA”颁发。电脑上的 curl 用 --cacert 告知了这个 CA。
| 连接完成 | TLS 招呼完成 | 回复的第一个字节 | 往返次数 | |
|---|---|---|---|---|
| HTTP | 0.041 秒 | — | 0.083 秒 | 2 |
| HTTPS(TLS 1.3) | 0.041 秒 | 0.085 秒 | 0.127 秒 | 3 |
| HTTPS(TLS 1.2) | 0.041 秒 | 0.126 秒 | 0.168 秒 | 4 |
在往返 0.041 秒的线上,HTTP 到收到回复用 2 个往返。HTTPS(TLS 1.3)因 TLS 招呼多了 1 个往返,共 3 个;旧的 TLS 1.2 多了 2 个往返,共 4 个。加密计算本身只花了几毫秒。慢的真正原因不是计算,而是往返。
# TLS 1.3(单程 20 ms 的线):TCP 招呼之后,TLS 招呼只要 1 个往返。申请(第 9 个)在密文里 1 0.000 pc → srv TCP [SYN] 2 0.041 srv → pc TCP [SYN, ACK] 3 0.041 pc → srv TCP [ACK] 4 0.043 pc → srv TLSv1 Client Hello (SNI=expenses.example) 5 0.083 srv → pc TCP [ACK] 6 0.084 srv → pc TLSv1.3 Server Hello, Change Cipher Spec, Application Data, Application Data, Application Data, Application Data 7 0.084 pc → srv TCP [ACK] 8 0.085 pc → srv TLSv1.3 Change Cipher Spec, Application Data 9 0.086 pc → srv TLSv1.3 Application Data 10 0.126 srv → pc TLSv1.3 Application Data, Application Data 11 0.130 srv → pc TLSv1.3 Application Data # TLS 1.2:TLS 招呼要 2 个往返。申请(第 10 个)在那之后 1 0.000 pc → srv TCP [SYN] 2 0.041 srv → pc TCP [SYN, ACK] 3 0.041 pc → srv TCP [ACK] 4 0.042 pc → srv TLSv1 Client Hello (SNI=expenses.example) 5 0.083 srv → pc TCP [ACK] 6 0.083 srv → pc TLSv1.2 Server Hello, Certificate, Server Key Exchange, Server Hello Done 7 0.083 pc → srv TCP [ACK] 8 0.085 pc → srv TLSv1.2 Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message 9 0.128 srv → pc TLSv1.2 Change Cipher Spec, Encrypted Handshake Message 10 0.128 pc → srv TLSv1.2 Application Data 11 0.170 srv → pc TLSv1.2 Application Data
TLS 1.3 的招呼(实测时刻)
TLS 1.3 中,电脑在第一个 Client Hello 里就先送出密钥材料,所以收到服务器的回复(Server Hello)时就已准备好加密。Server Hello 之后连证书也已在密文中。TLS 1.2 交换材料还要再多一个往返。TLS 1.0 和 1.1 已在 2021 年被正式规定停止使用。
往返增加了,第 6 讲的“复用连接”就更有效。HTTPS 每次重新连接,TCP 和 TLS 要 2 个往返。keep-alive 和连接池必不可少(第 10 讲)。
密钥怎么定
新井加密的密钥怎么交给对方?用线发过去,途中不就被读走了?
古井密钥本身不发送。双方各自在手边算出相同的密钥。
不发送密钥,两边各自算出相同的密钥
电脑和服务器各自生成秘密数,只把由此算出的“可公开的数”放进 Client Hello 和 Server Hello 交换。把自己的秘密和对方的公开数组合起来,两边就得到相同的密钥。途中看到公开的数,也算不出密钥。秘密每次连接都重新生成并丢弃,所以即使之后服务器的密钥泄露,也解不开过去的记录(前向保密)。
# 用 openssl s_client 查看协商结果
pc$ openssl s_client -connect 203.0.113.20:3443 -servername expenses.example -CAfile ca.pem -brief
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN = expenses.example
Hash used: SHA256
Signature type: ECDSA
Verification: OK
Server Temp Key: X25519, 253 bits
# 证书链(0 是服务器,i: 是签名者)
0 s:CN = expenses.example
i:CN = Kiji Practice Root CA
Verify return code: 0 (ok)
# 服务器的证书
subject=CN = expenses.example
issuer=CN = Kiji Practice Root CA
notBefore=Oct 24 23:40:01 2026 GMT
notAfter=Jan 22 23:40:01 2027 GMT
DNS:expenses.example
用 openssl s_client 查看,这次连接是 TLS 1.3,密钥共享用 X25519,内容加密是 TLS_AES_256_GCM_SHA384。AES-256-GCM 在加密的同时附上“是否被篡改”的标记,三个承诺中的两个由它完成。剩下的一个,确认对方是不是真的,由证书负责。
对方是真的吗:证书
密钥共享再安全,如果对方是冒牌服务器,也只是在和冒牌货安全地对话。所以服务器要出示证书,声明“我是 expenses.example”。证书上写着名字、有效期、公钥,并附有可信 CA 的签名。
沿签名能否追溯到可信的根
真实网站的证书,是由操作系统和浏览器内置的“根 CA”经中间 CA 签名的。电脑确认名字是否一致、是否在有效期内、沿签名能否追溯到根这三点。本讲的练习用 CA 不在电脑的列表里,所以告诉 curl 让它信任。
验证失败的 4 种情况
我们故意制造了开发中一定会遇到的错误。名字不符、过期、自签名的证书都是为练习而做的。
# 不告诉练习用 CA 就连接
curl: (60) SSL certificate problem: unable to get local issuer certificate
# 用地址(IP)连接。证书上的名字是 expenses.example
curl: (60) SSL: no alternative certificate subject name matches target host name '203.0.113.20'
# 过期的证书(到 2026-09-29 为止)
curl: (60) SSL certificate problem: certificate has expired
# 自己给自己签名的证书
curl: (60) SSL certificate problem: self-signed certificate
# 告诉练习用 CA,用名字连接 → 验证成功
HTTP 404 (GET /api/expenses)
| 错误 | 哪项验证失败 | 正确的解决方法 |
|---|---|---|
| unable to get local issuer certificate | 沿签名追溯不到根 | 在服务器上也配置中间证书。公司内部 CA 则把它加入信任列表 |
| no alternative certificate subject name matches | 名字不一致 | 用证书上写的名字连接。把需要的名字写进证书 |
| certificate has expired | 已过期 | 更新,并改为自动更新(第八章) |
| self-signed certificate | 没有可信的签名 | 请 CA 颁发。开发用途可在本地建一个开发用 CA |
不要为了消除错误而关掉验证本身(curl 的 -k、Node.js 的 NODE_TLS_REJECT_UNAUTHORIZED=0、Python requests 的 verify=False 等)。即使加了密,也无法知道对方是不是真的,三个承诺就少了一个。开发时“先凑合”加上的设置留到正式环境的事故,从来没断过。
仍然看得见的东西
如封面照片所示,Client Hello 的 SNI 里原样写着想连接的对方名字 expenses.example。一台服务器承载许多名字的网站时,需要在加密之前知道该出示哪个证书。此外,对方的 IP 地址、通信量和时刻,即使用了 TLS,在途中也看得见。
为了隐藏这个名字,把 Client Hello 本身加密的 ECH(Encrypted Client Hello)于 2026 年 3 月成为标准 RFC 9849。不过,名字也会从 DNS 查询(第 9 讲)中泄露,两者都做到才能真正隐藏。
证书的有效期与自动更新
公开网站证书的最长有效期(CA/Browser Forum 的决议)
浏览器和 CA 的行业组织 CA/Browser Forum 决定分阶段缩短公开网站证书的有效期。从 2026 年 3 月 15 日起最长 200 天,从 2029 年 3 月 15 日起为 47 天。一年更新好几次,人工处理不现实,所以用 ACME 协议自动颁发和更新成为前提。免费 CA Let's Encrypt 也是用 ACME 发放证书的。
作为开发者,重要的是用监控和自动更新防止“证书过期导致停摆”的事故。有效期可以用 openssl x509 -noout -dates 或浏览器的锁形标记确认。
在自己的电脑上确认
| 想查的内容 | 方法 |
|---|---|
| TLS 版本与加密方式 | openssl s_client -connect example.com:443 -servername example.com -brief |
| 招呼的过程与证书 | curl -v https://example.com/ -o /dev/null |
| 招呼所需时间 | curl -o /dev/null -s -w '%{time_connect} %{time_appconnect}\n' URL |
| 证书有效期 | openssl x509 -in cert.pem -noout -dates |
| 信任的 CA 列表 | Mac:钥匙串访问,Windows:certmgr.msc,Linux:/etc/ssl/certs |
点击浏览器地址栏左边的标记,就能查看该网站的证书。确认一下颁发的 CA、名字和有效期吧。
结 — 一页总结与下一讲
- 第 6 层是表示(UTF-8、JSON、gzip、加密),第 5 层是会话管理。实际上融入在 TLS 和应用里。
- HTTP 的记录里能读出 URL 和金额,HTTPS 里找不到。
- TLS 的三个承诺:不被读取、发现篡改、对方是真的。
- HTTPS(TLS 1.3)比 HTTP 多 1 个往返,TLS 1.2 多 2 个。慢的原因不是计算,而是往返。
- 不发送密钥,而是交换可公开的数,两边各自计算(X25519),每次连接重新生成。
- 证书要验证名字、有效期、签名链。失败了也不能关掉验证。
- SNI 的名字、IP、通信量和时刻仍看得见。名字可用 ECH(RFC 9849)隐藏。证书有效期 2029 年缩短到 47 天。
新井说起来,curl 一直用 --resolve 写着“expenses.example 就是 203.0.113.20”,这不是作弊吗?
古井是作弊。其实有一套从名字查地址的机制。下一讲是 DNS,互联网的电话簿。
参考
- RFC 8446 — The Transport Layer Security (TLS) Protocol Version 1.3
- RFC 8996 — Deprecating TLS 1.0 and TLS 1.1
- RFC 5280 — Internet X.509 Public Key Infrastructure Certificate and CRL Profile
- RFC 9525 — Service Identity in TLS
- RFC 9849 — TLS Encrypted Client Hello (2026-03)
- RFC 8555 — Automatic Certificate Management Environment (ACME)
- RFC 2606 — Reserved Top Level DNS Names(.example)
- DigiCert — Public TLS certificates: 199-day validity(SC-081)
- DNSimple — SSL Certificate Validity Changes (2026–2029)
- Let's Encrypt — How It Works
- curl — SSL Certificate Verification
- Node.js — TLS (SSL)
记录来自笔者在云端 Linux(Ubuntu 24.04,内核 6.18)上用网络命名空间搭建的练习网络,于 2026 年 10 月 25 日抓取。电脑和服务器之间是第 6 讲的捣乱线(单程 20 毫秒)。证书由笔者用 OpenSSL 3.0.13 制作的练习用 CA 颁发(过期证书用 Python 的 cryptography 指定日期制作),服务器是 Node.js 22(OpenSSL 3.5),客户端是 curl 8.5.0。表中的数字都是从这些记录和当时命令的输出读出的实际数值。证书有效期的规定依据 DigiCert 和 DNSimple 关于 CA/Browser Forum 决议(SC-081)的公告。登场人物均为虚构。
古法网络入门 第8讲 — 第 5、6 层与 TLS / 2026.10