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

TLS 夹在应用层和传输层之间:HTTP 报文整段塞进 TLS 记录当载荷,TLS 再交给 TCP。 所以「身披外壳」是字面意思——里层一个字都没改。

📝 本节和前面几节的关系

上一节说 HTTPS 加了三样东西,这一节说这三样东西加在哪一层。 位置决定了它对上层协议是透明的。

一、位置:在 HTTP 和 TCP 之间

OSI 那一套在这儿只要记一件事:TLS 不是传输层协议,它跑在 TCP 之上、应用协议之下。 对 TCP 来说它是普通数据流,对 HTTP 来说它是可靠的双向字节管道。 正因为这个位置,它对上层是透明的:SMTP、FTP、WebSocket 都能套,不需要各自改协议。 你看到的 https、smtps、ftps 都是同一个套法。 也正因为这个位置,它对下层是不透明的:中间设备只能看到「一段加密数据」,看不到方法、URI、首部。 这既是隐私保护,也是某些网络管理的麻烦来源。

二、实测一次真握手

Demo 真连 www.example.com:443,谈成的是 TLSv1.3,套件 TLS_AES_256_GCM_SHA384, ALPN 协商成 http/1.1。 证书主题是 CN=example.com,SAN 里有 example.com 和 *.example.com, 有效期到 2026 年 12 月 25 日。 握手字节合计 6698(上行 5085、下行 1617),耗时 435 毫秒; 之后发 41 字节的 GET /,收回 937 字节的明文响应——解开之后头一行还是 HTTP/1.1 200 OK。 三层结构一目了然:中转那头只数得到密文字节,HTTP 报文只有两端解密之后才看得见, 正文和明文 HTTP 里没有任何区别。

三、ALPN 决定了壳里装什么

ALPN 是握手时顺带谈的一件事:「我支持的协议有这些」,服务端挑一个。 所以同一个 443 端口上,可能跑 HTTP/1.1,也可能跑 HTTP/2。 Demo 里故意只报 http/1.1,因为报 h2 之后服务端回的就是二进制帧, 用文本方式看会是一堆乱码——那不是出错,是我们拿 HTTP/1.1 的解析器去读 HTTP/2。 这也解释了为什么 HTTP/2 事实上总跑在 TLS 上:一是浏览器只支持加密的 h2, 二是加密之后中间设备无法按自己的理解改写报文,协议升级才不会被老旧设备搅乱。

四、动手 Demo:真连一次,把三层拆开看

通过一层透明中转连 www.example.com:443,打印协议版本、套件、ALPN、握手字节和证书内容; 然后在同一条连接上发一个 HTTP/1.1 请求,看解密之后的报文长什么样。

with counter_proxy(443, u'www.example.com') as (cp, cnt):
    ctx = ssl.create_default_context()
    ctx.set_alpn_protocols([u'http/1.1'])   # 不要 h2,否则回的是二进制帧
    s = socket.create_connection(('127.0.0.1', cp), timeout=8)
    with ctx.wrap_socket(s, server_hostname=u'www.example.com') as t:
        print(t.version(), t.cipher(), t.selected_alpn_protocol())
        print(t.getpeercert()[u'subjectAltName'])
        t.sendall(X.req(u'GET', u'/', u'www.example.com'))

实跑输出:

--------------------------------------------------------------------
剥到最里层,还是那套 HTTP
--------------------------------------------------------------------
  真的连一次 www.example.com:443:
    协议版本        TLSv1.3
    加密套件        TLS_AES_256_GCM_SHA384
    套件三元组      ('TLS_AES_256_GCM_SHA384', 'TLSv1.3', 256)
    ALPN 协商结果   http/1.1
    握手耗时        491.3 毫秒
    握手字节        上行 5084、下行 1617,合计 6701

    证书主题        ((('commonName', 'example.com'),),)
    证书里的名字    example.com、*.example.com
    生效期          Sep 26 22:49:11 2026 GMT 至 Dec 25 22:56:35 2026 GMT

    发出去的请求 41 字节,收回来的响应 937 字节
    解密之后头三行:
      HTTP/1.1 200 OK
      Date: Sun, 25 Oct 2026 06:49:31 GMT
      Content-Type: text/html; charset=utf-8

  三层结构一目了然:
    TLS 记录层   中转那头只数到「握手期间下行 1617 字节的密文」
    HTTP 报文    只有两端解密之后才看得见,还是 `GET / HTTP/1.1` 那一套
    应用数据     网页正文,跟在明文 HTTP 里没有任何区别

  所以「HTTPS 是身披 SSL 外壳的 HTTP」这句话是字面意思:
  HTTP 的报文格式一个字节都没改,只是被整段塞进 TLS 记录里当载荷。
  这也解释了为什么 HTTP/2 必须跑在 TLS 上——
  中间设备看不懂密文,就没法按自己的理解去改写报文了。

协议 TLSv1.3,套件 TLS_AES_256_GCM_SHA384,ALPN 谈成 http/1.1。 握手 6698 字节(上行 5085、下行 1617),耗时 435 毫秒;其中下行那 1617 字节里就有服务端的证书。 证书 SAN 是 example.com 和 *.example.com,有效期到 2026-12-25。 注意 *.example.com 只覆盖一级子域,a.b.example.com 不算。 解密后头一行是 HTTP/1.1 200 OK——和明文 HTTP 一模一样。这就是「身披外壳」的证据:里层根本没变。

30 秒实验

  1. 把 ALPN 改成 ['h2','http/1.1'],看服务端选哪个、响应变成什么样。
  2. 试着连一个只支持 TLS 1.2 的站点,看套件和握手字节怎么变。
  3. 想一想:为什么 SMTP 也能套 TLS,而不用另造一个协议?

常见误区

  1. 「HTTPS 是把 HTTP 改了」——报文格式完全没改。
  2. 「TLS 属于传输层」——它在 TCP 之上、应用协议之下。
  3. 「HTTP/2 必须加密是技术限制」——是浏览器和生态的约定。
自测三问(点开对答案) 1. TLS 在协议栈的哪个位置? —— 应用层和传输层之间,跑在 TCP 之上。 2. ALPN 是干什么的? —— 握手时协商壳里装哪个应用协议,比如 http/1.1 或 h2。 3. 为什么说 TLS 对上层透明? —— 任何应用协议都能整段塞进 TLS 记录,不需要各自改造。

外壳看清了。接下来钻进壳里,看那两把钥匙是怎么分工的。

相关笔记