摘要

  • RFC 1969 把前一只 PPP 包的最后一个密文块当成下一只包的 CBC 输入,节省了逐包重建起点的代价,也让解密能力依赖包外状态。
  • 初始 64 位 nonce 在 ECP 协商中明文传递,再经共享 DES 密钥变成第一只包的链式输入;16 位序列号暴露顺序缺口,但二者都不认证端点,也不保护密文不被修改。
  • 若 N-1 丢失,N 因缺少前一密文末块而无法解密;只要 N 的密文本身收到,接收端便可用其末块解开 N+1。链条恢复了,N-1 与 N 的明文没有回来。

一只不能读、却不能扔的包

许多恢复机制会把出错数据立即丢弃。RFC 1969 给出一个反直觉的例外:即使包 N 已经无法解成明文,它的密文仍有价值。只要保留最后 64 位,这个末块就能成为 N+1 的 CBC 起点。

原因在于 RFC 1969 没有让每个 PPP 包重新开始。第一只包之后,发送端取前一只包的最后密文块作为新包的 C[0];接收端必须按相同顺序做同一件事。若 N-1 消失,接收端缺少解开 N 所需的输入。可是 N 的密文是完整收到的,所以其最后一块不需要先被解密,也可以直接供 N+1 使用。

于是一次丢包形成两只包宽的明文空洞。第一只是根本没有到达的 N-1,第二只是到了却打不开的 N。从 N+1 起,链式计算重新成立。这不是纠错,不是重传,更不是把空洞填回去;它只是把后续解密能力恢复到可运行状态。

CBC 为什么越过了包边界

RFC 1969 信息记录显示,它于 1996 年 6 月以 Informational 发布,目标是在 PPP Encryption Control Protocol 已经协商完成后,给出一种具体的 DES 承载协议。DES 以 64 位分组工作,CBC 在一个包内让每块明文先与前一密文块异或,再用 56 位密钥加密。RFC 1969 进一步把这条链延伸到了下一只包。

连续链有两项实际好处。它不必在每个加密包里携带新的初始化向量;相同的明文片段位于不同包时,也不会因为逐包回到同一起点而轻易生成同样的密文模式。代价是,PPP 的包边界不再是密码状态边界。独立截获一只包,并不等于拥有解开它的全部输入。

这正是效率与恢复耦合。省下逐包状态,就必须保存跨包状态;让密文流连续,就必须知道包在加密序列中的顺序。本文因此不重述 RFC 1968 的通用 ECP 状态机。我们从 ECP 已到 Opened、单向算法已选定处开始,只问 DESE 数据路径实际留下什么证据。

nonce 在阳光下,密钥在规范之外

DESE 配置选项总长 10 字节,其中 8 字节是 Initial Nonce。接收方把 nonce 提供给对端,用于对端发来的第一只加密包。RFC 建议每次 ECP 协商都换一个值,以降低重放风险;它甚至给出“1970 年以来的秒数加当前秒内纳秒数”的例子。

nonce 明文传输并非缺陷误写,因为它不是秘密。第一只包的 C[0] 是 E[k](Initial Nonce):公开 nonce 先经过双方掌握的共享 DES 密钥。接收方知道自己发出的 nonce 和同一密钥,才能重建相同起点。第二只包以后,nonce 退出数据路径,前包的密文末块接过位置。

这三个对象承担不同职责。nonce 使每次协商的起点有机会不同;密钥承担保密;前包末块维持连续状态。把 nonce 当作密钥,会忽略它公开可见;把成功协商当作身份认证,又越过了 RFC 1969 的边界。规范明确说共享秘密如何到达双方不在范围内,通常依靠人工配置,也可能参考 PPP authentication 或 Multilink Endpoint Identifier。

所以,一份抓包能证明对端提出了某个 nonce,不能证明某家机构、某个用户或某台受信设备持有预期密钥。一次成功解密说明某端具有兼容密钥和状态,却仍不是具名主体签发的身份证明。

16 位序列号只负责把缺口照亮

ECP 进入 Opened 后,DESE 第一只数据包的显式序列号为 0,随后在 16 位空间递增。接收端比较相邻值,发现不连续就知道加密序列出现缺口。没有这个字段,跨包链丢失后更难判断当前密文应接在哪个状态上。

序列号并没有参加 RFC 1969 定义的消息认证。它在包中明文可见,也没有独立完整性保护。它能回答“观察到的编号是否连续”,不能回答密文有没有被改、缺口为何产生、发送方是谁,甚至不能单独区分网络丢包、重排、主动干预和本地丢弃。

能解密也不是防篡改证书。没有额外完整性机制的 CBC 只提供有限的保密属性,密文可被有结构地改变。RFC 2419 在取代原规范时把这条边界说得更直白:只处理 confidentiality,不提供 integrity、authentication 或 nonrepudiation,也不保证抵抗 replay、cut-and-paste 与 active tampering。

因此,序列号不是没用,而是用途必须保持狭窄。它是单一 DESE 方向中的顺序告警,不是安全封印。

八字节尺子如何挤压 MRU

DES 只吃八字节整块,PPP 的 Protocol 与 Information 字段却未必恰好对齐。RFC 1969 允许对带有内部长度的 IP、IPX、XNS、CLNP 追加随机尾字节,因为接收端可按内部长度截断;对桥接等可能被尾随垃圾改变解释的协议,则引入自描述填充。

这套自描述方法来自 RFC 1570。它还有一个容易漏算的角落:如果原文已经八字节对齐,但最后几个字节恰好长得像填充尾巴,为避免接收端误删,发送方可能再加完整八字节。填充不是单纯“向上取整”,其上限由末尾内容决定。

RFC 给出的 MRU 算例很具体。若 PFC 已协商,原 MRU 又恰为八的倍数,满尺寸数据加一字节 Protocol 后需要七字节填充;再加两字节序列号和外层协议效应,DESE Information field 可比原包大 10 字节。PPP 各配置选项相互独立,协商 DESE 不会自动把 MRU 调大。

这也暴露 RFC 1969 的互操作薄弱处:发送方必须判断哪些内层协议有可靠长度、哪些会被垃圾尾巴破坏。不同实现若分类不同,密码计算都正确,最后仍可能对明文边界产生分歧。

RFC 2419 取代它,不是因为换成了 3DES

RFC Editor 的 RFC 2419 记录 明确标注其取代 RFC 1969。后继文档第 8 节也直接列出差异:所有明文包统一使用一种自描述填充;这套规则与 PPP 单独的 SDP 选项是否协商无关;最大填充固定为 8;接收端若发现填充字节不符合模式,应丢弃帧。

由于新旧规则可能让同一密文解出后被不同解释,DESE-bis 获得新的 ECP 类型 3。旧 DESE 类型 1 被弃用,遵循 RFC 2419 的实现不得主动提出,并必须拒绝收到的旧类型。今天的 IANA PPP 编号表 仍保留这条谱系。

RFC 2419 还进入 Standards Track,并把安全边界写得更完整。它没有用 3DES 替换 DES;PPP Triple-DES 另见 RFC 2420,使用 ECP 类型 2。把两者混同,会掩盖真正的取代原因:统一填充和解释规则,用新编号阻止静默版本混配,再加上更清楚的规范文字。

出口限制留在页边的一道痕迹

RFC 1969 说 DES 已被广泛研究和实现,随后注明:虽然引用资料中可找到 ECB 模式的源代码,美国出口法律禁止把“可直接编译”的源码放进本文档。RFC 2419 两年后仍保留这句话。

这是一条可以直接从原文验证的历史痕迹。它说明 1990 年代的密码软件出口限制影响了标准文档能携带什么内容。但它不能证明某个厂商是否出口、某段代码是否合法,也不能从“文档没有源码”倒推出实际部署或安全强度。缺失的代码是文档来源史的一部分,不是运行证据。

IETF Datatracker 与 RFC Editor 能证明文档身份、状态和取代关系。抓取 RFC 1969 errata 页面 时未得到可用正文,所以本文不宣称“没有勘误”。

把一盏绿灯拆成五张收据

可信记录至少分五层。第一层是 ECP 配置:方向、DESE 类型、nonce 和所引用的密钥身份,但不记录密钥本身。第二层是传输顺序:16 位序列号与缺口。第三层是密码状态:本包实际使用哪一个前置密文末块。第四层是明文重建与填充校验。第五层才是 RFC 1661 下 PPP 是否接受、是否交给上层,以及应用是否回执。

DESE 最多走到第四层。密码运算成功,帧仍可能因填充或 PPP 规则被拒;PPP 上交成功,应用仍可能失败。反过来,序列缺口可以准确预测多一只不可读包,却说不出业务损失。

Heng Lu 的 Running-Code Primacy 要求书面状态与可观察运行在扩张主张前相遇;Minimum Initial Specification 要求只给字段完成其功能所需的最小权威;Reality Layers 防止把符号直接抬升为结果,Reality, Not Advocacy 则要求揭示结构而不虚构动机。这些文章是编辑方法,不是 RFC 历史证据。