HTTP 报文是明文,路上任何一台设备都能读。 这不是「加密不够强」的问题,是根本没有加密——账号密码就躺在报文体里,谁都能看见。
第六章把首部翻了个遍,全是「怎么把话说清楚」。 从这一章开始换方向:怎么让话在传的路上不被人听见、不被冒名、不被改。
一、明文到底是什么样
Demo 里搭了一条「客户端 → 中间人 → 服务端」的链路,中间人只做一件事:把经过的字节存下来再转发。
结果很直白:请求共 97 字节,中间人存下 97 字节,一字不差。
打印出来就是一段可读的文本,正文里那行 user=bozai&pass=MySecret123&amount=100 清清楚楚。
注意中间人没有破解任何东西。
它不需要密钥、不需要算法、不需要算力——报文本来就不是密的,它只是「看见」了。
这是最容易被误解的一点:窃听不是高级攻击,是默认状态。
二、窃听点在哪儿
链路上任何一跳都算:你能连上的那个 Wi-Fi 热点、运营商的一段链路、机房里的镜像端口、 甚至同一台机器上另一个进程。 公共 Wi-Fi 是最常见的场景,因为它让「同一广播域」变成了「同一局域网」——抓包门槛降到装个软件就行。 还有一个更容易被忽略的:日志。 明文传输意味着中间的代理、网关、负载均衡器在转发时都能顺手记下来, 而这些日志往往存在比链路更松的地方。
三、为什么别自己造套壳
Demo 里用一个固定字节序列异或了一下,38 字节正文里能读出来的只剩 13 字节,看起来「加密」了。
但这只是把可读性藏起来,不是加密。
固定密钥的异或,攻击者只要猜到一段明文(比如 user=),就能直接反推出密钥,然后解开全部。
这叫已知明文攻击,是最基础的破法之一。
自己设计加密协议的历史上几乎全是事故:WEP、PPTP、SSL 3.0 的 POODLE、
各种「自定义加密」的 App 接口,都是同一个路子。正确的做法是用现成的 TLS,而不是发明一个。
四、动手 Demo:中间人一字不差地读走全部 97 字节
起一个真服务端,中间夹一层「只记账再转发」的代理; 客户端发一个带账号密码的 POST,看看中间人手上留下了什么。 然后对同一段正文做一次异或,量一量可读字符掉到多少。
def origin(raw, st):
return X.reply(u'HTTP/1.1 200 OK', [(u'Content-Type', u'text/html')], PAGE)
with X.hub(origin) as (po, ho):
def mitm(raw, st):
captured['raw'] += raw # 记账,不改一个字节
return X.ask(po, raw) # 原样转发
with X.hub(mitm) as (pm, hm):
req = X.req(u'POST', u'/pay', u'127.0.0.1', body=FORM)
out = X.ask(pm, req)
实跑输出:
--------------------------------------------------------------------
明文:中间那台机器能读出每一个字
--------------------------------------------------------------------
客户端 → 中间人 → 服务端,中间人把经过的字节全存下来。
请求共 97 字节,其中正文 38 字节
中间人存下 97 字节
把这些字节按文本打出来(这就是抓包工具里看到的):
POST /pay HTTP/1.1
Host: 127.0.0.1
Content-Length: 38
user=bozai&pass=MySecret123&amount=100
账号密码那一行就在里面。中间人不需要破解任何东西——它只是「看见」了。
如果换成加密的呢?同一段正文,用一个固定的字节序列异或一下:
明文 38 字节,能按 UTF-8 读出来的 38 字节
异或后 38 字节,能按 UTF-8 读出来的 13 字节
异或后的前 16 字节:b'\x1e\x18\x0e\x19V\t\x04\x11\n\x02M\x1b\n\x18\x18V'
这里只演示「不可读」这件事,异或本身**不是**加密——
固定密钥的异或,攻击者只要猜到明文片段就能反推出密钥。
真正的 TLS 用的是流加密或分组加密,密钥每次会话都不同。
重点在于:HTTP 的问题不是「别人能不能看懂」,而是「别人根本不用懂」——
报文是明文,路上任何一台设备想读就读。这就是 7.1.1 说的窃听风险。
请求 97 字节、正文 38 字节,中间人存下 97 字节——全拿到了。
打出来的四行里,第三行是 Content-Length: 38,第四行就是正文,账号密码都在里面。
抓包工具显示的和这个一模一样。
异或那一段:明文 38 字节里有 38 个可读字符,异或后只剩 13 个。
看起来像「加密了」,其实只是把字节打乱了。
所以这一节的结论不是「要加密」,而是「要知道明文等于公开」。在此基础上才谈得上为什么需要 TLS。
30 秒实验
- 把 FORM 里的密码改长一点,看中间人存下的字节数怎么变。
- 试着只用一次一密(密钥与正文等长且随机)改一遍,想想要怎么分发密钥。
- 想一想:如果只在登录那一页用 HTTPS,登录完切回 HTTP,会漏什么?
常见误区
- 「窃听需要很高的技术」——装个抓包软件就能看,明文就是公开。
- 「HTTPS 只是让页面变慢的装饰」——它解决的是被读、被冒名、被改三件事。
- 「异或一下就算加密了」——固定密钥的异或能被已知明文直接破掉。
自测三问(点开对答案)
1. HTTP 明文的风险本质是什么? —— 报文不加密,链路上任何一跳都能直接读取,不需要破解。 2. 为什么固定密钥异或不算加密? —— 猜到任意一段明文就能反推密钥,进而解开全部内容。 3. 公共 Wi-Fi 为什么特别危险? —— 同一局域网的设备可以直接抓包,门槛极低。被读是第一种风险。第二种更阴——你连上的那台机器,可能根本不是它自称的那台。
- ☐ (本节完,下一节 7.1.2 不验证通信方的身份就可能遭遇伪装)