Authenticator 批量导入 把多条 2FA 密钥打包成一张 otpauth-migration:// 迁移二维码,用 Google Authenticator 的「转移账号 → 导入账号」一次性扫进去。支持完整 otpauth 链接、「名称:密钥」和纯 Base32 三种写法混填,账号多时自动拆成多张码。全部在浏览器本地生成,密钥不上传、不留存。
Google Authenticator 有个长期让人难受的地方:它能把自己的账号导出成迁移二维码,却没有提供反向的批量导入入口——想加账号只能一个一个扫服务方给的码,或者手动敲账号名加密钥。
于是从别家(1Password、Bitwarden、Authy)搬过来的人就卡住了:那些工具导出的是一条条 otpauth://totp/... 链接,Google Authenticator 不认这种批量格式。
但 GA 自己用的迁移格式是公开可构造的——它就是一段 protobuf,装着账号名、发行方、密钥和参数,再 base64 塞进 URL。既然能解开,就能反过来拼出来。这一页做的就是这件事。
otpauth-migration://offline?data=<base64(protobuf)>
protobuf 里每个账号是这样一条记录:
| 字段 | 内容 |
|---|---|
| secret | 密钥原始字节(不是 Base32 文本) |
| name | 账号标签,常见形如 GitHub:me@example.com |
| issuer | 发行方 |
| algorithm | SHA1 / SHA256 / SHA512 / MD5 |
| digits | 只有六位和八位两个取值 |
| type | TOTP 或 HOTP |
| counter | HOTP 专用的计数器 |
外层还有 batchSize / batchIndex / batchId 三个字段用来标记分张:同一次导出的几张码 batchId 相同,验证器靠它把它们认作一批。
这一页刻意不保存任何东西——不写 localStorage,刷新即清空。原因很直白:这里粘进来的密钥不像验证码那样三十秒就失效,它是永不过期的长期凭证,谁拿到谁就能算出你的两步验证码。生成的二维码同理,截图存在相册里等于把钥匙配了一把放在门口。
不会上传。Base32 解码、protobuf 编码、二维码绘制全部在你的浏览器里完成,不发任何网络请求。输入和生成结果都不写入 localStorage,页面一刷新就没了。但请注意:生成的二维码里装的是明文密钥,等同于你全部账号的长期钥匙——不要截图发给别人、不要在公共设备上操作,用完就关掉页面。
三种都认,可以混着写,每行一条:① 完整的 otpauth:// 链接(会自动读出里面的账号名、发行方、算法、位数、周期);② 名称:密钥,比如 GitHub:me@example.com:JBSWY3DPEHPK3PXP;③ 光秃秃一条 Base32 密钥。后两种默认按 TOTP、6 位、SHA1、30 秒处理。
二维码本身有容量上限,装的数据越多模块格子越密,密到一定程度手机就对不上焦、扫不出来。所以账号多时会自动按数据量拆成多张,和 Google Authenticator 自己导出时的做法一致。这几张码属于同一批(内部共享一个 batchId),在验证器里要每张都扫一遍,少扫一张就会缺账号。
Google Authenticator 迁移格式的数据结构里,位数字段只有 6 位和 8 位两个取值,写不进 7 位。这是 Google 定的格式限制,不是本工具的取舍。7 位的账号只能在验证器里手动添加,或者改用支持任意位数的验证器 App。
微软 Authenticator、2FAS、Aegis 等多数主流验证器都认 otpauth-migration 格式,可以直接扫。少数 App 只支持单条 otpauth 链接,那就用工具箱里的 TOTP 验证码页逐条生成二维码,或者直接复制链接手动导入。
方向正好相反。那一页是把 Google Authenticator 导出的迁移码拆开,读出里面的密钥,用于搬离 GA;这一页是把散落的密钥装回迁移码,用于搬进 GA。两个配合就是一条完整的双向迁移链路。