2FA 验证码总是对不上?TOTP 时间步、SHA 算法与 Base32 密钥三类坑排查

· 约 4 分钟 🔐 TOTP 验证码

不管是自己动手实现两步验证登录,还是换手机后想用备份密钥恢复 Google Authenticator,最容易让人抓狂的就是:算出来的 6 位码,和服务端要的就是对不上。TOTP 的迷人之处在于双方不通信也能验证同一个码,但这也意味着任何一个参数错位都会导致完全不同的结果、且没有任何报错提示。这篇先讲清它怎么算,再把对不上的原因按概率排查掉。

TOTP 是怎么算出来的

TOTP(基于时间的一次性口令,RFC 6238)的全过程其实只有四步:

  1. Base32 解码 密钥,得到二进制 key
  2. 当前 Unix 时间 ÷ 周期(默认 30 秒)向下取整,作为计数器 counter
  3. 用 key 对 counter 做 HMAC-SHA1(或 SHA256/SHA512)
  4. 对 HMAC 结果做动态截断,取末几位十进制即为验证码

关键在第 2 步:counter 来自时间,所以同一个 30 秒窗口内你和服务端算出的 counter 相同、码相同;窗口一过 counter 加一、码换新。双方共享的只是那个静态密钥,验证时各算各的、无需联网——这就是为什么验证器离线也能用。理解了这条链路,排查就有了方向:密钥、时间、算法参数,任何一环错位,码都不对。

坑一:设备时间不准(最高频)

因为 counter 直接由时间分片而来,本机时钟偏差是验证码对不上的头号原因。时间快或慢超过一个时间步(30 秒),算出的 counter 就和服务端差一格,码彻底不同,而且没有任何”时间错了”的提示。

  • 解决:打开系统”自动设置时间 / 自动对时”,与 NTP 服务器同步
  • 验证器 App(如 Google Authenticator)也有”时间校正”选项,本质是和 Google 的时间服务器对一次时间偏移

服务端为了容错,通常会接受前后各一个窗口(±30 秒)的码,但这只能容忍轻微漂移,本地时钟差几分钟照样失败。

坑二:算法、位数、周期不一致

TOTP 有三个可调参数,默认值是 SHA1 / 6 位 / 30 秒(绝大多数验证器和服务都按这套),但服务端完全可以改:

参数默认可选不一致的后果
算法SHA-1SHA-256 / SHA-512码完全不同
位数67 / 8长度和数值都不同
周期30 秒15 / 60 秒分片错位、码不同

只要有一项不符,码就对不上。如果对方给了标准的 otpauth:// 链接,里面的 digitsperiodalgorithm 参数会被工具自动解析,照着填最稳;没有链接就逐项问清服务端配置。

坑三:密钥不是 Base32

TOTP 密钥按 Base32(RFC 4648)编码,字符集只有 A–Z 和 2–7,大小写不敏感、空格和短横线会被忽略。常见误区:

  • 十六进制密钥当 Base32 填(Hex 含 0、1、8、9 和 a–f,Base32 里没有这些数字)
  • 复制时带进了多余字符、换行或把展示用的分组空格当成了密钥的一部分(其实空格会被自动忽略,整段粘贴即可)

拿到密钥先看字符集:只要出现 0、1、8、9 或小写 a/f 等 Base32 之外的字符,基本可以断定它不是 Base32,要回头确认来源或编码方式。

备份密钥与跨设备恢复

很多人换手机后才发现验证器里的码全没了——因为 2FA 绑定的是密钥,不是 App。每次开启两步验证时,屏幕上那串字母(或二维码otpauth 链接的 secret)就是密钥,务必离线备份原始密钥或服务给的恢复码,而不是只截个验证器界面的图。

只要留存了密钥,任何设备上输入它、参数设一致,就能重新算出当前码登录,再到账户安全页重新绑定新设备。临时应急、或验证自己实现的服务端逻辑时,本工具就是这样一个”有密钥就能出码”的地方——但密钥是长期凭证,别在公共设备粘贴真实账户的密钥

小结

验证码对不上,几乎逃不出三类:设备时间不准、算法/位数/周期不一致、密钥不是 Base32。按这个顺序排查——先校时、再核参数、后查密钥编码——基本都能解决。理解 TOTP”密钥 + 时间 → 动态码”的本质,既能快速定位联调问题,也能在换设备时靠备份密钥从容恢复 2FA。

❓ 常见问题

为什么我本地算的验证码和服务端就是对不上?

按概率从高到低查三处:① 设备时间不准——TOTP 完全靠系统时间分片,差超过一个时间步(默认 30 秒)就错,先校准时钟并开启自动对时;② 参数不一致——服务端可能用 SHA256/SHA512、8 位或 60 秒周期,而你按默认 SHA1/6/30 算;③ 密钥格式——密钥必须是 Base32(只含 A–Z 和 2–7),别误填成 Hex 或带了多余字符。三处对齐后码就一致了。

TOTP 和 HOTP、短信验证码有什么区别?

HOTP 基于"计数器"(每用一次加一),TOTP 基于"时间"(当前时间除以周期作为计数器),所以 TOTP 的码每 30 秒自动换、过期作废,这就是验证器里那串不断刷新的数字(RFC 6238)。短信验证码是服务端随机生成再下发,依赖网络和运营商;TOTP 算法公开、密钥共享后双方各自本地算、无需联网通信,更快也更抗短信劫持。

默认 SHA1 算法是不是不安全?为什么不用 SHA256?

TOTP 用的是 HMAC-SHA1,不是裸 SHA1。HMAC 结构对底层哈希的抗碰撞性要求低得多,HMAC-SHA1 至今没有实用攻击,且这是 RFC 6238 的默认值、几乎所有验证器和服务都按它实现,互通性最好。SHA256/SHA512 可选但并不会显著提升 2FA 安全性(动态码就 6 位、30 秒有效),改了反而可能和对方不互通。除非服务端明确要求,否则保持 SHA1。

换手机后 Google Authenticator 里的码没了,能用密钥恢复吗?

能,前提是当初保存了密钥。每个 2FA 条目背后都是一个 Base32 密钥(添加时屏幕上的那串字母、或二维码里 otpauth 链接的 secret 参数)。只要留存了密钥,在任意 TOTP 工具里输入它、参数设成一致,就能重新算出当前码登录,再到账户安全设置里重新绑定新设备。所以开启 2FA 时务必把密钥/恢复码离线备份——光备份验证器 App 的截图不够,要存原始密钥。

在这个工具里粘贴真实账户的密钥安全吗?

计算本身全在你浏览器本地完成、不上传、不联网,这一点是安全的。但务必注意:密钥(seed)是长期凭证,等同于你 2FA 的"种子",一旦泄露别人就能持续生成你的动态码。所以绝不要在公共/不可信电脑上粘贴真实账户密钥,也别截图明文存档。本工具定位是开发调试、参数验证和应急取码,日常 2FA 请继续用有加密存储的验证器 App 或硬件密钥。

🔐 打开 TOTP 验证码 2FA 动态口令 · 兼容 Google Authenticator · 倒计时 · otpauth 二维码 · 本地计算不上传