写点什么

谷歌两步验证系统的工作原理

  • 2014-09-18
  • 本文字数:1538 字

    阅读完需:约 5 分钟

为了改进 Android 的安全问题,Google 在 Android 系统中引入了谷歌验证应用(Google Authenticator)来保证账号的安全。谷歌验证应用的使用方法是:用户安装手机客户端,生成临时身份验证码,提交到服务器验证身份,类似的验证系统还有 Authy。Robbie 在其 GitHub 页面发布了自己用 Go 语言实现的版本,并撰写了一篇博文来解释其工作原理。

通常来讲,身份验证系统都实现了基于时间的一次性密码算法,即著名的 TOTP(Time-Based One-Time Password)。该算法由三部分组成:

  • 一个共享密钥(一系列二进制数据)
  • 一个基于当前时间的输入
  • 一个签名函数

1、 共享密钥

用户在创建手机端身份验证系统时需要获取共享密钥。获取的方式包括用识别程序扫描给定二维码或者直接手动输入。密钥是三十二位加密,至于为什么不是六十四位,可以参考维基百科给出的解释

对于那些手动输入的用户,谷歌身份验证系统给出的共享密钥有如下的格式:

复制代码
xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx

256 位数据,当然别的验证系统可能会更短。

而对于扫描的用户,QR 识别以后是类似下面的 URL 链接:

otpauth://totp/Google%3Ayourname@gmail.com?secret=xxxx&issuer=Google

2、 基于当前时间的输入

这个输入是基于用户手机时间产生的,一旦用户完成第一步的密钥共享,就和身份验证服务器没有关系了。但是这里比较重要的是用户手机时间要准确,因为从算法原理来讲,身份验证服务器会基于同样的时间来重复进行用户手机的运算。进一步来说,服务器会计算当前时间前后几分钟内的令牌,跟用户提交的令牌比较。所以如果时间上相差太多,身份验证过程就会失败。

3、 签名函数

谷歌的签名函数使用了 HMAC-SHA1。HMAC 即基于哈希的消息验证码,提供了一种算法,可以用比较安全的单向哈希函数(如 SHA1)来产生签名。这就是验证算法的原理所在:只有共享密钥拥有者和服务器才能够根据同样的输入(基于时间的)得到同样的输出签名。伪代码如下:

复制代码
hmac = SHA1(secret + SHA1(secret + input))

本文开头提到的 TOTP 和 HMAC 原理类似,只是 TOTP 强调输入一定是当前时间相关。类似的还有 HOTP,采用增量式计数器的方式,需要不断和服务器同步。

算法流程简介

首先需要用 base32 解码密钥,为了更方便用户输入,谷歌采用了空格和小写的方式表示密钥。但是 base32 不能有空格而且必须大写,处理伪代码如下:

复制代码
original_secret = xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
secret = BASE32_DECODE(TO_UPPERCASE(REMOVE_SPACES(original_secret)))

接下来要从当前时间获得输入,通常采用 Unix 时间,即当前周期开始到现在的秒数

复制代码
input = CURRENT_UNIX_TIME()

这里有一点需要说明,验证码有一个时效,大概是 30 秒。这种设计是出于方便用户输入的考虑,每秒钟变化的验证码很难让用户迅速准确输入。为了实现这种时效性,可以通过整除 30 的方式来实现,即:

复制代码
input = CURRENT_UNIX_TIME() / 30

最后一步是签名函数,HMAC-SHA1,全部伪代码如下:

复制代码
original_secret = xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx
secret = BASE32_DECODE(TO_UPPERCASE(REMOVE_SPACES(original_secret)))
input = CURRENT_UNIX_TIME() / 30
hmac = SHA1(secret + SHA1(secret + input))

完成这些代码,基本就已经实现了两次验证的功能。由于 HMAC 是个标准长度的 SHA1 数值,有四十个字符的长度,用户很难一次性正确输入,因此还需要做一些格式上的处理。可参考下面的伪代码:

复制代码
four_bytes = hmac[LAST_BYTE(hmac):LAST_BYTE(hmac) + 4]
large_integer = INT(four_bytes)
small_integer = large_integer % 1,000,000

感谢郭蕾对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2014-09-18 03:5115921
用户头像

发布了 268 篇内容, 共 110.5 次阅读, 收获喜欢 23 次。

关注

评论

发布
暂无评论
发现更多内容

架构师训练营第五周学习笔记

李日盛

笔记

架构作业--第九周

Nick~毓

一致性HASH算法和相关测试

DL

第五周作业

小兵

week09作业

追风

架构师一期

native关键字作用到底是什么?

秦怀杂货店

Java 源码 源码刨析 native

Week5 - 技术选型 - 缓存,队列,负载均衡

evildracula

学习 架构

架构师训练营第 1 期 - 第 9 周课后练习

Anyou Liu

极客大学架构师训练营

架构师3期3班-week1-总结

zbest

总结 week1

架構師訓練營 week9 總結

ilake

架构师训练营第五周作业

李日盛

架構師訓練營第 1 期 - 第 09 周總結

Panda

架構師訓練營第 1 期

架構師訓練營第 1 期 - 第 09 周作業

Panda

架構師訓練營第 1 期

第九周课后练习

knight

微服务手册:分库分表从分析到实践,不再停留只会说分库分表

互联网应用架构

分库分表

架构一期第九周作业

Airs

架构师训练营第 1 期 - 第 9 周学习总结

Anyou Liu

极客大学架构师训练营

架构师训练营第五周作业1

韩儿

架构师训练营第五周作业2

韩儿

架构师训练营 1 期 -- 第九周总结

曾彪彪

极客大学架构师训练营

架构师训练营第 1 期第九周作业

Leo乐

极客大学架构师训练营

深入理解r2dbc-mysql

程序那些事

响应式编程 R2DBC 程序那些事 响应式数据库 r2dbc-myql

第五周作业一

lithium

Java核心基础——动态代理、静态代理

老农小江

java基础 代理模式

架构师3期3班-week1-作业

zbest

作业 week1

架构师训练营第九周总结

_

极客大学架构师训练营 第九周总结

9.5系统性能优化案例:秒杀系统

张荣召

架构师入门学习感悟五

笑春风

架構師訓練營 week9 作業

ilake

Python进阶——如何实现一个装饰器?

Kaito

Python

第五周-作业一

ray-arch

谷歌两步验证系统的工作原理_Google_张天雷_InfoQ精选文章