TOTP 验证码是什么?2FA 动态验证码的工作原理
TOTP(基于时间的一次性密码)是验证器 App 用的那套机制。它只做一件事:把一把固定的密钥和当前时间放在一起,算出一个 6 位数字。时间在走,所以数字一直在变。
本文另有 English

TOTP 是什么
TOTP 是一套算验证码的规则,写在 RFC 6238 这份标准里。Google Authenticator、Microsoft Authenticator、Authy、1Password 里的验证码功能,用的都是它,所以它们之间可以互换。
它的输入只有两样:
- 一把密钥(开启两步验证时平台给你的那串 Base32 字符)
- 当前时间
输出是一个 6 位数字。密钥不变,时间在变,所以数字每 30 秒换一个。
TOTP 和 2FA 是什么关系
2FA 说的是「除了密码,再要一个证据」这件事本身。至于第二个证据用什么,有很多选择:短信、邮箱、硬件密钥、验证器 App。
TOTP 是其中一种实现。它之所以流行,是因为它不需要平台承担任何发送成本,也不依赖手机号,而且断网可用。
所以「2FA 验证码」这个说法在日常语境里通常指的就是 TOTP 验证码。
核心只有一步:密钥 + 时间 → 验证码
把整套机制压缩成一句话:
密钥(不变) + 当前时间(一直在走) → 当前验证码
具体是这样运转的:
- 取当前时间的 Unix 时间戳,也就是从 1970 年 1 月 1 日到现在的秒数。
- 除以 30 并取整,得到一个「时间片编号」。同一个 30 秒窗口内,这个编号不变。
- 用密钥对这个编号做一次 HMAC 运算,得到一串固定长度的字节。
- 从这串字节里截取一段,转成数字,取末尾 6 位。
关键在第二步。时间不是按秒进入计算的,而是先切成 30 秒一片。这样在同一片时间里,任何时刻算出来的结果都一样——你和平台不需要时间精确到毫秒一致,只要落在同一片里就行。
第三步的 HMAC 是一种单向运算:给定密钥和输入,结果唯一且可重复;但反过来,拿着一堆结果推不出密钥。这就是为什么把验证码给别人看,对方也无法反推出你的密钥。
为什么网站和验证器能算出一样的验证码
因为双方手上的两个输入是同一份。
密钥在开启两步验证的那一刻就交接完毕,平台存着一份,你的验证器存着一份。时间则是全世界共用的同一个 Unix 时间戳。两个输入相同,同一套算法自然得出相同的 6 位数字。
平台核对时做的事很简单:用它存的密钥算一次当前验证码,和你输进来的比一比。
正因为它靠的是双方时间一致,时间就成了整套机制里最脆弱的一环。设备时间慢了一分钟,算出来的就是一分钟前那一片的验证码,平台自然不认。这也是 2FA 验证码报错最常见的原因。
考虑到网络延迟和手动输入的耗时,平台通常会同时接受前一片和后一片的验证码,相当于给了大约正负 30 秒的容错。少数平台放得更宽,也有平台严格只认当前这一片。
为什么每 30 秒更新
30 秒是标准里的默认值,也是两种压力之间的折中。
时间片越短,验证码泄露后可用的时间越短,但用户越容易在输入过程中赶上刷新,体验变差。时间片越长,输入从容,但一个被人看到的验证码可以用更久。
30 秒之所以成为事实标准,主要是因为绝大多数验证器都按它实现,平台按它校验。有些平台会用 60 秒,otpauth 链接里的 period 参数就是用来声明这件事的。
为什么是 6 位
算法在截取那一步会得到一个很大的数字,取末尾几位就是验证码。取几位是可配置的,标准默认 6 位。
6 位有一百万种组合。配合 30 秒有效期和平台的失败次数限制,猜中的概率低到没有实际意义。再加位数会提高安全性,但输入负担也上去了——8 位的实现存在,但少见,通常出现在对安全要求更高的企业系统里。
otpauth 链接里的 digits 参数负责声明位数。
Base32 在这里做什么
算法需要的是密钥的原始字节,而人需要的是能抄写、能复制的文本。Base32 负责在两者之间转换。
它的字符表是 A–Z 加 2–7,刻意去掉了 0、1、8、9 和 O、I、L——这些字符手抄时容易互相认错。所以密钥里出现小写字母不用担心(不区分大小写),但出现 0、1、8、9 基本可以断定抄错了。
工具拿到密钥后的第一步就是把 Base32 文本还原成字节,然后才进入 HMAC 运算。这一步失败,就是你看到「密钥格式不对」的时候。密钥本身长什么样、在哪里找,见2FA 密钥是什么。
二维码里到底存了什么
二维码不是加密,只是把一段文字画成图案。开启两步验证时那张码里存的是这样一行:
otpauth://totp/GitHub:zhang@example.com?secret=JBSWY3DPEHPK3PXP&issuer=GitHub
密钥就明文写在 secret= 后面。验证器扫码,做的是读出这行文本、取出密钥、把账号名存进列表——和你手动输入密钥完全等价。
如果链接里还带着 algorithm=SHA256、digits=8 或 period=60,说明这个平台没用默认参数。这种情况下只抄 secret 的值会算出对不上的码,得把整条链接一起用。
顺带一个安全提醒:这张二维码的截图和密钥本身一样敏感,任何人扫到它就等于拿到了密钥。
TOTP 不是短信验证码
两者都是 6 位数字,来源完全不同。
| TOTP 验证码 | 短信验证码 | |
|---|---|---|
| 谁生成 | 你的设备本地算 | 平台生成后下发 |
| 传输过程 | 没有传输 | 经过运营商网络 |
| 断网能用 | 能 | 不能 |
| 主要风险 | 密钥泄露 | 短信被拦截、SIM 卡被转移 |
| 有效期 | 通常 30 秒 | 通常几分钟 |
短信验证码的弱点在于「要送达」,路上就有被截走的可能。TOTP 没有这段路,代价是密钥必须自己保管好。
在浏览器里生成 TOTP 验证码
理解了原理,操作就很直接:有密钥就能出码,不必先装 App。账号、密码和密钥各自填在哪,见2FA 密钥使用教程。
把密钥粘贴进 2FA 验证码生成器,页面用浏览器自带的 Web Crypto 做上面那套 HMAC 运算,给出当前验证码和剩余秒数。多个账号一行一个密钥,各自出码、各自倒计时。带参数的 otpauth 链接可以整条粘贴,algorithm、digits、period 会被读出来。
密钥只在浏览器里参与计算,EnvTrace 不会把它发到服务器,也不会写进存储。
如果算出来的码平台不认,先量一下设备时间——按上面的原理,时间偏差是唯一能让「密钥完全正确」和「验证码错误」同时成立的原因。排查步骤见2FA 验证码无效怎么办。