摘要

  • RFC 1446 的 SNMPv2 认证并非只核对 MD5 摘要。接收方还要独立判断报文时间戳是否落在本地接受的生存期内;摘要正确,并不自动意味着报文仍然新鲜。
  • 同一私有认证密钥继续使用时,partyAuthClock 必须保持不减。若时钟单独倒退,已经关闭的时间区间会被重新打开,攻击者保存的旧报文可能再次满足接收条件。
  • 因而,时钟回拨与密钥更换必须构成同一次安全纪元切换。后来的 RFC 3414 用权威引擎标识、非易失的启动计数和本次启动时间重写了状态表示,但仍把密码校验与时效校验分开。

断电留下的不是空白,而是两个“现在”

设想一台代理在认证时钟走到 36,000 后断电。此前有一份时间戳为 35,500 的报文被旁观者完整保存。管理员把允许生存期设为 300 秒;因此,当接收方认定源方时钟已经超过 35,800,这份报文便过期了。

设备重启后,从非易失存储里读出较早的时钟值 35,400,私有密钥却没有变化。现在,那份时间戳为 35,500 的旧报文又落在窗口内。它没有被重新签发,内容没有变,MD5 摘要也没有神奇地恢复;改变的是接收方用来界定“最近”的本地历史。

这就是 RFC 1446 中最值得保留的设计洞见。重放防御不只存在于报文里。它依赖接收方持续记得哪些时间已经过去、哪些密钥曾经承认过哪些报文。若时间状态倒退而密钥保持连续,旧流量就同时重新满足两项条件:摘要仍能被旧密钥识别,时间戳又重新看起来足够新。

所以规范没有把回拨当作普通校时动作。它要求:只要某个参与方仍使用同一私有认证密钥,其认证时钟就不得减少;若必须减少,则要同时改变密钥。时钟值、密钥值都不是完整身份。真正需要连续管理的是由参与方身份、密钥代际和单调时间组成的安全纪元。

一次认证里有两道互不替代的门

RFC 1446 于 1993 年 4 月发布,最初属于标准轨道,如今状态为 Historic。它为 SNMPv2 定义了一种认证协议和一种隐私协议。在采用 v2md5AuthProtocol 的参与方上,认证配置使用 16 个八位组的私有材料,并以 MD5 计算 128 位摘要。

发送时,源方先把秘密临时放进摘要字段,再对完整的认证消息编码计算摘要,随后用摘要结果替换秘密并传输。接收时,对端取出收到的摘要,把自己保存的私有密钥放回相应位置,重新构造并计算。两个结果不一致,snmpStatsWrongDigestValues 增加,报文被判为不真实。

但摘要匹配只是其中一道门。接收方还要取出源方在本地记录的认证时钟、私有密钥和生存期,用报文携带的源方时间戳执行时效检查。其拒绝条件可以写成:

authSrcTimestamp + 生存期 < 本地认证时钟

条件成立时,snmpStatsNotInLifetimes 增加,消息不能进入后续处理。这里最容易误读的是术语:某条消息的摘要可能完全匹配,它仍会因为不在生存期内而在完整认证流程中失败。密码完整性与时间可接受性回答的是两个问题。

证据记录也应维持这条界线。摘要比对成功,可以支持“这些字节在当时的本地密钥配置下通过了指定的完整性和来源校验”。生存期比对可以支持“这些字节处于或不处于该接收方接受的时间窗口”。两者都不能单独证明访问控制已经授权、代理已经执行、外部设备状态已经改变,或改变在下一次启动后仍然存在。

生存期是风险决定,不是网络常数

认证消息同时携带源方与目的方的认证时间戳,粒度为一秒。partyAuthLifetime 所表达的不是物理世界中固定的“新鲜度”,而是管理员愿意容忍的最大传送延迟。RFC 1446 建议,在时钟精度、往返延迟和校验频率所允许的范围内,把它设得尽可能小。

窗口过小会把漂移、排队或慢链路上的合法消息挡在门外;窗口过大则让被保存的消息拥有更长的可用期。把生存期调大以消除告警,短期会让系统更平静,实际却改变了重放风险。这不是单纯的性能调优,而是在可用性与时间确定性之间重新定价。

落在窗口内也不等于“从未重放”。同一消息的两份副本可以同时足够新。对于会改变状态的请求,RFC 1446 建议在收到肯定确认或等到相关期限过去之后,再发送后继请求。这一建议承认了协议的边界:摘要和时间窗可以压缩重放空间,却不会把无连接消息自动变成具备严格顺序和恰好一次语义的事务。

时效与摘要均通过后,访问策略才参与决定。只有存在某种许可访问时,较大的已认证时间戳才可以选择性推进本地保存的参与方时钟。谁可以教会接收方“现在几点”受到约束,但这仍不是把认证、授权与执行合成一个结果。推进时钟不能证明操作被允许,更不能证明设备状态发生了持久改变。

回拨为什么必须和换钥成为一个动作

如果只看时钟,回拨似乎只是把数字改小;如果只看密钥,沿用旧值似乎意味着信任关系没有中断。两件事结合起来,含义却变了。攻击者捕获的旧报文保留着旧密钥下正确的摘要,也保留着曾经进入窗口的时间戳。时钟倒退后,后一个条件重新成立;密钥不变,则前一个条件也继续成立。

更换密钥会切断这条交集。旧时间戳也许又在数值上接近“现在”,旧摘要却无法在新秘密下通过。换钥并没有修理时间,它是在声明:相同的时钟读数已经属于另一段密码历史。

RFC 1447 把这种关系写进参与方 MIB。partyAuthClock 的定义明确要求,除非参与方的私有认证密钥同时改变,否则该值不得递减。partyAuthLifetime 记录允许的秒数。两项状态加上身份与密钥代际,共同描述一段可以解释的安全历史,而不是几个互不相关的管理字段。

密钥变化本身也不是瞬间完成的事实。负责的管理站决定新密钥、发出请求、确认传送、观察对端采用、让其他管理站获得新值,最后才淘汰旧秘密。每一步都需要自己的证据。交接期间,管理站可能必须同时保留新旧密钥,甚至要保证自己重启后两者仍在,直到确知对端完成转换。

因此,“密钥更新请求已接受”最多证明流程中的一个节点。它不证明远端已经持久保存,不证明第二个管理站已同步,也不证明旧密钥可以安全销毁。过早销毁会造成中断;无限期保留又会让多个信任纪元长期重叠。

先取得一个不可信的时间,再用认证确认

RFC 1446 假定各方的时钟松散同步,并要求至少有一个负责的管理站协调时钟与秘密分发。秘密不变时,正常同步把较慢的一方往较快的一方推进,而不是把较快时钟往回拉。这一方向性正是为了避免在同一密钥下重新开放旧区间。

规范也面对了一个启动悖论:管理站还不知道双方时钟是否一致时,如何发出能通过时间检查的认证请求?它允许管理站先做一次未认证读取,取得对端所声称的时钟值,把它当作候选基准;随后必须再做一次认证读取,确认采用后的值。

第一次读取有用,却不因有用而变成可信事实。它只告诉管理站“对方当前提供了这个数字”。第二次读取在共享秘密下补充了不同性质的证据。把候选观察与认证确认分开保存,才能看到信任是在哪一步加入的;若只保留最终数字,发现过程的弱点会被抹去。

当多个管理站共同负责时,技术问题又成为治理问题。它们可能持有不同的时钟观念或密钥代际,一个站完成的切换会把另一个站留在旧纪元。规范能够要求本地协调,却不能替组织决定谁有权发起变化、谁必须确认、以何种证据宣布过渡结束。

防重放状态必须跨过故障

只在进程存活时单调的时钟,无法保护会断电的设备。RFC 1446 因而要求参与方身份、认证时钟、私有认证密钥和私有隐私密钥具有非易失且不可破坏的表示;条件允许时,生存期也应得到同类保护。

实现可以使用带电池的时钟,也可以周期性地把检查点写入非易失存储,并在重启时保守地向前跳跃,越过自上次检查点以来可能达到的最大值。重点不是恢复精确到秒的墙上时间,而是不让同一密钥重新走进已经使用过的认证区间。

若代理停机很久,直到某个参与方时钟到达最大值,时钟应停在最大值。此后可以发送的认证管理请求,只应是至少同时改变时钟和私有认证密钥的请求。最大值代表当前纪元耗尽,不是把计数器随手归零的理由。

若受保护的参与方参数丢失或毁坏,规范要求用随机值替换,使通信必须经过人工重新分发才能恢复。这样的设计宁可让不连续变得明显,也不肯用猜测维持服务。负责管理站检测到代理重启后,还可能需要恢复其他参与方属性,包括未能保留的生存期;修复过程甚至可以人为推进时间戳,使认证恢复请求能够落入可接受窗口。

后来的设计允许秒表归零,但不允许纪元消失

RFC 3414 的基于用户安全模型后来把时效锚定到权威 SNMP 引擎。snmpEngineID 确认是哪一个引擎,snmpEngineBoots 记录它重启或重新初始化的次数,snmpEngineTime 则记录本次启动已经过去的秒数。引擎标识和启动计数必须留在非易失存储中。

这种设计不要求一只参与方时钟在断电时继续走。每次启动,秒表可以从零开始,只要启动计数先增加。旧报文即使带着相似的秒数,也属于不同的启动纪元。权威接收方会拒绝启动计数不符的消息,也会拒绝超出固定 150 秒时间窗的消息。

表示方法变了,证据边界没有变。认证值仍不能独自证明时效。若引擎无法确定最近一次启动计数,它要把计数锁定在最大值,认证消息会持续无法通过时效检查,直到人工建立新的引擎身份或新的用户秘密。可见的停机优先于伪造的连续性。

RFC 3414 并非直接取代 RFC 1446,中间还有其他标准阶段。这里的比较不是简化谱系,而是观察同一个原则如何换了形状:RFC 1446 用换钥保护连续时钟回拨后的边界;RFC 3414 用持久启动计数给可归零的秒表加上纪元。两者都拒绝从“摘要正确”直接跳到“这条消息属于现在”。

资料来源