TOTP动态口令生成
把 Base32 密钥换成和手机验证器一模一样的 6 位口令,纯 JS 离线算 SHA-1 与 HMAC,支持多账户、HOTP 计数器、口令反查与时钟漂移诊断
只在本地算,密钥不外传
本页 100% 离线:所有 SHA-1 / HMAC-SHA1 / Base32 计算都是页面内的纯 JS 代码, 不发任何网络请求(连字体和图片都不从 CDN 取),断网、内网 http 打开照样能用。
账户只存在这台浏览器的 localStorage(键名 u2-totp-accounts)里, 不上传、不进任何统计;换浏览器或清掉站点数据就没了,请自行在验证器里留一份。
本页 100% 离线:所有 SHA-1 / HMAC-SHA1 / Base32 计算都是页面内的纯 JS 代码, 不发任何网络请求(连字体和图片都不从 CDN 取),断网、内网 http 打开照样能用。
账户只存在这台浏览器的 localStorage(键名 u2-totp-accounts)里, 不上传、不进任何统计;换浏览器或清掉站点数据就没了,请自行在验证器里留一份。
当前时间因子
Unix 秒—
计数值 counter—
倒计时—
计时用 Date.now() 的绝对时间戳现算,不是每秒累加:切到别的标签页、电脑休眠再回来,
口令和进度条都会直接对齐真实时间,不会越计越偏。周期切换取上方表单里的「周期」。
我的账户
口令默认打码(只露最后 3 位),点大号口令那行临时显示完整口令 10 秒后自动遮回;
点卡片空白处直接复制当前口令。上面一排按钮分别是编辑、复制 otpauth 链接、看链接原文、上移、下移、删除(删除要连点两次确认)。
新增账户
固定 HMAC-SHA1(纯 JS 实现,不用 crypto.subtle,内网 http 打开也能算)。
主流验证器(Google Authenticator / 微软 Authenticator / 1Password / FreeOTP)默认都是 SHA-1,
所以绑定时看到 SHA-256、SHA-512 的选项请保持默认;真遇上非 SHA-1 的站点,本页导入时会提醒你口令可能对不上。
从验证器导入 / 导出 otpauth 链接
本页不生成二维码(不引任何第三方二维码库):手机验证器都有「输入一个设置密钥 / 粘贴 otpauth 链接」的入口,
把上面这条链接复制过去粘贴即可,比拍照扫码更不容易出错。手动录入时要填的是去掉空格的大写 Base32 密钥,
位数和周期如果改过也要一起在验证器里改,否则两边算出来的口令不一样。
与 RFC 6238 / RFC 4226 官方向量对照
| 类型 | 输入 | RFC 期望值 | 本页算出 |
|---|
测试密钥是 RFC 6238 附录 B 的 ASCII 串 12345678901234567890,Base32 写法
GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ(8 位口令、周期 30 秒)。
这个密钥是公开印在标准里的,不要拿去绑真实账号,只用来验证算法实现对不对。
想核对真实账号时,把「载入 RFC 6238 示例密钥」生成的那个示例账户删掉即可。
工具介绍
两步验证里那六个数字不是随机数,也不是服务器发过来的——它是你手机里存的那串密钥和当前时间一起算出来的结果。任何一个持有同一份密钥、同一套参数的设备,在同一秒内都能算出同一个码。这个工具就是把这条公式在浏览器里完整跑一遍:填进密钥,页面上出现的口令就和你手机验证器上显示的一致。
口令是怎么算出来的
- 共享密钥:绑定时那串 Base32 字符(形如
JBSWY3DPEHPK3PXP),网站和你的设备各存一份,从此不再在网络上传输 - 时间因子:把 Unix 秒除以周期(默认 30 秒)向下取整,得到一个整数 counter,每过 30 秒自增 1
- HMAC-SHA1:用密钥作 key,对这个 8 字节大端 counter 做带密钥哈希,得到 20 字节摘要
- 动态截断:取摘要最后一个字节的低 4 位作为偏移,从该位置起读 4 字节并清掉最高位,得到一个 30 位整数
- 取模定长:对
10^位数取余,6 位就是mod 1000000,不足位补前导零 - 校验端同样算:服务器收到码后按自己的时间再算一遍,通常还会接受前后各 1 期,用来容忍几秒到几十秒的时钟差
页面上能做的事
- TOTP 动态口令:多账户卡片,口令默认打码只露后 3 位,点口令行临时显示完整 10 秒后自动遮回,点卡片空白处直接复制
- 时间因子面板:实时显示 Unix 秒、当前 counter、剩余秒数,环形进度与时间条在每个周期归零。计时基于绝对时间戳现算而不是逐秒累加,切到别的标签页、电脑休眠再回来都能立刻对齐真实时间,不会越计越偏
- HOTP 计数器:手填计数器值生成口令,「计数器 +1 并记住」模拟事件型令牌,还能一次列出连续 10 个口令,用来对齐 RFC 4226 附录 D
- 口令反查:把手机上正在显示的 6 位填进来,在所有账户的前 ±1 期或前 ±2 期里查找。命中上一期说明你电脑时钟快了,命中下一期是慢了,都没命中基本就是密钥不是同一份
- otpauth 链接:粘贴
otpauth://totp/...一键添加账户,也能反向导出某个账户的链接复制到手机;解析时会自动 URL 解码、容忍前后多余文字,遇到非 SHA-1 参数会提醒口令可能对不上 - 随机密钥:16 / 20 / 32 字符三档(80 / 128 / 160 bit),走
crypto.getRandomValues,给自己搭的站点生成初始密钥很方便 - 官方向量自测:内置 RFC 6238 附录 B 与 RFC 4226 的对照表,点一下就能把本页算出的值和标准期望值逐行比对,任何一格不一致都会标红
- 账户本地保存:写在浏览器
localStorage,支持编辑、上移下移、两次点击确认删除和一键清空全部
参数含义速查
| 参数 | 常见取值 | 改错会怎样 |
|---|---|---|
| 密钥 secret | Base32,A–Z 与 2–7 | 哪怕只差一个字符,口令就和对方完全不同 |
| 位数 digits | 6(少数 8) | 位数不同取模结果不同,两边看着就不一样 |
| 周期 period | 30 秒(有站点用 60) | 换算是第几期,口令永远错开一档 |
| 算法 | SHA-1(标准还允许 SHA-256 / 512) | 主流验证器默认都是 SHA-1,选别的就对不上 |
| 设备时间 | 与 UTC 相差 30 秒内 | 漂移超过一个周期就开始报「验证码错误」 |
常见问题
- 为什么我算出来的码和手机不一样?按顺序排查四件事:密钥是否漏抄或多抄字符(Base32 里没有 0、1、8、9,把
O看成0极常见)、位数与周期是否两边一致、电脑时钟是否漂了、站点是否用了非 SHA-1 算法。本页的口令反查能直接把前三条区分出来 - Base32 而不是 Base64?密钥要让人手抄或在电话里念,Base32 只用 32 个不易混淆的大写字母与数字,去掉了
0O1IL这类视觉歧义字符,也避开了 Base64 里+ / =需要转义的问题。粘贴时带空格、连字符、换行和=填充,工具会先规范化再解码,非法字符会连位置一起报出来 - 能替代手机验证器吗?能算出口令,但没有系统级通知、加密存储和多设备同步,浏览器数据被清掉就没了。稳妥用法是把它当调试与应急备用:主验证器照常绑,密钥自己再留一份离线备份
- 密钥会上传吗?不会。本页的 SHA-1、HMAC-SHA1、Base32 全是页面里的纯 JS 实现,不调
crypto.subtle、不发任何请求,连字体和图片都不从 CDN 取,断网或内网 http 打开照样能算。账户只存在你这台浏览器的本地存储里 - 账户数据怎么备份?逐个复制 otpauth 链接存进密码管理器的备注,或用「复制 otpauth 链接」导出后集中保管;密钥是明文,保管方式按密码级别对待。需要给别人看参数时,用链接原文比截图安全
- TOTP 和短信验证码哪个更稳?口令生成在本地完成,不依赖运营商通道,也就不受 SIM 互换、短信劫持和信号影响;缺点是密钥丢了没法像手机号那样通过身份复核找回,所以恢复码一定要留
- 连续两次输错会不会锁?限制在服务器侧,与本页无关。真出现「越往后越错」的情况,先对一下电脑时间,再检查是不是把 HOTP 账户当成 TOTP 用了
- HOTP 现在还有人用吗?硬件令牌和少数老系统还在用。它每用一次计数器加一,两边必须保持同步,一旦不同步就得重新配对,所以基于时间的新系统基本都是 TOTP
- RFC 6238 那个示例密钥能绑真实账号吗?不能。测试密钥
12345678901234567890(Base32GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ)是公开印在标准文档里的,任何人都能算出它的口令,只用来验证算法实现是否正确
想核对密钥串本身是不是合法 Base32,可用 Base64/Base32 编解码;做接口调试时顺手解一下 JWT 里的声明用 JWT 解析;给自己系统选哈希算法参考 哈希计算器;生成初始密钥前先看看随机口令的熵值可比较 随机密码生成 与 密码强度评估;要把 otpauth 链接交给手机扫码则用 二维码生成器;排查服务器时间偏差先对 时间戳转换。