摘要
基于时间的一次性密码(Time-based One-Time Password,TOTP)以极低的实现成本成为二次验证事实上的默认选项。它用“共享密钥 + 粗粒度时钟”替代了挑战—应答交互,从而把一个物理假设直接写进了安全模型:验证方与被验证方对“现在是几点”必须达成一致。本文回顾从 HOTP 到 TOTP 的标准演进与 HMAC 构造,提炼出该方案成立所依赖的四条隐含假设;随后结合 RFC 6238 与 NIST SP 800-63B 的规范文本,分析时钟漂移、重放与密钥前置暴露三类典型失效模式,并说明窗口放宽与在线猜测成功率之间的定量关系;最后从熵、速率限制与替代方案三个层面给出工程建议。本文的立场是:TOTP 的安全性并非来自六位数字本身,而是来自“一次性”与“限速”这两条常被实现者忽略的约束。
关键词:一次性密码;TOTP;HMAC;二次验证;时钟同步;认证协议
1 引言
口令认证的缺陷早已被系统性地讨论过。Bonneau 等人提出的评估框架从可用性、可部署性与安全性三个维度比较了数十种 Web 认证方案,结论之一是:不存在在所有维度上占优的方案,口令之所以长期存续,主要因为它在“可部署性”上几乎无对手 [6]。
二次验证(2FA)试图在不显著提高部署成本的前提下弥补单一口令的脆弱性。在众多形态中,基于时间的一次性密码(TOTP)占据了特殊位置:它不需要专用硬件,不需要网络往返,客户端只需一个共享密钥与一个大致准确的时钟。代价是,它把一个原本属于分布式系统的问题——时间同步——变成了认证协议的安全前提。
本文不提出新算法,而是对既有标准做一次“假设审计”:TOTP 在什么条件下成立,这些条件在真实部署中如何被削弱,以及工程上应当如何补偿。
2 从 HOTP 到 TOTP
2.1 HMAC 构造
HOTP 与 TOTP 的消息认证码均由 HMAC 给出。HMAC 将任意长度的密钥归约为分组长度,通过内外两次哈希抵抗长度扩展攻击,其安全性可归约到底层哈希函数的伪随机性 [3][4]。RFC 2104 给出的定义为:
HMAC(K, m) = H((K' ⊕ opad) || H((K' ⊕ ipad) || m))
其中 K' 为密钥填充或哈希后的结果。HMAC 的安全性论证不依赖哈希函数的抗碰撞性,这一点对本文后续讨论的“密钥保密即全部”很关键。
2.2 计数器与时间戳
HOTP 使用一个单调递增的计数器 C 作为 HMAC 的消息 [1]。服务端与令牌各自维护 C,验证成功后双方同步推进。这一设计的问题在于同步会漂移:客户端多按一次、服务端少记一次,都会造成永久性偏移,需要“向前看若干步”的补偿逻辑。
TOTP 把计数器换成时间的函数 [2]:
C = ⌊(T − T0) / X⌋, TOTP = Truncate(HMAC-SHA-1(K, C))
标准取 T0 = 0、X = 30 秒。时间戳的引入消除了“按键次数”这一状态,代价是把时钟精度纳入了协议。换句话说,HOTP 的同步问题是可观测、可修复的;TOTP 的同步问题则被转移给了 NTP [10]、运营商网络与用户设备。
2.3 截断与熵
两种方案都对 HMAC 输出做动态截断:取最后一字节的低 4 位作为偏移,从该位置取出 4 字节并清除最高位,得到 31 位整数,再对 10^d 取模。NIST SP 800-63B 明确指出,六位十进制输出约合 20 比特熵 [5]。这意味着单次猜测的成功率约为百万分之一——这个数字只有在配合速率限制时才具有意义,后文将回到这一点。
3 安全模型:四条隐含假设
将 TOTP 视为一个认证协议,其安全性依赖于以下四条假设。
假设一:密钥保密。 依 Kerckhoffs 原则,算法公开、唯密钥保密 [8]。HMAC 的构造保证了在密钥未知时输出不可预测,但这一保证在密钥被复制到多个设备、或以明文形式写入前端代码时立即失效。
假设二:时钟同步。 验证方以本地时间重建 C,因此双方时钟偏差必须小于窗口宽度。Lamport 曾指出,分布式系统中“同时发生”本身缺乏物理定义,只能用逻辑时钟建立偏序 [9];TOTP 反其道而行,直接用物理时钟替代逻辑时钟,从而把时钟同步误差转化为认证失败率。
假设三:一次性。 同一时间步内产生的码只能被接受一次,否则截获者可在窗口内重放。NIST SP 800-63B 对此的表述是强制性的:验证方“SHALL accept a given time-based OTP only once during the validity period” [5]。
假设四:实现一致。 客户端与验证方对哈希算法、截断规则、时间步长的解释必须完全一致。这一条看似平凡,却是互操作问题的主要来源。
4 三类失效模式
4.1 时钟漂移与窗口放宽
RFC 6238 在讨论验证策略时指出,由于传输延迟与设备时钟误差,接收端不应只比对当前时间步,而应容忍相邻时间步的取值 [2]。工程实践中常见的做法是允许前后各一个步长,即接受 C−1、C、C+1 三个计数器。
这一放宽是有代价的。若窗口为 2k+1 个时间步,则在任意时刻可被接受的码有 2k+1 个,单次盲猜的成功率由 10^−6 上升为 (2k+1)×10^−6。放宽到 ±1 步即三倍,放宽到 ±5 步则十一倍。窗口越宽,用户体验越好,抗猜测能力越弱——二者是同一个旋钮的两端。
NIST 的处理方式是把窗口与速率限制绑定:若认证器输出的熵低于 64 比特,验证方必须实施限速,将单账户连续失败次数限制在 100 次以内 [5]。换言之,标准承认六位码本身不足以独立支撑安全性,必须由限速兜底。
4.2 重放
在 30 秒窗口内,一个被截获的码仍然有效。若验证方不记录已使用的码,攻击者可在窗口内重放。这解释了为何“一次性”是 TOTP 名字里的定语而非修饰:去掉它,TOTP 退化为一个每 30 秒轮换一次、且可被窗口内重放的静态口令。
需要强调的是,重放防护与时钟窗口是一对张力:窗口越宽,可重放的时长越长;记录已用码的状态越多,存储与并发一致性成本越高。
4.3 密钥的前置暴露
第三类失效与密码学无关,与部署形态有关。笔者在实践中遇到过一种常见做法:把 TOTP 生成逻辑整体放进静态页面,由浏览器用 JavaScript 计算并展示动态码。这种实现完全可行,因为算法是公开的、计算是确定性的;但它同时意味着密钥随脚本一起下发给每一位访问者。
此类实现的能力边界因此被严格限定为“展示”:任何能看到页面的人都能自行算出同一个码,验证也就失去了意义。若需要“验证”,密钥必须留在服务端,前端只能提交待验证的码。这条边界不是实现缺陷,而是假设一被放弃后的必然结果。
5 人为因素与替代方案
技术上的失效并非主要风险来源。Petsas 等人对主流服务的测量显示,2FA 的部署并不均衡,短信通道仍是主要形态,而部分服务提供的回退路径显著削弱了整体强度 [7]。更根本的问题在于,TOTP 不绑定来源:中间人可以把用户输入的码实时转发给真实服务端, phishing 因此对 TOTP 依然有效。
WebAuthn 的设计正是针对这一点:凭证与源(origin)绑定,签名覆盖挑战值,从而在协议层排除转发攻击 [11]。代价是需要浏览器与认证器的支持,部署复杂度高于“扫一个二维码”。
Bonneau 等人的框架提醒我们,这类取舍没有唯一正确答案:安全性、可部署性与可用性构成一个三角,选择取决于被保护资产的价值 [6]。对低价值账户,TOTP 加限速已足够;对高价值资产,源绑定的公钥凭证更合适。
6 工程建议
综合上述分析,给出五条可操作的约束:
- 验证方必须记录已用码,在有效期内拒绝重复提交,这是“一次性”的唯一实现方式。
- 速率限制不是可选项。六位码约 20 比特熵,必须与“单账户连续失败上限”配合才有意义;标准给出的参考值是 100 次 [5]。
- 窗口取最小可行值。±1 步足以吸收常见网络抖动;更宽的窗口应视为可用性妥协,而非默认配置。
- 密钥强度与存储。标准建议密钥提供不低于 112 比特的安全强度 [5];前端展示型实现应明确标注“不具备验证能力”,避免被误用为认证手段。
- 分层选择方案。展示、低价值二次确认可用 TOTP;涉及资金、身份或长期凭证的场景应优先采用源绑定的公钥方案 [11]。
7 结论
TOTP 的精巧之处在于用最少的密码学原语换取了可部署性:一个 HMAC、一个时间戳、一次取模。但它的安全性并不来自那六个数字,而来自三条容易被省略的工程约束——时钟误差被显式建模、码只能使用一次、失败尝试被限速。省略任何一条,方案都会在不知不觉中退化。
把时间当作安全参数是有代价的。本文的分析表明,这个代价可以量化:窗口每放宽一步,盲猜成功率线性上升;限速每放松一档,在线猜测的可行性就提高一个量级。理解这一点,比记住“TOTP 是安全的”更有用。
参考文献
[1] D. M'Raihi, M. Bellare, F. Hoornaert, D. Nennemann, C. O'Rourke. HOTP: An HMAC-Based One-Time Password Algorithm. RFC 4226, IETF, 2005. https://www.rfc-editor.org/rfc/rfc4226
[2] D. M'Raihi, S. Machani, M. Pei, J. Rydell. TOTP: Time-Based One-Time Password Algorithm. RFC 6238, IETF, 2011. https://www.rfc-editor.org/rfc/rfc6238
[3] H. Krawczyk, M. Bellare, R. Canetti. HMAC: Keyed-Hashing for Message Authentication. RFC 2104, IETF, 1997. https://www.rfc-editor.org/rfc/rfc2104
[4] M. Bellare, R. Canetti, H. Krawczyk. Keying Hash Functions for Message Authentication. In Advances in Cryptology — CRYPTO '96, LNCS 1109, Springer, 1996, pp. 1–15.
[5] P. A. Grassi, J. L. Fenton, E. M. Newton, R. A. Perlner, A. R. Regenscheid, W. E. Burr, J. P. Richer. Digital Identity Guidelines: Authentication and Lifecycle Management. NIST Special Publication 800-63B, 2017(含 2020-03-02 修订). https://doi.org/10.6028/NIST.SP.800-63b
[6] J. Bonneau, C. Herley, P. C. van Oorschot, F. Stajano. The Quest to Replace Passwords: A Framework for Comparative Evaluation of Web Authentication Schemes. In Proc. IEEE Symposium on Security and Privacy (S&P), 2012, pp. 553–567.
[7] T. Petsas, G. Tsirantonakis, E. Athanasopoulos, S. Ioannidis. Two-factor Authentication: Is the World Ready? Quantifying 2FA Adoption, Deployment and Security. In Proc. 8th European Workshop on System Security (EuroSec '15), ACM, 2015. https://doi.org/10.1145/2751323.2751327
[8] A. Kerckhoffs. La cryptographie militaire. Journal des Sciences Militaires, IX: 5–83, 1883.
[9] L. Lamport. Time, Clocks, and the Ordering of Events in a Distributed System. Communications of the ACM, 21(7): 558–565, 1978.
[10] D. Mills, J. Martin, J. Burbank, W. Kasch. Network Time Protocol Version 4: Protocol and Algorithms Specification. RFC 5905, IETF, 2010. https://www.rfc-editor.org/rfc/rfc5905
[11] D. Balfanz, A. Czeskis, J. Hodges, M. B. Jones (eds.). Web Authentication: An API for Accessing Public Key Credentials — Level 1. W3C Recommendation, 2019. https://www.w3.org/TR/webauthn-1/