Old-School Networking / 2026 ・ No.08

古法网络入门

第8讲 第 5、6 层与 TLS ── 给申请上锁

发布

Wireshark 的界面。列表里排着 TCP 招呼、第 4 个 Client Hello (SNI=expenses.example)、Server Hello、Application Data,最后是 FIN 和 RST。选中第 4 个,详情里展开 Transport Layer Security,选中 Extension: server_name 中的 Server Name: expenses.example。字节栏里 expenses.example 的文字变成蓝色
用 HTTPS 发送申请时的记录。展开第 4 个 Client Hello,只有想连接的对方名字(SNI)expenses.example 因为在加密之前而可读(在笔者于云端搭建的练习用网络中,于 2026 年 10 月 25 日记录,用 Wireshark 4.2.2 打开后截图。界面保持英文。窗口上方的标题栏和小标题是为了说明而后加的)

到目前为止,申请都是以途中任何人都能读到的状态运送的。这一讲讲的是夹在 TCP 和应用之间、给申请上锁的 TLS,它也是 HTTPS 里那个 S 的真身。
我们搭建练习用的 CA(颁发证书的角色),把证书交给服务器。改成 HTTPS 后,什么看不见了,什么仍然看得见,又慢了多少?我们还会故意制造验证失败的 4 种情况来确认。

一

表示与会话这两层

新井路线图第 8 站写的是“第 5、6 层”,为什么放在一讲里?

古井在实际的互联网里,第 5 层和第 6 层几乎不是独立的,而是融入在 TLS 和应用里。所以一起讲。

全 12 讲路线图(当前:第 8 讲)

1 全景图 2 物理层 3 数据链路层 4 IP 地址 5 路由器 6 TCP 7 UDP・套接字 8 TLS 9 DNS 10 HTTP 11 开发者的网络 12 故障排查 当前位置 全景图 第1层 第2层 第3层 第4层 第5・6层 第7层 实战
  • 第 5、6 层 ── 表示(字符编码、JSON、压缩、加密)与会话管理
  • 改成 HTTPS ── 什么看不见了,慢了多少(实测)
  • 密钥与证书 ── 共享密钥、证书链、验证失败的 4 种情况(实测)
  • 仍然看得见的 ── 名字(SNI)以及隐藏它的 ECH,证书有效期变短的趋势

第 5、6 层位于应用和 TCP 之间

7 第 7 层 应用 HTTP、申请 API 6 第 6 层 表示 UTF-8、JSON、gzip、加密 5 第 5 层 会话 开始・结束・恢复 4 第 4 层 传输 TCP、UDP TLS 横跨两层 在实际的互联网中,第 5、6 层并不是独立的层, 而是融入在 TLS、应用和库里

第 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
把 Wireshark 的详情和字节栏上下并排两份。上面是 HTTP 的申请,字节栏右侧可以读出 POST /api/expenses、Host、Content-Type,以及 JSON 的 date 和 amount 1280。下面是 HTTPS 的申请,只显示 Transport Layer Security,字节栏右侧是无意义的符号
同一个申请分别用 HTTP 和 HTTPS 发送的比较。用 HTTPS 时,URL 和 JSON 在途中都读不出来(在笔者于云端搭建的练习用网络中,于 2026 年 10 月 25 日记录,用 Wireshark 4.2.2 打开后截图。界面保持英文。窗口上方的标题栏和小标题是为了说明而后加的)

HTTP 的记录里原样包含 URL 和金额。经过途中的路由器、同一个 Wi-Fi 里的某人、运营商的设备时,都是以可读的状态运送的。能读,就意味着也能改写后再往下传。HTTPS 的记录里,URL 和金额都找不到了。只有 expenses.example 这个名字例外(第七章)。

TLS 的三个承诺

不被读取 加密 对途中的任何人 内容都是无意义的字节 发现篡改 篡改检测 哪怕改了 1 位 也拒绝接收 对方是真的 证书 确认名字、期限、签名 之后再对话 三者齐备才算安全。就算加了密,对方是冒牌的也没有意义 TLS 只保护线上,不保护两端的电脑或服务器内部
三

改成 HTTPS

给服务器配上名字为 expenses.example 的证书,用 Node.js 的 https 在 3443 号开设申请 API(.example 是为说明而保留的名字)。证书由笔者制作的练习用 CA“Kiji Practice Root CA”颁发。电脑上的 curl 用 --cacert 告知了这个 CA。

连接完成TLS 招呼完成回复的第一个字节往返次数
HTTP0.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 的招呼(实测时刻)

电脑 服务器 SYN 0 SYN+ACK ACK, Client Hello 43 ms Server Hello … Finished, POST 86 ms 201 TCP 招呼 1 个往返 TLS 招呼 1 个往返(1.2 要 2 个) 申请与回复 1 个往返 Server Hello 之后已经在密文中(包括证书)
把两个 Wireshark 数据包列表上下并排。上面是 TLS 1.2:Client Hello、Server Hello, Certificate, Server Key Exchange, Server Hello Done、Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message、Change Cipher Spec, Encrypted Handshake Message 之后,第 10 个在 0.128 秒出现 Application Data。下面是 TLS 1.3:Client Hello、Server Hello, Change Cipher Spec, Application Data 之后,第 9 个在 0.086 秒出现 Application Data
TLS 1.2 与 1.3 招呼的比较。1.3 的招呼少了一个往返,开始发送申请早了 0.042 秒(在笔者于云端搭建的练习用网络中,于 2026 年 10 月 25 日记录,用 Wireshark 4.2.2 打开后截图。界面保持英文。窗口上方的标题栏和小标题是为了说明而后加的)

TLS 1.3 中,电脑在第一个 Client Hello 里就先送出密钥材料,所以收到服务器的回复(Server Hello)时就已准备好加密。Server Hello 之后连证书也已在密文中。TLS 1.2 交换材料还要再多一个往返。TLS 1.0 和 1.1 已在 2021 年被正式规定停止使用。

POINT

往返增加了,第 6 讲的“复用连接”就更有效。HTTPS 每次重新连接,TCP 和 TLS 要 2 个往返。keep-alive 和连接池必不可少(第 10 讲)。

四

密钥怎么定

新井加密的密钥怎么交给对方?用线发过去,途中不就被读走了?

古井密钥本身不发送。双方各自在手边算出相同的密钥。

不发送密钥,两边各自算出相同的密钥

电脑 秘密 a 不放到线上 可公开 A 相同的密钥 服务器 秘密 b 不放到线上 可公开 B 相同的密钥 在 Client Hello 和 Server Hello 中交换 途中能看到的只有 A 和 B 由此算不出密钥 a + B b + A 两边得到相同的密钥 本讲实测用的是 X25519。每次连接都重新生成,即使之后秘密泄露,也读不了过去的记录

电脑和服务器各自生成秘密数,只把由此算出的“可公开的数”放进 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 (本讲练习中省略) 签名 服务器的证书 expenses.example 签名 信任的 CA 列表 内置于操作系统、浏览器 电脑要确认的事 ✓ 名字是否一致(expenses.example) ✓ 是否在有效期内(到 2027-01-22) ✓ 沿签名能否追溯到根 练习用 CA 不在电脑的列表里,所以用 --cacert 告诉 curl 真实的网站从 Let's Encrypt 等 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
POINT

不要为了消除错误而关掉验证本身(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 的决议)

〜2026-03-14 398 天 2026-03-15 起 200 天 2027-03-15 起 100 天 2029-03-15 起 47 天 有效期越短,被盗的密钥或错误证书造成的危害就越短 人工更新将不再现实,自动更新(ACME)成为前提

浏览器和 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、名字和有效期吧。

结 — 一页总结与下一讲

  1. 第 6 层是表示(UTF-8、JSON、gzip、加密),第 5 层是会话管理。实际上融入在 TLS 和应用里。
  2. HTTP 的记录里能读出 URL 和金额,HTTPS 里找不到。
  3. TLS 的三个承诺:不被读取、发现篡改、对方是真的。
  4. HTTPS(TLS 1.3)比 HTTP 多 1 个往返,TLS 1.2 多 2 个。慢的原因不是计算,而是往返。
  5. 不发送密钥,而是交换可公开的数,两边各自计算(X25519),每次连接重新生成。
  6. 证书要验证名字、有效期、签名链。失败了也不能关掉验证。
  7. SNI 的名字、IP、通信量和时刻仍看得见。名字可用 ECH(RFC 9849)隐藏。证书有效期 2029 年缩短到 47 天。

新井说起来,curl 一直用 --resolve 写着“expenses.example 就是 203.0.113.20”,这不是作弊吗?

古井是作弊。其实有一套从名字查地址的机制。下一讲是 DNS,互联网的电话簿。

参考

记录来自笔者在云端 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

← 返回文章列表