切换深浅色
第 14 章 对称密码学
14.1 算法是公开规则,密钥选择具体变换
已经拥有共同秘密的两个端点,需要反复保护大量数据。对称密码的基本安排是:发送者和接收者使用同一把秘密密钥,或者使用能够由共同秘密导出的对应密钥,完成数据保护和验证。网络上不必为每个明文字节重新交互一次;双方按约定处理字节即可。
明文是准备受到保护的字节串,密文是加密得到的字节串,解密是在正确输入条件下恢复明文的过程。它们不一定是文字。一段HTTP正文、另一个协议的请求头、一个已经加密的网站TLS记录,都可以成为外层加密的明文。因此“明文”是相对于当前这一层而言,不能简单等同于人眼能读的中文或英文。
算法规定计算步骤,密钥决定这套步骤中的具体变换。算法通常是公开的;通信者不能依赖攻击者不知道程序怎样工作来代替密钥管理。以AES为例,密钥长度可以是128、192或256位,分别是16、24或32字节,但这不意味着把任意16字节易猜文本放进去就获得128位不可预测性。FIPS 197规定算法的这些尺寸。
如果一个秘密实际上只能从100万个等可能候选里产生,那么猜中它需要考虑的候选约为2的20次方,而不是因存储它的容器有256位,就突然变成2的256次方。这里用“不确定性”描述攻击者面对多少可能值;密码学中的熵是刻画这种不确定性的量。真实分布往往不均匀,常见口令还会被优先猜测,因此字符数并不能直接换算成均匀随机密钥强度。
口令与密钥因此需要区分。口令面向人的记忆,密钥面向算法使用。把口令经过一次快速散列变成32字节,通常只改变表示,并没有凭空增加原口令的不确定性。专门的口令派生设计可以增加每次猜测的成本,却仍不能创造用户从未提供的秘密。后文遇到旧Shadowsocks口令处理和新协议预共享密钥时,将分别分析它们的来源。
同一密钥能否处理多条消息,还依赖算法要求的其他输入。其中一个常见输入是nonce,即一次加密调用使用的区分值。它通常不需要保密,但对于本章讨论的GCM与ChaCha20-Poly1305,在同一把密钥下必须遵守不重复要求。先记住密钥和nonce不是一回事:秘密可以在一段会话中保持不变,每条记录仍需要不同的nonce。其构造、传输和计数在14.5节展开。
还有两个层次不能混淆。AES、ChaCha20给出核心密码变换;把任意长度消息分块、怎样形成认证标签、怎样解释nonce,则需要更完整的构造。只写“AES加密”不能说明是否可以检测改写;“256位”也没有说明消息边界在哪里或重放如何处理。下面先解释核心变换,再把它们组合成可以用于网络记录的认证加密接口。
14.2 AES处理固定分组,ChaCha20产生密钥流
AES是分组密码:固定一把密钥以后,它把任意128位输入分组变成一个128位输出分组,且存在用该密钥执行的逆变换。AES-256中的256指密钥长度,分组仍然是128位,即16字节。AES-128、AES-192、AES-256分别执行10、12、14轮核心变换,不能把较长密钥解释成较长分组。
标准把16字节输入排入一个4行4列的状态,以列为主要填入顺序。若输入依次为b0到b15,第一列为b0、b1、b2、b3,第二列为b4到b7。状态经过四类操作:逐字节的非线性替换、各行不同距离的循环移位、每列内部的线性混合,以及与轮密钥逐位异或。轮密钥由原始密钥的扩展规则得到;开始先加一次轮密钥,末轮省去列混合。这里的“加轮密钥”是异或,不是把字节当普通整数逐项相加。FIPS 197 §3—5。
非线性替换使输入与输出的关系不能仅用简单线性规则表达;移位与混合把局部变化传播到更多位置;轮密钥让整个映射依赖秘密。这说明各部分的职责,不构成对AES安全性的自行证明。标准化算法的轮数、常量和组合是整体设计,不能随意删掉几轮再沿用原算法的安全结论。
单个16字节置换还不是一套网络加密协议。如果把两个完全相同的明文分组分别送入同一把密钥的AES变换,输出也完全相同。直接独立处理每个分组的ECB模式会暴露这种相等关系。因此需要工作模式,规定多个分组和每条消息的额外输入如何组合。本书重点讨论的GCM使用计数器产生掩码,而不是把业务明文直接逐块做孤立的AES置换。
ChaCha20采用另一条路线。它从密钥、nonce和分组计数产生伪随机字节,也叫密钥流,再与明文逐位异或。若密钥流是S,明文是P,则密文C=P⊕S;解密再次计算同一个S,得到C⊕S=P。这里的伪随机表示输出由确定的秘密输入计算产生,但对不知道秘密的观察者应难以预测,并非每个字节都由一个新的外部随机事件直接抽取。
本书采用RFC 8439 §2.3—2.4中的IETF变体:32字节密钥、12字节nonce和32位分组计数。内部状态有16个32位字,排成4×4:4个固定常量、8个密钥字、1个计数字、3个nonce字。输入字节按小端解释,输出状态也按小端序列化。这是算法内部格式,并不违背其他网络协议常采用的大端规则。
核心混合由模2³²加法、异或和循环左移组成。循环左移会把移出的高位带回低位,区别于直接丢位的普通移位。每个四分之一轮作用在4个字上,使用16、12、8、7位的循环移位距离;列位置与对角位置交替混合,合计20轮。随后把结果各字与原始状态对应字模2³²相加,输出64字节密钥流。计数增加后生成下一块,最后不足64字节的明文只使用所需前缀,不要求把业务数据补成64的整数倍。
这里有两种不同的计数:同一消息内部的ChaCha分组计数用于得到下一块64字节;区分不同加密调用的nonce用于避免不同消息重复使用同一密钥流。增加消息内部计数并不能弥补另一次调用重新使用相同密钥、相同nonce和相同起点的错误。在ChaCha20-Poly1305中,计数0与从1开始的负载加密还承担不同任务,下一节会说明。
AES和ChaCha20的CPU成本取决于实现、处理器指令、批量尺寸与内存路径。某些处理器有适合AES及有限域运算的指令,ChaCha20也适合使用普通整数运算和向量化实现。因而“ChaCha总比AES快”与“AES总有硬件所以免费”都不能成立为通用结论。它们也都只解决整个数据路径中的一部分工作,未包括Socket调用、拷贝、认证和排队。
14.3 GCM与Poly1305怎样把改写变成验证失败
单独异或密钥流只能隐藏内容。如果攻击者翻转密文某一位,解密得到的明文对应位也会翻转。攻击者可能不知道完整明文,却仍能制造有规律的改动。因此需要一种用秘密计算、接收方可以验证、攻击者难以伪造的认证标签。
AES-GCM把计数器加密和一种带密钥的多项式计算组合起来。下面限定为常用的96位nonce与128位标签,记AES单分组变换为E,密钥为K,nonce为N:
| 材料 | 形成方式与职责 |
|---|---|
| H | E使用K处理16个零字节,得到GHASH的秘密子密钥 |
| J₀ | 12字节N后接00 00 00 01,形成16字节起始块 |
| 负载掩码 | 递增J₀最低32位,从末尾计数2开始,各块分别送入E |
| 密文C | 明文P与所需掩码逐位异或,负载长度保持不变 |
| 认证结果S | 对编码后的附加数据、密文和长度计算GHASH |
| 标签T | 完整128位情形为E(K,J₀)⊕S |
这个安排把J₀用于标签掩码,后续计数块用于负载掩码,两类用途不能重叠。96位以外的IV需要另一套形成J₀的规则;不能只把任意长度IV直接截断或补到12字节。NIST SP 800-38D §6—7给出完整定义。
GHASH把输入按128位分组X₁、X₂等处理,从全零Y₀开始,反复计算Yᵢ=(Yᵢ₋₁⊕Xᵢ)·H。这里的乘法不是普通整数相乘,而是把位串按规定解释为多项式,在一个有2¹²⁸个元素的有限域中运算。有限域可以暂理解为一套有限集合上的加乘规则,其中加法对应异或,乘法结果还要按规定约减。它使每一块内容都影响最终结果,但GHASH本身不是通用密码散列,不能脱离秘密H和完整GCM构造直接当作独立认证方案。
送入GHASH的不是简单的“A后面接C”。A和C分别用零补到16字节边界,最后再附上两个64位大端整数,分别表示原始A与C的位长度。这些长度有助于区分不同分界,内部零填充也不会变成解密后需要交给业务的额外正文。128位标签是本书重点协议中的常见选择;GCM标准对其他标签尺寸另有条件,不能由应用任意截短。
ChaCha20-Poly1305也有两条路径。使用同一K和N先计算ChaCha20计数0的64字节输出,取前32字节作为本次Poly1305的一次性密钥;负载从计数1开始用ChaCha20加密。随后Poly1305认证附加数据A和负载密文C,而不是认证尚未加密的P。RFC 8439 §2.6—2.8。
Poly1305的一次性密钥分成各16字节的r与s。r按标准清除若干高位和低位,这叫clamping,并非把它重新随机化。消息按最多16字节一组、小端解释,每组在有效数据之后额外加入一个值为1的标记位;累加值与该组相加后乘r,按2¹³⁰−5取模。最后加上s,取低128位,小端序列化为16字节标签。r与s承担秘密输入的角色,不能像普通公开校验多项式那样由观察者直接重算。
在AEAD组合中,Poly1305输入按以下布局构成:
text
A | 将A补到16字节边界的零 | C | 将C补到16字节边界的零
| A的字节长度,64位小端 | C的字节长度,64位小端这与GCM在长度单位和字节序上都不同。假设A为12字节、C为114字节,则内部输入依次占12+4+114+14+8+8=160字节。末16字节为:
text
0c 00 00 00 00 00 00 00 72 00 00 00 00 00 00 00标签的16字节要传给接收者,内部补零和这两个长度块却不一定逐字节出现在网络上;它们由两端根据相同输入自行构造。下一节将用统一接口区分“算法需要的材料”与“协议实际发送的材料”。
14.4 AEAD统一接口不等于统一报文格式
认证加密同时保护明文的保密性与完整性;如果还允许一段不加密、但必须验证没有被改动的数据,就称为带附加认证数据的认证加密,英文缩写AEAD。附加认证数据常写为AD或AAD。本书统一用A表示它,二者不是两种不同机制。
统一接口可以写成数学关系,而不限定软件函数的名字:
text
保护: (K, N, P, A) → C
打开: (K, N, C, A) → P 或 FAILK是密钥,N是nonce,P是明文,A是只认证不加密的数据;此处C按RFC 5116接口的含义包括该算法输出的完整密文,通常也包含标签。前一节为解释内部路径,把负载密文与标签分开记;接口层把它们视为一个输出。实际库也可能分两个返回值,两端只要按同一算法与协议处理即可。AEAD是共同的抽象接口,AES-GCM与ChaCha20-Poly1305是其具体构造。RFC 5116 §2。
A的作用是把公开上下文与密文绑定。例如,记录类型需要先被路由层读到,却不能允许攻击者把“请求”改成“响应”;记录头可保持明文,同时作为A输入。它没有因被认证而隐藏,观察者仍能读出它。反过来,字段没有直接放进A,也不一定未被保护:它可能已经在P内,或者通过密钥、nonce形成规则绑定。必须看完整构造。
下面是明确自行构造的记录,不是TLS或任何代理协议的格式。假设负载为5字节,采用16字节标签,nonce由双方已同步的状态计算,不在线上传送:
| 线上偏移 | 长度 | 构造字段 |
|---|---|---|
| 0 | 1 | 记录种类01 |
| 1—2 | 2 | 后续AEAD输出长度0015,即21,大端 |
| 3—7 | 5 | 加密后的负载,真实值依赖K、N、P |
| 8—23 | 16 | 标签,真实值依赖K、N、P、A |
公开头为01 00 15,两端把这3字节作为A;P为5字节,因此完整AEAD输出为5+16=21字节,整条记录为3+21=24字节。这里不编造密文字节;14.6节会给出输入齐全的公开向量。若另一种格式明确发送12字节nonce,总线上长度可能变成36字节,但必须同时规定其位置和认证关系,不能由接口的四个输入推断网络上一定发送四段。
长度字段被认证仍不意味着可以先无条件信任它。接收者需要用头中的长度收集记录,验证却要等到记录到齐,因此必须在分配缓存以前检查上限、下限与合法状态。攻击者把0015改成ffff,即使最后认证必败,也可能让一个没有上限的实现先耗费大量资源或等待不可能完成的消息。解析安全与密码验证共同构成接收路径。
打开结果只能在验证成功后交给上层使用。计算过程内部可以暂时产生候选明文,但认证失败时不能把它当作“尽量恢复的数据”继续执行。否则完整性保护已经被应用绕过。标签比较、错误分支和内存使用也需要由实现正确处理;一个正确的算法定义不会自动修复错误的调用顺序。
成功验证表达的是:在算法及输入要求得到满足、秘密未泄漏等条件下,这段记录与持有相关密钥的一方所产生的内容一致。共享K的两方通常都可以产生有效标签,所以它不是只有某一方能制作、还能向独立第三方证明作者的数字签名。AEAD也没有自己决定这个用户是否有权限、这条旧记录是否已经执行过;这些问题仍由外部协议状态解决。
14.5 nonce的唯一性由整个发送状态保证
nonce可以公开,因为它承担区分加密调用的职责,保密主要依赖密钥。对于本章两种AEAD,关键要求是在固定K下,不同加密调用不能重复使用同一个N。这个范围跨越一把密钥的所有使用者,而非仅限一条Socket或一个进程。RFC 5116 §3。
一种容易推理的构造是固定前缀接单调计数。例如自行规定一个12字节nonce:前4字节为本发送方分配的固定部分,后8字节为大端记录序号。假设固定部分为a1 b2 c3 d4,则前两条记录的N为:
text
记录0:a1 b2 c3 d4 00 00 00 00 00 00 00 00
记录1:a1 b2 c3 d4 00 00 00 00 00 00 00 01这只是解释唯一性的构造,不是TLS规定的nonce形成方式。若多台设备共用K,它们必须有不重叠的分配范围,或者改用各自独立的密钥。若C与P两个方向同时使用同一个K、同一固定部分且都从0开始,第一条上行与第一条下行就已经碰撞。方向独立的密钥可以让相同数字序号属于不同的密钥域;也可以设计有严格分区的nonce空间,但两端必须采用同一明确规则。
序号不一定在线上出现。对一条可靠有序字节流,两端可以各自保存下一个预期记录序号。发送方保护记录0后增加计数,接收方成功打开记录0后增加对应计数。若攻击者删掉中间一条记录,后续密文便会在错误N下验证失败。省略序号节约线上字节,却把同步责任交给状态机;不能在失败后随意跳过若干计数继续猜测,也不能把旧流的计数状态不加区分地移植到新流。
无序数据报通常还要让接收方知道使用哪个序号,或者使用能够重建它的编号。收到同一个合法数据报两次,不要求接收方再执行一次“加密”;使用同一N进行多次解密本身不是这里禁止的nonce重用。它是否应再次交付应用,属于防重放规则。运输层把同一批已加密字节重传到可靠接收端,也不能简单等同于重新以同一N加密一条新消息。
进程重启是另一种危险边界。如果K从磁盘恢复,计数却重新变成0,之前用过的nonce可能再次出现。因此有状态设计需要可靠保存分配进度,或在重启、重连等边界建立新的独立密钥。快照回滚、进程复制、多个发送线程竞争同一计数器,也都必须纳入唯一性分析。把计数器变量写成64位并不能解决这些生命周期问题。
完全随机选择nonce则需要评估碰撞。若独立均匀抽取n位值,q次调用至少一次碰撞的概率在较小概率区间约为q(q−1)/2ⁿ⁺¹。这是生日碰撞估计,前提包括独立与均匀;随机源失效时不能沿用。以96位N、q=2³²为例,数量级约为2⁻³³,因而“96位很多,所以任意次数都不会重复”不是成立的论证。具体协议还可能施加更严格的总用量限制。
计数没有回绕也不是无限安全的许可。单条消息的分组计数有范围,一把密钥处理的总记录数、总字节数以及允许的失败验证次数,也可能受安全分析限制。协议应在达到限制以前更新密钥或终止,不能把序号溢出后回到0当成正常继续。TLS与QUIC会给出各自的密钥更新和用量安排,后文将按实际标准解释。
14.6 用公开标准向量看见完整字节关系
公开测试向量给出固定输入和确定输出,方便核对实现是否使用同一规则。它们的密钥故意公开,绝不能用于实际保密。本节不是实际用户抓包,也没有把临时计算得到的未知字节冒充标准向量。
先看AES-128单分组的标准例子。下列每行均为16字节,空格只是阅读分隔:
text
K:00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
P:00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff
E:69 c4 e0 d8 6a 7b 04 30 d8 cd b7 80 70 b4 c5 5a这是FIPS 197的2001版附录C.1中的AES-128结果。该旧版已由2023年编辑更新版替代;AES算法没有技术变更,更新版将原附录的具体向量改为外部示例资料的引用。这里引用旧版只为精确定位这组历史标准向量。这里没有nonce、附加数据和标签,因为它展示的只是单个分组变换,不是一条完整的AEAD记录。不能给这一行输出加上“通过了身份认证”的解释。
再看RFC 8439 §2.8.2的ChaCha20-Poly1305完整向量。输入密钥K为32字节,nonce N为12字节,A为12字节。RFC把N拆成4字节固定部分与8字节IV展示;进入AEAD时使用下面这一整串12字节:
text
K:80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f
90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f
N:07 00 00 00 40 41 42 43 44 45 46 47
A:50 51 52 53 c0 c1 c2 c3 c4 c5 c6 c7P为114字节,以下前七行各16字节,最后一行2字节:
text
4c 61 64 69 65 73 20 61 6e 64 20 47 65 6e 74 6c
65 6d 65 6e 20 6f 66 20 74 68 65 20 63 6c 61 73
73 20 6f 66 20 27 39 39 3a 20 49 66 20 49 20 63
6f 75 6c 64 20 6f 66 66 65 72 20 79 6f 75 20 6f
6e 6c 79 20 6f 6e 65 20 74 69 70 20 66 6f 72 20
74 68 65 20 66 75 74 75 72 65 2c 20 73 75 6e 73
63 72 65 65 6e 20 77 6f 75 6c 64 20 62 65 20 69
74 2e相同长度的负载密文为:
text
d3 1a 8d 34 64 8e 60 db 7b 86 af bc 53 ef 7e c2
a4 ad ed 51 29 6e 08 fe a9 e2 b5 a7 36 ee 62 d6
3d be a4 5e 8c a9 67 12 82 fa fb 69 da 92 72 8b
1a 71 de 0a 9e 06 0b 29 05 d6 a5 b6 7e cd 3b 36
92 dd bd 7f 2d 77 8b 8c 98 03 ae e3 28 09 1b 58
fa b3 24 e4 fa d6 75 94 55 85 80 8b 48 31 d7 bc
3f f4 de f0 8e 4b 7a 9d e5 76 d2 65 86 ce c6 4b
61 1616字节标签为:
text
1a e1 0b 59 4f 09 e2 6a 7e 90 2e cb d0 60 06 91如果按RFC5116式完整输出理解,前114字节为负载密文,后16字节为标签,总计130字节。A和N属于输入,不因列在这里就必然附在这130字节里面。一个实际记录还要根据自身格式传送或重建这些材料,并补充运输定界。
该向量第一字节的负载密钥流为9f,所以4c⊕9f=d3。这个关系解释加密如何保持负载长度,也说明密文不是十六进制字符串的“字符替换表”:第二字节及之后使用不同密钥流位置,不能把d3简单当成所有4c的固定编码。
本书在编写时独立复算了上述AES分组结果与完整ChaCha20-Poly1305输出,逐字节相符,并分别改变nonce、A或标签中的一位确认打开失败。这只验证了所用库对这些输入的行为与向量一致,不是对全部实现路径的证明,也不是协议性能测试。这里不给出程序或读者实验任务;重要的是读者已经能指出每一个公开输入、秘密输入和输出分别处于哪一层。
14.7 重复密钥流会泄露关系,标签也不能补救一切
14.6节给出第一字节关系4c⊕9f=d3。若另一条消息在同一位置错误地重用了这段密钥流,而明文字节为41,则其密文字节为41⊕9f=de。观察者无需知道9f,也能计算:
text
d3 ⊕ de = 0d = 4c ⊕ 41这是异或的消去性质:C₁⊕C₂=(P₁⊕S)⊕(P₂⊕S)=P₁⊕P₂。知道其中一个明文,就能从这个关系恢复另一个;不知道时,两个明文之间的结构关系仍然被泄露。这个1字节反例不是完整伪造记录,也没有编造标签,它仅展示保密性已经怎样受损。
GCM与ChaCha20-Poly1305在相同K和N下都会重用相应位置的负载密钥流。因此nonce错误不是“稍微降低随机性”。两种构造的认证部分也可能受到破坏:GCM重用会重复标签掩码并暴露关于GHASH秘密子密钥的关系;ChaCha20-Poly1305会重复本应按次区分的Poly1305密钥。具体恢复与伪造需要相应数据条件,不能把一个1字节异或例子直接当成完整攻击证明;但也不能依靠“后面还有标签”来恢复原本的安全保证。RFC 5116 §5.1.1、RFC 8439 §4。
认证标签还有上下文边界。设设计者想把两个可变长字符串作为A,直接串联却不写长度:第一组是a和bc,第二组是ab和c,得到的A都为61 62 63。AEAD看到完全相同的字节串,无法替上层辨认原本的两组含义。需要固定字段宽度、带长度的无歧义编码或其他明确规则。第4章的编码唯一性,在这里变成了认证语义的前提。RFC 5116 §3.3。
类似地,一条记录在错误的方向、错误的会话或错误的用途下仍被接受,可能是密钥和上下文没有充分区分。可以使用不同派生标签得到不同方向的密钥,或把必要上下文明确绑定到A;究竟采用哪一种要按协议分析。随意把字段“多拼一些”并不能代替对编码和作用域的说明。
16字节标签也不是允许无限次试错的理由。在理想化独立猜测模型下,一次盲猜128位标签成功的概率是2⁻¹²⁸,q次尝试的简单数量级约为q/2¹²⁸;真实AEAD的安全界还依赖消息长度、用量、构造及攻击者已见的信息。协议的限制应依据具体分析,不能只拿标签位数做一个脱离条件的结论。
此外,标签比较如果泄露匹配前缀长度,攻击者可能从时延得到额外信息。常数时间比较的意思是避免执行耗时随这类秘密相关差异变化,而不是要求所有网络响应在物理上同时到达。RFC8439明确要求适当的标签比较并禁止截短其AEAD标签。一个正确的16字节数学输出,若被实现成泄漏比较过程或失败后仍使用明文,仍可能不能实现预期保护。
重新加上一层外部加密也不能代替修复内部错误。外层或许在特定观察位置隐藏了有缺陷的内层,但一旦外层在某个节点终止,内层错误仍存在。分析组合机制时,应分别建立每层保证,再判断它们怎样衔接,而不是把算法名字的数量当作安全强度相加。
14.8 记录完整性还需要与应用完成状态衔接
网络安全协议通常把长字节流拆成若干受保护记录。这样接收者可以逐条验证与交付,不必等整个下载结束才验证一次;一条记录失败时,也能在较小范围内停止。但记录尺寸改变开销和交付节奏,不能越小越好。
只计算16字节标签,1字节负载的额外比例为1600%,16KiB负载则约为0.0977%。这两个数是16除以负载长度的算术结果,不含记录头、nonce传输、外层TLS、TCP/IP或链路层,也不是实际速度测量。小记录可以降低某些等待与缓冲,却增加每条记录的固定工作;大记录降低固定开销比例,却可能让接收者更晚拿到第一份通过验证的明文。具体实现还会受到批量处理与调度影响。
接收路径可以按职责表示为:
text
收集有界记录 → 重建当前密钥与nonce → 验证并打开
→ 验证成功的字节 → 上层消息解析器 → 完整业务对象最左侧需要完整记录材料才能完成这一条的认证,最右侧是否完整则由应用格式决定。一个HTTP正文可能跨越很多安全记录,一个记录也可能装下多条短消息。TLS记录边界不自动成为HTTP消息边界,正如TCP分段边界不自动成为TLS记录边界。
假设某个已认证的应用头声明文件总长为1000字节。接收端随后成功打开四条记录,每条含200字节文件内容,然后连接结束。已经收到的800字节可以全部真实且未被改写,但完整1000字节文件仍然没有收到。接收者不能把连接断开当成“发送者把总长改为800”的授权,也不能因为每个局部认证都成功就对外报告完整成功。
若应用没有预先声明总长,也可能使用经过认证的结束标记、最终对象校验或明确的事务提交消息来区分正常结束与中途断开。具体选项取决于协议,不要求所有应用采用同一种方式。TLS的关闭通知保护会话的结束语义,HTTP又有自己的消息定界;两者的组合将于第16章细化,不能用一次裸TCP EOF替代全部判断。
重放状态同样在接口之外。接收者对同一组K、N、A和密文调用打开两次,数学上可能两次都成功。若有序记录协议把N绑定到下一个预期序号,第二次旧记录就会在新序号下失败;若无序协议在网上显式携带序号,接收者还要保存已接收范围并决定重复数据如何处理。认证算法既不知道某个计数是否已经被业务使用,也不知道一笔动作是否已在另一台服务器执行。
这说明防重放可能需要跨连接甚至跨节点的状态。把握手压成0-RTT并允许客户端立即发送早期请求时,服务器尚未为本次完整交互产生新的响应证明,旧请求是否可被再次接受就成为独立问题。后文不会因为早期数据也使用AEAD,就宣称它天然与完整往返后的数据具有相同重放保证。
到这里,对称记录机制已经能够保护大量数据,但前提仍是双方正确取得相应秘密。若把相同静态密钥直接复制给所有用户,认证粒度、泄露影响与nonce协调范围都会随之扩大。下一章要解决的正是密钥怎样跨越不可信网络建立,以及长期身份秘密与短期会话秘密为什么应承担不同职责。