跳到主要内容

浏览器工具

把你的密钥从 Google Authenticator 里取出来。

Google Authenticator 会给你一张装着手机里全部账号的二维码,然后不给你任何读它的办法。把那张码截图,拖到这里,你就能拿到每个账号的 base32 密钥和 otpauth 链接。

图片绝不会离开这个标签页。读取二维码、解析它、生成验证码,全都在你的浏览器里发生;断网之后照样用这个工具,你就能证明这一点。

工具

把导出码的截图拖到这里

也可以粘贴一张、选择文件,或者把摄像头对准手机。

解码在你自己的机器上、在这个标签页里完成,所以它必须靠 JavaScript 才能进行。这里没有任何服务器可以代劳。下面的内容讲的是这个格式,以及工具拿它做了什么;如果你更愿意自己跑一遍,源代码是公开的。

开始之前

把那张导出码当成密码来对待。

这不是走形式的警告。导出二维码比大多数人以为的更危险,具体有三点,值得在生成它之前先弄清楚。

  • 它装的是每一个密钥,不是你验证码的一张照片

    这张码里含有其中每个账号的原始密钥。拿到这张图片的人,可以在任何设备上、不需要你的手机、也不会被你察觉,无限期地为这些账号生成有效验证码。

  • 改密码并不能吊销它

    换新密码、换新手机或者删掉应用,都不会让一个已经导出的密钥失效。唯一有效的办法是在每个服务上把双重验证关掉再重新开启,这会签发一个新密钥,并让旧的作废。

  • 截图不会随那一刻消失

    截图会落进你的相册,再从那里进入你所用的任何照片备份。一年后翻到它的人,拿到的访问权限和你当时一模一样。请在你信得过的设备上做这件事,事后把图片删掉,包括「最近删除」相簿里的那一份。

返回的内容

每一个字段,以及它们各自的来处。

把这件事做对的理由是:你能分清哪些是解码出来的数据,哪些只是合理的默认值。两者都会显示出来,而且标注不同。

  • 密钥

    RFC 4648 的 base32,大写、不带填充,和身份验证器应用显示的完全一致。十字节的密钥是十六个字符,二十字节的是三十二个。这就是整个凭据。其余的一切都只是关于它的元数据。

  • 发行方与账号

    也就是服务方,以及它所属的那个账号。Google 有时把发行方放在它自己的字段里,有时又把它并进账号标签,写成「发行方:账号」,所以两处都会读取,并且分开显示。

  • 算法、位数与类型

    几乎总是 SHA-1、六位、基于时间,因为服务实际部署的就是这一套。导出内容里若有哪一项没有设置,就采用惯例上的默认值,并把该字段标注为默认值,而不是数据。

  • otpauth 链接

    一个标准的 otpauth:// URI,由上面那些字段重新拼出来,而不是解码得到的,因此它正是另一个身份验证器所期待的样子。每一个还会附带一张二维码,可以直接扫进另一个应用。

  • 周期,如实标注

    每个账号报的都是三十秒,而这个值并不是从任何地方解码出来的。导出格式里有密钥、名称、发行方、算法、位数、类型和计数器的字段,却根本没有周期这个字段。Google Authenticator 对它导出的一切都使用三十秒,所以这里报的就是三十秒,并标注为默认值,免得被误当成码里本来就有的东西。

  • 基于计数器的账号

    少数账号是基于计数器而不是基于时间的,所以它们的验证码不会轮换。计数器按导出时的值显示。这类账号要小心处理:用掉一个验证码会让服务端的计数器往前走,而落在后面的副本就不再能用。

工作原理

一共是五次解码,一次接一次。

这里面没有一步是密码学,也没有一步需要服务器。整条链短到可以完整讲清楚,而这值得讲,因为通常的解释是「装这三个程序」。

这五步都由工具自己实现,没有用第三方代码。读取二维码是其中最大的一块,而且远远大过其余几步,因为浏览器不会替你做:内置的条码检测接口在 Safari 和 Firefox 里都没有,所以解码器是照着规范写出来的。

  1. 01

    读取二维码

    图像先降成黑白两色,找出三个角上的定位图案,穿过它们拟合出一张网格(照片斜着拍的话,就按透视拟合),把每个模块逐一采样,再由 Reed-Solomon 纠错修好摄像头拍错的地方。出来的是一串以 otpauth-migration://offline?data= 开头的字符串。

  2. 02

    解码 data 参数

    对这个参数做百分号解码,得到的是标准 base64,再解出几十个字节。有一个坑值得点名:不能用 URL 查询参数解析器去读它,因为那类解析器会按表单规则解码,把加号变成空格,而标准 base64 会用到加号。

  3. 03

    解析 protobuf 消息

    这些字节是一条没有公开文档的 protobuf 消息,里面装着可重复的账号记录、一个版本号、分片总数、分片序号和一个分片标识。每条账号记录有七个字段:原始密钥、名称、发行方,以及分别代表算法、位数、类型和计数器的数字。

  4. 04

    把密钥重新编码为 base32

    密钥拿到手时是原始字节。身份验证器应用是以 base32 显示它的,所以这里把这些字节再编码一遍,得到你会手动敲进另一个应用的那串字符。

  5. 05

    生成一个验证码来核对

    基于时间的验证码,就是用那个密钥对当前的三十秒区间做 HMAC,再截断成六位。HMAC 由浏览器自带的加密接口完成,所以这里没有一处是自己手写的密码学。如果验证码和你手机上显示的一致,提取就是对的,而这也是唯一真正重要的检验。

隐私

这个说法很窄,而且你可以验证它。

无法核实的隐私说法就是营销话术。所以下面是准确的那一条,以及你花十秒钟就能做的验证。

你的图片和你的密钥绝不会被传输

你拖进来的图片、解码出的数据、密钥和生成的验证码都留在这个标签页里。处理它们的代码里根本没有对 fetch、XMLHttpRequest、WebSocket、EventSource 或 sendBeacon 的调用;离线版还加了一份内容安全策略,让浏览器来强制执行这一点,而不是听代码的一面之词。

值得你自己做一遍的验证

把网络关掉,从缓存重新加载这个页面,或者打开下载好的单文件版,然后照样使用这个工具。它能用。任何需要服务器的东西都做不到这一点。

这个页面本身会加载什么

说准确一点,因为更宽泛的说法并不成立:这个页面会加载一段自己托管的小型分析脚本,它只记录页面地址,别的什么都不记。它碰不到这个工具,也看不到你提取出了什么。如果你还是介意,离线版没有任何一处外部引用。

什么都不保存

没有 cookie,没有本地存储,没有会话存储,没有数据库,没有缓存。重新加载会丢掉你已经提取出的一切,这只会让你不方便一次,却是永远正确的做法。

清除实际上做了什么

清除会把密钥字节覆写成零。这值得做,但它不等于把内存擦干净:JavaScript 里的字符串没法被覆写,引擎可能还留着我们和你都拿不到的副本。真正把这件事做完的是关掉标签页。老实说,任何页面都不能声称做得比这更多。

已公开,而且小到读得完

整个东西就是一个采用 MIT 许可、没有运行时依赖的软件包,所以它背后没有供应链需要你去审。二维码解码器、protobuf 读取器、base32 和 base64 编解码器,以及一次性密码函数,全都在里面;它的测试对照的是已公开的测试向量,而不是它自己。

拿到那张码

导出码藏在哪里。

它不叫导出密钥,也不叫备份,而且不在设置里。Google 把它归在换新手机这件事下面,所以大多数人从来没找到过。

  1. 01在存着这些账号的手机上打开 Google Authenticator。
  2. 02打开菜单,然后选择「转移账号」。
  3. 03选择「导出账号」,在它要求时完成身份验证。
  4. 04勾选你要的账号并继续。一张二维码就出现了。
  5. 05把那张码截图,或者用这个工具的摄像头对准手机。

如果你拿到了不止一张码

列表长的时候,Google 会把它拆到几张二维码上,并告诉你现在是第几张。把它们全部捕获,然后一张接一张地加进来。它们共用一个标识,各自带着自己的序号,所以无论顺序如何都能合并,工具也会说明还缺哪些分片。来自两次不同导出的码会被拒绝,而不是合到一起,因为把不同导出中彼此重叠的账号凑在一处,会得出一份你手机上从未存在过的列表。

用截图,而不是拍照

截图一次就能解出来。拍屏幕会带来反光、摩尔纹和透视变形,这些解码器都能处理,但都不必要。如果你拍的是别人的手机,就让二维码填满画面,手机正对着镜头。

之后

拿到密钥之后该怎么办。

把它转到另一个应用里

这里每个账号都有自己的一张二维码,用的是所有身份验证器都能读的格式,可以直接扫进另一个应用。手动输入 base32 密钥也行;如果你想确认扫描过程中没出错,这样做值得。

拿一个验证码和手机对一下

在你依赖这里的任何结果之前,把生成的验证码和 Google Authenticator 在同一时刻为同一个账号显示的验证码比一比。一致,密钥就是对的。不一致,密钥就是错的,结果里其他内容也都不重要了。

像存密码那样存它

密钥应该放进密码管理器里那个账号的条目下,而不是记在便签或表格里。有些管理器在你粘贴 otpauth 链接之后会替你生成验证码。有一点值得先想清楚:同时握着密码和第二重因素的管理器是一个单点故障,这个取舍你可能愿意接受,也可能不愿意。

删掉那份捕获

包括「最近删除」相簿里的那一份,而且要在你的照片图库把它同步到别处之前。然后清除工具里的内容,关掉标签页。

细节

具体规格。

这个工具做什么、不做什么,不带营销修饰。这里的每一行都由软件包自己的测试断言过。

运行方式
完全在你的浏览器里。没有服务器,没有账号,没有要安装的东西。
可读取
Google Authenticator 的导出码(otpauth-migration),以及单账号的 otpauth 二维码
输入方式
拖放、粘贴、文件选择器,或设备摄像头
输出
base32 密钥、发行方、账号名称、算法、位数、类型、计数器、otpauth 链接、一张导入用的二维码,以及一个实时验证码
网络
没有。工具里任何地方都没有 fetch、XMLHttpRequest、WebSocket、EventSource 或 sendBeacon。
存储
没有。没有 cookie、本地存储、会话存储、数据库或缓存。
离线
可以。单文件版在断网的情况下也能从你自己的磁盘上运行。
浏览器
Chrome、Edge、Firefox,以及 Safari 16 或更高版本。摄像头需要安全页面,所以从本地文件打开时可能用不了。
依赖
运行时没有依赖。二维码编解码器、protobuf 读取器、base32 与 base64 编解码器,以及一次性密码函数,全都在这个包里。
许可协议
MIT

值得一问的问题。

把我的导出码放进一个网站里安全吗?
老实的回答是:你不该把凭据放进一个你无法核实的页面里,这也正是这个页面做成可核实的原因。什么都不传输,源代码公开,还有一个你可以下载下来、断网运行的单文件版。如果你根本不想信任任何页面,那就用那一个。
有任何内容会被发到你们的服务器吗?
不会,而且我们这边也没有任何东西能接收它。解码、解析和验证码生成都在你的浏览器里运行。打开浏览器的网络面板,提取一次,你会看到根本没有请求。
没有互联网连接也能用吗?
可以。从代码仓库的发布页下载那个单个 HTML 文件,打开它。它没有任何一处外部引用,还带着一份直接禁止连接的内容安全策略。除摄像头以外一切都能用:对于从文件地址打开的页面,有些浏览器不会授予摄像头权限;拖入或粘贴截图则始终可用。
如果有人拿到了我的导出码,我该怎么吊销它?
单独吊销一个密钥是做不到的。到受影响的服务上把双重验证关掉,再重新设置一次,这会签发一个新密钥,让旧的变得没用。改密码做不到这一点,删掉应用或者重置手机也做不到。
Google 给了我好几张二维码,接下来怎么办?
把它们全部捕获,一张一张地加进来。它们共用一个分片标识,各自带着自己的序号,所以无论顺序如何都能合并,工具也会告诉你还缺哪些分片。重复添加已经有的那一张没有坏处。来自两次不同导出的码会被拒绝,而不是合并,因为合起来的会是一份你手机上从未存在过的列表。
为什么每个账号都显示三十秒?
因为导出格式里没有周期字段,而 Google Authenticator 对所有账号都用三十秒。这个值是对的,但它不是从任何地方解码出来的,所以它被标注为默认值,而不是当成数据显示。如果某个服务用的是别的间隔,Google Authenticator 的导出内容也不会告诉你。
我有一个账号的验证码不会轮换。
那是一个基于计数器的账号,它按设计就不会轮换。它的计数器按导出时的值显示。对它要当心:用掉一个验证码会让服务端的计数器往前走,所以落在后面的副本就不再被接受。
支持哪些算法?
SHA-1、SHA-256 和 SHA-512,用的是浏览器自带的加密接口。MD5 出现在这个格式的算法列表里,但浏览器无法计算它,所以不会为这种账号生成验证码;密钥和其余每个参数依然会正确返回。实际中服务用的是 SHA-1 和六位数字,而且无论如何,Google Authenticator 都会忽略其他设置。
摄像头用不了。
它需要安全页面,所以在普通 http 上打不开,从文件地址打开也可能不行。一次拒绝往往会被记住,所以去浏览器的网站设置里看一下。不管摄像头怎么样,拖入或粘贴截图在任何地方都能用。
它打不开我的 iPhone 照片。
iPhone 拍的照片是 HEIC,除了 Safari,没有浏览器会解码它。改用截图而不是拍照,或者把文件转成 PNG 或 JPEG。截图无论如何都是更好的输入,因为它里面没有反光,也没有透视变形。
我能把这些转到另一个身份验证器里吗?
可以。每个账号都有自己的一张标准 otpauth 格式的二维码,所有身份验证器应用都能读,你也可以改成手动输入 base32 密钥。在你从手机上删掉任何东西之前,先拿一个生成的验证码和手机对一下。
我见过有人用 zbarimg 加一个 Go 程序来做这件事。
那是同样的三次解码,只是前面多了三次安装:读取二维码、对 data 参数做 base64 解码、解析 protobuf。这里把这些都放在一个页面里完成;如果你更想写脚本,背后的软件包也是公开的。这里没有包装那些工具:二维码解码器和 protobuf 读取器都是照着各自的规范写出来的。

归你所用,也任你查验。

一个采用 MIT 许可、没有运行时依赖的软件包。读它、fork 它、离线跑它,或者就在这里用。

阅读源代码