TOTP 算法原理详解:30 秒验证码是怎么生成的
最后更新:2026/9/14
打开谷歌验证器或 free2fa二次验证码 小程序,你会看到一个每 30 秒变化一次的 6 位数字。断网也能变,刷新页面也不需要向服务器要码。
这就是 TOTP(Time-based One-Time Password,基于时间的一次性密码)。它已经写入国际标准 RFC 6238,也是 GitHub、Google、OpenAI 这类平台两步验证(2FA)的主流算法。
一句话讲完原理:
TOTP 验证码 = HMAC(共享密钥, 当前时间因子) -> 动态截断为 6 位数字
下面按计算顺序拆开。
TOTP 需要哪三样东西
生成一个 TOTP 验证码,客户端和服务器必须事先约定三件事:
| 要素 | 常见取值 | 作用 |
|---|---|---|
| 共享密钥(Secret Key) | 绑定二维码里的那段密钥 | 只有双方知道,用来做 HMAC |
| 时间步长(Time Step) | 通常 30 秒 | 把连续时间切成固定格子 |
| HMAC 算法 | 默认 HMAC-SHA-1 | 把密钥和时间因子混成一段哈希 |
少了任何一项,两边就算不出同一个码。
共享密钥是什么
绑定两步验证时,平台会给你一张二维码。扫进去的,其实就是一段 共享密钥。
这段密钥通常用 Base32 编码,同时保存在你的验证器和平台服务器上。之后每次出码,都不需要再联网传密钥,双方各自拿本地保存的密钥来算。
这也是为什么验证器断网还能用:验证码不是服务器发来的,是手机本地算出来的。
密钥一旦泄露,别人就能算出和你一样的动态码。所以一定要保管好自己的密钥。
使用谷歌身份验证器的用户,之前都很怕换手机、卸载APP或者误删账号。其实用户真正怕的不是 6 位数字丢了,而是这段密钥丢了。
目前,微信小程序「free2fa二次验证码」解决了密钥数据加密自动云备份的问题,大家不必再为此焦虑。

时间窗口怎么算
HOTP 用递增计数器。TOTP 把计数器换成了时间。两者的取舍见TOTP 和 HOTP 该怎么选。
标准算法是:
时间因子 T = floor(当前 Unix 时间戳 / 30)
这里的“当前时间戳”指的是从1970-01-01 00:00:00 UTC 算起,直到现在这一秒,所经过的秒数。用秒数除以 30 再向下取整,得到当前落在第几个 30 秒格子里。
同一个 30 秒窗口内,T 不变,验证码也就不变。跨进下一个格子,T +1,验证码立刻换成新的。
所以你看到的不是“每秒都在变”,而是“每 30 秒换一格”。格子还没走完,这个码对服务器仍然有效。

HMAC 做了什么
有了密钥和时间因子,下一步不是直接把它们拼成 6 位数字,而是先做一次 HMAC。
HMAC-SHA-1(共享密钥, 时间因子 T) -> 20 字节哈希
HMAC 的作用其实也很简单:没有密钥的人,就算知道现在是第几个 30 秒窗口,也算不出正确的哈希。时间因子每变一次,哈希就整段重算。
RFC 6238 默认用 HMAC-SHA-1。标准也允许扩展到 SHA-256 或 SHA-512。一些企业级硬件令牌会采用 SHA-256 版本的 TOTP,例如 YubiKey 的 OATH 应用和飞天诚信 ePass C200。普通 App 和微信小程序验证器,绝大多数仍是 SHA-1 + 6 位数字,这样才能和 GitHub、Google 这类平台兼容。
20 字节怎么变成 6 位数字
HMAC 算出来的是 20 字节,不能直接拿去登录。TOTP 接着做 动态截断(Dynamic Truncation):
- 取哈希最后一个字节的低 4 位,得到一个偏移量
- 从偏移量开始,取出 4 个字节
- 把最高位清零,转成一个正整数
- 对 1,000,000 取模,得到 0 到 999999 的数字
- 不足 6 位时左边补 0
于是你看到的就是 894050 这种 6 位码。
少数系统会要求 8 位。算法一样,只是最后取模的位数不同。平台和验证器必须约定同一位数,否则对不上。
服务端为什么容忍前后一个窗口
按公式,客户端和服务器只要时钟一致,算出的码就一样。现实里手机时间可能快几秒、慢十几秒。
所以服务端通常不只认当前这一个 30 秒窗口,还会额外检查 前一个 和 后一个 时间片。也就是大约 90 秒的容忍范围。
这是体验和安全之间的折中:
- 完全不容忍,手机差 10 秒就登录失败
- 容忍太多,一个码的有效期被拉得很长,被截获后可利用的时间也变长
即便在有效窗口内,同一个码也不应被使用两次。服务端必须拒绝重复提交,否则 30 秒内仍可能被重放。
验证码突然全错,优先检查手机系统时间,而不是先怀疑密钥坏了。很多验证器会提供时间校准,就是为了把本地时钟拉回正确的时间格子。比如,free2fa二次验证码小程序就自带“时间校对”功能,便于用户快捷检查本机时间和服务器时间是否有差异。

常见误区
- 验证码是服务器发到手机上的。 不是。TOTP 是本地计算,短信验证码才是服务器下发。
- 30 秒一到,旧码立刻在所有服务器上失效。 不一定。服务端常容忍前后一个窗口,旧码可能再多活几十秒。
- 同一个 30 秒窗口内,把码发给别人也没关系,过期就作废。 窗口内仍可能被重放。安全的服务端会记录已使用的码,拒绝第二次提交。
- 密钥可以随便保存在截图、备忘录、聊天记录里。 密钥等于永久出码权。截图和明文备份一旦泄露,动态验证码这层就失效了。
保管密钥的几个要点
TOTP 并不神秘。共享密钥负责证明“是你”,时间因子负责让码定期作废,HMAC 和动态截断负责把这两样东西压成 6 位数字。
日常使用时,真正要保管的是密钥,不是某一瞬间的 6 位码。换手机、卸 App、误删账号之前,先确认验证器有加密备份。微信里搜索 「free2fa二次验证码」,或用微信扫描下方小程序码即可打开:

