跳到主要内容

TOTP 动态验证码原理:两步验证那串数字到底怎么来的

两步验证里每 30 秒跳一次的 6 位码,跟 Google Authenticator 同一套算法。讲清 TOTP 怎么用 Base32 密钥和时间算码,以及你能自己验证它。

发布于 作者 李雷
#TOTP #两步验证 #2FA #安全 #验证码

TOTP 动态验证码原理:两步验证那串数字到底怎么来的

你开两步验证时,网站弹出一个二维码,扫完手机上就开始每 30 秒蹦一个 6 位数字。很多人以为这串码是手机偷偷联网问服务器要的,其实不是。手机扫码后就再没跟谁通信过,它和服务器各自算,算出来还一模一样。这件事第一次想明白的时候挺反直觉,这篇就把它拆开讲清楚。

那串码的全名叫 TOTP

它的正式名字是 TOTP,基于时间的一次性密码(Time-based One-Time Password)。规范写在 RFC 6238 里,2011 年发布,Google Authenticator、Authy、Microsoft Authenticator、1Password 跑的都是这一套,默认参数也都一样:HMAC-SHA1 算法、30 秒周期、6 位数字。所以你换验证器 App,同一个账号的码不会变,因为大家算法相同。

输入只有两样东西:一个共享密钥,和当前时间。密钥就是扫码时网站给你的那串字符,只含 A 到 Z 和 2 到 7,这种编码叫 Base32。没有 0、1、8、9,因为这几个数字长得像字母 O、I、B、g,容易抄错。常见的密钥长度是 16 或 32 个字符,比如 JBSWY3DPEHPK3PXP

时间是怎么变成验证码的

算的过程分三步。第一步,把当前 Unix 时间戳除以周期再向下取整,得到一个计数器。周期是 30 秒,所以这个计数器每过 30 秒才加一,而不是每秒都在变。第二步,用 Base32 解出来的密钥,对这个 8 字节的计数器做 HMAC-SHA1 哈希。第三步,把哈希结果动态截断,取出 6 位数字。

关键就在第一步的取整。全世界任何一台设备,只要时间大致对齐,落在同一个 30 秒窗口里,算出的计数器就是同一个值。你的手机和登录的服务器从不交谈,却因为共享了密钥、又都看着同一个时钟窗口,自然算出同一个码。这也是为什么手机时间差太多会一直登不上,因为窗口对不齐。

来一个真能复现的例子

RFC 6238 自带一组测试向量,任何实现都该算出相同结果。拿密钥 GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ,在 Unix 时间 59 秒这一刻,标准答案是 94287082。这个值不是我编的,是规范附录里写死的参考值,你用任何符合标准的工具喂同样的输入,都该得到它。

日常用的密钥短一些。拿 JBSWY3DPEHPK3PXP 这个常见示例,在 Toolora 的 TOTP 验证码生成器 里粘进去,会看到当前 6 位码,旁边一条进度条显示这个窗口还剩几秒,以及下一个窗口的码。比如此刻显示 282760,进度条剩 7 秒,条一归零,码立刻跳到旁边那个预告的值。这个跳变就是计数器加一的瞬间。

我自己踩过的坑

我第一次在后端接 TOTP,服务器把测试手机产生的每个码都拒了,折腾了一下午。后来把密钥粘进生成器,逐字段比对才发现,我的库默认是 8 位 SHA-256,而手机 App 按默认的 6 位 SHA-1 在算,参数对不上,密钥再正确码也会错。还有一次是测试机的系统时间慢了一分多钟,窗口整个错位。这两类问题,光盯着代码看一年也看不出来,把两边的码、计数器、剩余秒数摆在一起对照,几秒钟就定位了。

跟它配套的几样东西

TOTP 的核心其实是 HMAC,把密钥和计数器哈希成一段不可逆的值。想单独看 HMAC 这一步,可以用 HMAC 生成器 手动喂密钥和消息,观察输出。两步验证只是 HMAC 的一种时间化用法。

几个安全提醒。密钥本身要当成密码看待,别截图发群里,别存在明文便签里。真正保护生产账号的密钥,不要粘进任何网页,包括上面那个工具,因为浏览器扩展和旁边的人都可能看到,生成器只适合拿临时测试密钥试,或在你信得过的机器上做应急登录。正在用的两步验证密钥,留在专门的验证器 App 或硬件密钥里最稳妥。

小结

两步验证那串每 30 秒跳一次的数字,既不神秘也不联网。它是密钥加时间,经 HMAC-SHA1 算出再截断成 6 位的结果,规范是 RFC 6238,跟 Google Authenticator 同源。理解了取整这一步,你就知道为什么手机和服务器不通信却能对上,也知道接入出问题时该先查参数和时钟。


Made by Toolora · Updated 2026-06-13