TOTP验证码
调 2FA 登录时,为了一个 6 位码去掏手机挺麻烦。把密钥贴进来看当前口令,带倒计时和下一步预览,码点一下就能复制。计算全在浏览器里完成,密钥不发送、不存储、刷新即清空。
- 看得见剩余时间:环形倒计时加秒数,快过期时变黄提醒。
- 提前知道下一步:当前码快到期时可以先看下一个,不会填到一半失效。
- 支持 otpauth 链接:从二维码里解出来的
otpauth://链接直接粘进来,参数自动识别。
动态口令
WebCrypto 本地计算空格、横线、大小写都不影响;也可以直接粘贴整条 otpauth:// 链接。
⚠️ 这是给开发调试用的。银行、主邮箱这类高价值账号的密钥,别在公共电脑上贴进任何网页,包括这一页。本站不上传密钥,但你无法验证这一点时,就别用。
从 otpauth 链接导入
多数二维码里就是这条链接扫码工具解出来的是这条地址,把 secret 以外的参数一起带进来也行,位数、周期、算法会自动套用。
这个页面怎么算的
没什么黑箱标准名是 RFC 6238,做法只有四步:把 Base32 密钥解成字节,用当前时间除以周期得到计数器的值,对这个计数器做 HMAC,再从结果里截出一段数字。所以只要密钥、时间、周期、算法四项一致,算出来的码就一定和手机 App 相同。
这里全部用浏览器的 crypto.subtle 完成,没有发一次网络请求。你可以打开开发者工具的 Network 面板验证——输入密钥的过程中,请求列表应该一直是空的。
TOTP 是怎么算出这 6 位数的
知道原理,才能判断什么时候它算错了——绝大多数「验证码不对」其实是设备时间不对。
时间本身就是密钥的一部分
算法把「当前时间 ÷ 周期」向下取整得到一个计数器,比如 30 秒周期下,现在的时间戳会得到一个整数。这个整数和密钥一起做 HMAC 运算,结果再截断成 6 位。所以同一密钥在不同时刻的码完全不同,而且不需要联网——手机上的验证器也是这么离线算的。
对不上,九成是时间不同步
服务器允许前后一个周期左右的误差。当设备时间慢了 40 秒,算出来的码就落到上一个周期,服务器自然拒绝。手机验证器通常会自动校准,网页端没有这个能力,用的就是你系统的时间。遇到反复不对,先去系统设置里打开「自动设置时间」,再回来刷新页面看码是否变化。
Base32 不是 Base64
密钥用 Base32 编码(字母 A~Z 加 2~7),因为这种字符集在人工抄写时不容易把 0/O、1/l 看错。有些服务会把密钥按 4 位一组加空格,或者带换行,这些都可以直接粘进来,工具会自动清理。
为什么很多服务只给二维码
二维码里其实就一条 otpauth:// 链接,用任意扫码工具解出来就能看到明文密钥。这也是为什么不要随手把二维码截图发出去——它等价于把验证器直接交给别人。真要分享,分享的是密钥,而不是二维码图片,风险是一样的。
在线输入密钥的风险,说清楚
任何网页都有可能被你装上恶意扩展、被运营商注入脚本,理论上都能读到你输入的内容。本页没有任何网络请求,密钥也不写入 localStorage 或 Cookie,但「我保证安全」这种话没有意义——你自己验证才作数。高价值账号请用手机原生验证器或硬件密钥,这个页面适合调试自建服务、测试环境这类低风险场景。
常见问题
和手机验证器算出来的不一样?
按顺序查四项:① 密钥有没有粘错,尤其注意 0 和 O、1 和 I(Base32 里本来就只有数字 2~7,出现 0 和 1 基本就是抄错了);② 位数是 6 还是 8;③ 周期是 30 秒还是 60 秒;④ 算法是不是 SHA-1。四项都对但仍然不一致,看系统时间是否准确——差半分钟以上就会对不上。
验证码刷新太快,来不及填
看上面「下一步」那一行。当前码剩余时间不到 5 秒时,直接填下一步的值,等它生效再提交即可。多数服务的输入框允许在旧码失效前提交,提前一点更稳。
密钥会被保存吗?关掉页面还在吗?
不会。密钥只放在内存变量里,不写 localStorage、不写 Cookie、不进 URL,也不发网络请求。刷新页面或点「清空密钥」就彻底没有了。代价是每次都要重新输入——这是刻意的取舍。
为什么支持 SHA-256?不都是 SHA-1 吗?
绝大多数服务用 SHA-1(RFC 6238 的默认值),但部分企业系统会改用 SHA-256 或 SHA-512。如果密钥对、位数对、周期对,算出来却始终不一致,可以试着切成 SHA-256 看看。反过来,如果不知道用哪种,先试 SHA-1。
可以批量算多个账号的码吗?
当前不做。多密钥意味着要在内存里同时留存多份敏感数据,也会让界面变复杂。调试单一服务时,一次一个够用了。