📘 第七章 确保Web安全的HTTPS
📄 一句话概括

一次完整握手分五步:打招呼、选参数、发证书、验身份并算密钥、开始加密传数据。 TLS 1.3 把中间两步并成一趟,所以只要一次往返就能发数据。

📝 本节和前面几节的关系

前四节把零件都拆开看过了,这一节按顺序装回去,并且量出每一步花多少时间。

一、五个阶段

  1. 客户端发 ClientHello:支持的版本、套件列表、一个随机数、以及密钥交换用的份额。
  2. 服务端回 ServerHello:选定的版本与套件、自己的随机数、自己的份额。
  3. 服务端发 Certificate:证书链,客户端顺着它追到受信根。
  4. 双方各自用两份份额算出同一份会话密钥,互发 Finished 校验握手没被改。
  5. 之后所有 HTTP 报文都装进 TLS 记录里加密传输。 第 4 步那句「算出同一份」是整件事的巧妙之处:双方各出一个秘密,只用公开的数交换, 最后算出同一个结果。中间人看得见交换的全过程,却算不出那个结果——这就是 Diffie-Hellman 的本事。

二、实测的耗时构成

Demo 真连 www.example.com,分三段计时:DNS 解析 5.4 毫秒、TCP 三次握手 1236.0 毫秒、 TLS 握手 478.8 毫秒,合计 1720.2 毫秒。 TCP 那段特别长,是因为到这台服务器的网络本身就慢(跨洋),不是协议的问题。 这个数只能当「本机到该站的实测」看,不能当通用结论。 谈成的是 TLSv1.3、套件 TLS_AES_256_GCM_SHA384,证书名 example.com 与 *.example.com, 有效期到 2026-12-25。 第一次请求 41 字节、收 937 字节、耗时 451.7 毫秒;同一条连接上再要一次,199.3 毫秒。 差的这 250 毫秒就是「省掉三次握手」省下来的——这也是长连接的价值。

三、TLS 1.3 为什么快

TLS 1.2 的握手要两趟往返:先谈参数,再换密钥。 TLS 1.3 把这两趟并成一趟,客户端甚至在收到 ServerHello 之后就能直接发数据(0-RTT 复用)。 它还砍掉了一批老旧算法(RSA 密钥交换、CBC 模式、RC4、SHA-1),套件从几十个缩到五个。 这不是「功能变少」,是「不给选错的机会」。 会话复用也值得一提:第二次连同一个站点,可以用上次的 PSK 直接恢复,省掉证书验证那一大段。 Demo 里那 199 毫秒虽然还是新连接,但没有再付证书和密钥交换的钱。

四、动手 Demo:分三段计时,再把同一条连接用第二次

真连一次公网站点,分别计 DNS、TCP、TLS 三段耗时;打印谈成的版本、套件、证书名; 然后发一个请求、再在同一条连接上发第二个,比较两次往返。

t0 = time.time()
infos = socket.getaddrinfo(host, 443, proto=socket.IPPROTO_TCP)
t_dns = time.time() - t0

t0 = time.time()
s = socket.create_connection((ip, 443), timeout=8)
t_tcp = time.time() - t0

ctx = ssl.create_default_context()
ctx.set_alpn_protocols([u'http/1.1'])
t0 = time.time()
t = ctx.wrap_socket(s, server_hostname=host)     # 握手在这里发生
t_tls = time.time() - t0

实跑输出:

--------------------------------------------------------------------
一次完整握手,分几个阶段
--------------------------------------------------------------------
  目标 www.example.com,解析到 172.66.147.243

  分阶段计时(单位毫秒):
    DNS 解析              5.4  域名换 IP
    TCP 三次握手         1236.0  建立连接
    TLS 握手            478.8  协商密钥 + 验证证书
    合计               1720.2

  握手谈成了什么:
    协议版本      TLSv1.3
    加密套件      TLS_AES_256_GCM_SHA384
    套件所属协议  TLSv1.3
    会话是否复用  否
    证书里的名字  example.com、*.example.com
    证书有效期至  Dec 25 22:56:35 2026 GMT

  第一次请求的往返时间:
    请求 41 字节,收到 937 字节,耗时 451.7 毫秒

  同一条连接上再要一次(省掉 DNS、TCP、TLS 三段):
    收到 937 字节,耗时 199.3 毫秒
    第二次数值明显更小:这就是长连接和 HTTPS 一起用的意义——
    握手只付一次,后面的请求只付往返。

  把五个阶段按顺序排一遍,就是书上那张图:
    1. 客户端发 ClientHello:支持的版本、套件、一个随机数、密钥交换的份额
    2. 服务端回 ServerHello:选定的版本与套件、自己的随机数、自己的份额
    3. 服务端发 Certificate:证书链,客户端顺着它追到受信根
    4. 双方各自用份额算出同一份会话密钥,互发 Finished 校验握手没被改
    5. 之后所有 HTTP 报文都装进 TLS 记录里加密传输
  TLS 1.3 把 2 和 3 并成一趟,所以只需一次往返就能开始发数据;
  TLS 1.2 要两趟。这就是 1.3 更快的主要原因。

三段耗时 5.4/1236.0/478.8 毫秒,合计 1720.2 毫秒。TCP 那一段长是网络距离造成的,不是协议慢。 谈成 TLSv1.3 加 TLS_AES_256_GCM_SHA384; 证书覆盖 example.com 和 *.example.com,到期日 2026-12-25。 第一次请求 451.7 毫秒,第二次 199.3 毫秒,省掉的就是握手。 注意第二次收的字节数一样多(都是 937),差的全在时间上。 这也解释了「为什么首屏慢、点进去快」:首屏要付三次握手,点进去的请求复用同一条连接,只付往返。

30 秒实验

  1. 把 host 换成一个离你近的站,看 TCP 那段的毫秒数掉多少。
  2. 在同一条连接上连发五次,把平均耗时和第一次比。
  3. 想一想:为什么 0-RTT 复用有「重放攻击」的隐患?

常见误区

  1. 「TLS 握手要两趟往返」——那是 1.2,1.3 只要一趟。
  2. 「HTTPS 每发一个请求都要重新握手」——长连接只握手一次。
  3. 「握手慢是加密算得慢」——主要是往返次数和网络延迟,加密计算只占几毫秒。
自测三问(点开对答案) 1. 完整握手的五个阶段按顺序是什么? —— ClientHello、ServerHello、Certificate、算密钥并 Finished、加密传数据。 2. TLS 1.3 相比 1.2 快在哪? —— 把两次往返并成一次,并砍掉一批老旧算法。 3. 同一条连接上第二个请求为什么快得多? —— 省掉了 DNS、TCP 三次握手和 TLS 握手,只剩一次往返。

第七章到这儿收尾。下一章换个话题:谁在使用 HTTP——从浏览器到各种设备。

相关笔记