跳到正文
Biqilu Line

登录与验证常识

验证器应用是什么,它的 6 位验证码为什么离线能出、时间一错就失效

这串数字没有人发给你,是手机自己算的。知道它怎么算,二维码为什么不能外传、手机时间为什么要准,就都有了答案。

最后核对

身份验证器的验证码是手机自己算出来的:开启时网站交给它一份密钥,之后每次出码,用到的只有这份密钥和手机上的当前时间,所以断网、没信号都能出。参与计算的时间按固定长度分段,默认每 30 秒一段,进了下一段,算出来的就是另一个数。网站那头拿同一份密钥、按服务器的时钟也算一遍再比对,手机时间偏多了,两边算的不是同一段时间,屏幕上的码看着正常,却怎么输都不对。

验证器是装在手机上的出码应用

验证器应用是装在手机上、专门生成一次性验证码的应用。Google 对自家那一款的说明是:为支持身份验证器应用两步验证功能的网站和应用生成一次性验证码。你在某个网站开启这种验证方式以后,登录或做敏感操作时,除了密码,还要输入应用里当下显示的那串数字。

美国国家标准与技术研究院(NIST)的 SP 800-63B 文档把这类工具叫作一次性密码(OTP,one-time password)验证器,硬件设备和装在手机上的软件生成器都算在内。它的描述是:验证器里嵌着一个秘密值,作为生成验证码的种子;码显示在验证器上,由人手动输入,以此证明这个验证器在你手里。按它的分类,这属于「你拥有的东西」,和「你知道的东西」(密码)是两类。

算码的方法是公开的,写在两份 RFC 文档里:2005 年 12 月的 RFC 4226 和 2011 年 5 月的 RFC 6238,都属于参考性质,不在互联网标准的序列里。后一份的摘要里说明了公开的用意:让商业产品和开源实现之间可以互通。

一个 6 位码从扫码到核对是怎么算出来的

RFC 4226 定义的算法叫 HOTP(HMAC-Based One-Time Password),用「密钥 + 一个不断递增的计数器」算码。RFC 6238 的 TOTP(Time-Based One-Time Password)把计数器换成了时间,手机验证器里每隔一会儿自己换的那种码就是它。一次完整的验证分五步:

  1. 开启时交接密钥。网站为你的账户准备一份密钥(共享密钥,shared secret),自己留一份,另一份通过页面上的二维码交给验证器应用。RFC 6238 要求密钥一户一份,不同账户之间不共用;RFC 4226 要求密钥至少 128 位,推荐 160 位。
  2. 把当前时间换成时间段编号。验证器读取手机的当前时间,换算成从 1970 年 1 月 1 日 UTC 零点起经过的秒数(Unix 时间),除以时间步长(time step,默认 30 秒)后向下取整。
  3. 用密钥和编号算出一个数。两者一起送进一种叫 HMAC 的散列运算,得到一长串结果;按固定规则从中截出一小段,换算成一个整数,取末 6 位。屏幕上显示的就是这 6 位。
  4. 你把这 6 位输入网页或 App 的验证框。
  5. 网站用自己留的那份密钥和服务器的时间,照同样的步骤算一遍。两边的数相同,验证通过。

第 2、3 步全在手机里完成,不需要向网站要任何东西。Google 帮助页写的「即使未连接到网络或移动网络服务,您仍可以生成验证码」,原因就在这里。

时间段编号可以拿 RFC 6238 自己的例子来看:步长 30 秒时,Unix 时间为 59 秒,编号是 1;到了 60 秒,编号变成 2。编号一变,第 3 步的输入就变了,算出来的 6 位数也跟着变。

至于为什么是 6 位:按 RFC 4226,HMAC 运算的原始结果有 160 位,人没法照着输,所以要截短;文档要求截出来的码至少 6 位,并且最好只含数字,方便在按键有限的设备上输入。

30 秒一换是推荐值,同一个码只能用一次

同一个时间段里,不管你打开验证器看几次,码都是同一个。RFC 6238 把时间步长列为「系统参数」,推荐的默认值是 30 秒,并说明这个数是在安全和好用之间取的折中。关于步长,它写成「必须」的只有一条:验证器和网站必须用同一个步长,这个值在开启时就约定好了。

步长不设得更长,文档给了两个理由。一是码在用掉之前如果被第三方看到,对方可以在这个时间段内抢先用掉,时间段越长,留给对方的时间越多。二是想拿到一个不同的码,得等时钟走进下一个时间段;文档举的例子是,步长如果长到 10 分钟,多半不适合平常的网站登录。

按 RFC 6238 的建议,网站那头不该只认当前这一个时间段。码从你手机上显示到抵达服务器,中间有输入和网络传输的延迟,在一段末尾生成的码,到达时多半已经进了下一段。RFC 6238 建议网站为这种延迟定一个可接受的范围,并建议最多容许一个时间步长;范围越宽,可被利用的时间也越长。具体怎么定是各网站自己的事,所以刚换掉的那个码有时还能通过,有时不能。

RFC 6238 还有一条写成了「必须」:一个码验证成功以后,网站不得再接受同一个码的第二次提交。NIST 的文档里也有同样的要求。刚用一个码登录成功,紧接着另一个操作又要验证码,这时把刚才的码再输一遍会被拒绝,要等下一个。

手机时间不准,验证码为什么全错?

手机时间偏了几分钟,第 2 步换算出的时间段编号就和服务器的差出好几段,两边算出的 6 位数没有一个对得上。验证器只是照着手机给的时间在算。

小幅偏差,网站可以设一个容许范围来消化。RFC 6238 建议网站设一个上限,规定验证器的时间最多可以偏出几个时间步长,超过就拒绝。文档举的例子是:步长 30 秒、只往回多认两个时间段时,能容许的时间差大约 89 秒。这是文档里的举例,各网站实际认多宽由它们自己设定。文档还提到,验证器越久没用,累积的时钟偏差可能越大。

码一直提示无效时,Google 帮助页给的检查项有四条:输入的验证码未过期;输入的是相应应用或服务的验证码;输入的是相应 Google 账号的验证码;设备上的时间已同步,与所在时区的时间一致。中间两条说的是同一类失误:验证器里存了好几个账户时,把另一个账户的码输了进去。

第四条去手机系统的日期与时间设置里处理,建议把时间和时区都交给系统自动设置。不用在 Google 身份验证器里找「时间校正」:帮助页写明,7.0 版已不再支持时间校正设置,应用直接使用操作系统的时间设置。

连着输错几次会怎样

6 位数字一共只有一百万种组合,RFC 4226 明说把结果截得这么短会让逐个试码变得可行,所以服务器必须能发现并拦住这种尝试。它推荐两种做法:给验证失败的次数设一个上限,数值在不明显影响使用的前提下尽量小;或者每失败一次,下一次尝试前的等待时间就加长。达到上限以后,文档建议锁定账户并通知用户。

落到实际使用上:码提示错误时,别一个接一个地试。先按上面四条查一遍,尤其是手机时间。登录时弹出账户被禁用或被限制的提示,入口见账户被锁定或限制后去哪里处理。

二维码里那串密钥等于验证器本身

出码只需要两样东西:密钥和时间。时间人人都有,所以密钥在谁手里,谁就能算出和你完全相同的码。开启时页面上显示的二维码里装的就是这份密钥;页面同时给一串可手动输入的设置密钥的,作用相同。这个页面不要给别人看,也不要发到聊天软件里。

网站那头同样留着一份。NIST 的文档写到,验证方实际上是把生成验证码的过程重复一遍,因此验证器用的密钥在验证方那里也有,必须严格限制访问。RFC 6238 也建议服务端把密钥加密存放,只在核对验证码时短暂解开。

密钥在手机上,换手机、丢手机时要处理的也是它。Google 帮助页的说法是:设备丢失或被盗时,先用系统的远程清空功能把设备上的验证码抹掉;清空不了的,验证码已同步到 Google 账号就从账号里移除那台设备,没同步就到每一个设置过验证器的网站上移除旧的,再重新关联新设备。旧手机还在手里的,按换手机前先把验证器转到新手机的顺序做。

和短信码、邮件码放在一起看

三种码输入时看起来差不多,来路不一样:

由谁生成怎么到你手里手机没有网络和信号时
短信或语音验证码网站的服务器经电话网络发到你的手机号收不到
邮件验证码网站的服务器发到你的邮箱收不到
验证器验证码手机和网站各算各的不经过任何传送,你照着屏幕输入照常显示

短信和语音验证码在 NIST 的文档里对应「带外(out-of-band)」方式:一个短期有效的秘密值由验证方生成,经另一条通道发给你,你再把它填回登录页面。文档提醒,手机信号覆盖有限的地方可能收不到经电话网络发送的验证码,所以要求采用这种方式的系统同时提供别的验证方式。验证器的码不走任何通道,这类问题碰不上;短信、邮件迟迟不到时怎么查,见收不到验证码时的排查顺序。

有效期的来历也不同。发来的码能用多久,由发码的一方规定。NIST 这份指南面向的是接入美国政府信息系统时的身份验证,它定的是:这类验证 10 分钟内没完成就作废,同一个码在有效期内只接受一次。你收到的那条码有效多久,以发码那一方在短信、邮件或页面上写的为准。验证器的码没有「发出后多少分钟」这种算法,它的寿命由时间步长加上网站容许的偏差决定;NIST 的表述是,基于时间的一次性密码要有一个明确的有效期,长短取决于预期的时钟偏差,再加上网络延迟和用户输入所需的时间。

几种让人不放心的情况

出国或换了时区,验证器要不要重新调?

不用在验证器里调。算码用的是从 1970 年 1 月 1 日 UTC 零点起经过的秒数,这个数在哪个时区都一样。手机的时间和时区由系统自动设置时,换时区后码照常能用。要避免的是手动把钟点拨成当地时间、时区却没跟着改,那样手机上的实际时间就偏了。Google 帮助页列的排查项也是这一条:设备上的时间已同步,与所在时区的时间一致。

两台手机能同时显示同一个账户的验证码吗?

能。码只由密钥和当前时间段决定,两台设备存着同一份密钥、时间都准,同一时间段里显示的就是同一个数。Google 身份验证器登录 Google 账号后,验证码会在你的各台设备之间保持同步,就是这种情况。同一个码在网站那头只接受一次,在一台手机上用过,另一台上的同一个码也跟着作废,要等下一个。

为什么有的网站要输 8 位而不是 6 位?

位数是网站和验证器事先约定的参数。RFC 4226 规定至少取 6 位,也可以取 7 位或 8 位。按它附录里的算式,从 6 位换成 8 位,别人靠猜蒙中的概率降到原来的百分之一。位数多少由网站决定。

开启时把二维码截图存起来,安全吗?

截图等于多留了一份密钥。相册里的图如果会同步到云端或别的设备,密钥也就跟着到了那些地方,能看到这张图的人可以把它扫进自己的验证器,算出和你一样的码。想留备份,更稳妥的做法是用验证器自带的同步或导出功能,再给账户多登记一种验证方式;已经截了图的,确认验证器能正常出码后把图删掉,回收站也清一遍。

参考页面

栏目里的其他几篇

全部 9 篇