TOTP 验证码是怎么算出来的

TOTP 验证码是一个 6 位数字,认证器 App 与服务器各自独立算出,输入只有两个:同一把共享密钥,和按 30 秒窗口切分的当前时间。验证码本身在设置时从不传输,也不携带任何密钥信息,但两边总能得到相同的数字——因为 TOTP 不是魔法也不是随机数,它是对相同输入套用同一套公开算法(RFC 6238)。本文把计算从头到尾走一遍,讲清为什么“验证码错误”几乎总是时钟问题,并给出几条决定你的第二因素还能不能算第二因素的使用纪律。你可以在TOTP 生成器里亲手跑每一步——它在你的浏览器本地计算。

两个输入,一个验证码:密钥与时钟

开启两步验证时,服务生成一把随机密钥——通常 20 字节,以 Base32 字符串展示给你,并为了方便编码进二维码。这把密钥只存两处:服务的数据库和你的认证器。从那一刻起,登录变成一次同步计算:两边取当前 Unix 时间,除以时间步长(几乎所有服务都是 30 秒),把得到的计数器连同密钥一起送进哈希函数。谁拿出匹配的输出,谁就被视为持有密钥。

这个设计的优雅之处在于它省略了什么:没有要下发的挑战、没有要投递的验证码、没有网络往返、没有每次登录可供拦截的东西——“一次性”来自时钟,因为计数器每 30 秒变一次,旧输出随即作废。这也意味着整个系统只有两样东西能出问题:密钥,和时间。几乎每一次 TOTP 失败、每一次 TOTP 被盗,都能追溯到这两者之一。

计算过程,一步一步

计数器先写成 8 字节大端整数——一种双方约定的定宽编码。然后以密钥为键,对这条消息做 HMAC,使用约定的哈希:默认 SHA-1,服务也可以选 SHA-256、SHA-512。HMAC 是带密钥的构造,而不是普通哈希:只哈希计数器的话,任何人拿到算法都能复现验证码;而 HMAC 的输出在没有密钥时不可伪造,也无法反推出密钥。

摘要有 20 字节以上,登录框要的是 6 位数字。两者之间的桥是动态截断:取摘要最后一个半字节给出一个偏移,从该偏移读 4 字节并掩掉最高位(避免符号歧义),得到的 31 位数对 10 的位数次方取模——标准 6 位就是除以一百万——左侧补零。这就是你输入的验证码。下一个窗口从头重算;验证码之间没有链条,所以拍下来的码在 30 秒后一文不值。

验证码被拒:通常是时钟的锅

计数器由时间导出,所以生成方和服务器必须对“现在是哪个 30 秒窗口”达成一致。多数服务器会放宽处理:前后相邻窗口的码也接受,这样能悄悄吸收小幅偏差。超出这个范围,密钥再正确,每个码都是错的,而失败的样子和输错一位数字毫无区别——干脆拒绝,不给任何关于哪个输入不一致的提示。

所以认证器突然失灵时,第一个要查的永远不是密钥,而是生成验证码那台设备的时钟。手动设置的时间、快没电的 CMOS 电池、坐完航班时区模式不对的手机——任何一个都能让设备漂移一分钟以上。我们的TOTP 生成器显示实时时钟卡,并拿你的设备与服务器时间做比对,正是为此。打开自动日期与时间,验证码几乎总是立刻恢复——不用重置、不用换密钥、不用提工单。

密钥就是全部

剥掉仪式感,TOTP 的安全只剩一条事实:谁有密钥,谁就永远拥有你的第二因素,在任何地方,且服务无从分辨他的码和你的码。设置时扫的二维码不是“某个码的图片”——它就是密钥本体,加上账号标签和参数,格式化成 otpauth:// URI。它的截图,就是你第二因素的一份永久副本。

正因如此,使用纪律比密码还要严:密码由服务器核验、可以哈希存储;TOTP 密钥却必须能被双方以原文取回,无法哈希消解,每一份拷贝都是活拷贝。主本只放在你的认证器或加密的密码管理器里。永远不要把密钥输进在服务器上生成验证码的网站——你等于把一把永久钥匙交给了陌生人;也永远不要把它贴进聊天框,原因见下一节。

为什么密钥绝不能进聊天框

把 2FA 密钥贴进聊天——客服会话、团队频道、AI 助手——感觉无害,因为那串字符看起来像乱码,对话感觉是私密的。两者都不对。聊天会被记录、留存、同步到账号下的每一台设备、可搜索,并在你无法控制的服务器上处理;AI 聊天还要再加上训练管线和长期留存。密钥一落进去,你的第二因素就存在于为保存和索引文本而设计的系统里了。

安全的模式是:密钥只应存在于三类地方——服务的数据库、你的认证器或密码管理器、以及你掌控的设备上的本地计算——本站的TOTP 生成器正是这样造的:密钥只存页面内存,刷新即销毁。如果密钥已经被贴到了不该贴的地方,就当它已烧毁:到服务里把两步验证关掉再打开,换一把新密钥,重新绑定认证器,顺手在密码泄露检查里查一下密码本身。

TOTP 挡不住什么

TOTP 能挡住密码被盗、撞库和数据库泄露:光知道密码不再够用。它挡不住实时钓鱼:一个把你的凭据转发给真服务、再向你要验证码的假登录页,可以在 30 秒窗口内把码原样转发——验证码证明的是持有密钥,而不是你在和真网站说话。会话 Cookie 被盗同样能完全绕过它,因为验证码只校验一次。

这正是通行密钥(passkey)和硬件安全密钥在高端场景取代 TOTP 的原因:它们把证明绑定到网站的来源(origin),中继者无物可偷。TOTP 仍是强大而通用的基线——远胜于继承了电话网络弱点的短信验证码——但部署时要心里有数:把密钥当总钥匙保护,备份码单独存放,保持时钟准确,服务提供 passkey 时优先用 passkey。

来源

常见问题

能从我的验证码反推出密钥吗?

不能。每个验证码都是截断后的 HMAC 输出,而 HMAC 是单向构造:观察再多验证码,也无法重建密钥,或在没有密钥时预测未来窗口的码。第二因素真正被偷走的途径,是密钥本身泄漏(截图、聊天、服务器端生成器),以及在窗口内转发新鲜验证码的实时钓鱼。

为什么服务会接受上一个和下一个验证码?

为了容错。你的设备和服务器很少在窗口翻转的那一秒完全一致,小幅时钟偏差又很常见,所以多数服务器把相邻时间步的码也一并接受。这把有效窗口从 30 秒放宽到约 90 秒,是刻意的可用性取舍——拍下来的码在一分钟后仍然无用。

SHA-1 都“被攻破”了,基于它的 TOTP 还安全吗?

在这种构造里安全。著名的 SHA-1 攻击攻破的是抗碰撞性,那对签名和证书重要。HMAC 的安全性不依赖抗碰撞——它依赖带密钥构造表现得像伪随机函数——目前不存在从观察到的输出恢复 TOTP 密钥或伪造验证码的实际攻击。服务仍可选择 SHA-256;本站生成器三种都支持。