详解Google Authenticator工作原理

安全攻防 gongyj 3337℃ 已收录 0评论

实际上Google Authenticator采用的算法是TOTP(Time-Based One-Time Password基于时间的一次性密码)。TOTP(基于时间的一次性密码算法)是支持时间作为动态因素基于HMAC一次性密码算法的扩展。

google-authentication2.jpg

其核心内容包括以下三点:

    一个共享密钥(一个比特序列);

    当前时间输入;

    一个签署函数。

共享密密钥:

共享密钥用于在手机端上建立账户。密码内容可以是通过手机拍照二维码或者手工输入,并会被进行base32加密。

实际上Google Authenticator采用的算法是TOTP(Time-Based One-Time Password基于时间的一次性密码)。TOTP(基于时间的一次性密码算法)是支持时间作为动态因素基于HMAC一次性密码算法的扩展。其核心内容包括以下三点:

    一个共享密钥(一个比特序列);

    当前时间输入;

    一个签署函数。

手工密码的输入格式如下:

xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 

包含该令牌的二维码的内容是一个URL:

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

时间输入(当前时间):

输入的时间值来自于手机本身,一旦我们获得密钥后,就无需与服务器再进行通信了。但是最重要一点是务必确保手机上的时间是正确的,因为往后的步骤服务器会多次重复使用之前得到的时间值,服务器只会认准这个值。进一步说,服务器会比对所有提交的令牌以确认哪一个是你输入并提交的。

签署:

签署所使用的方法是HMAC-SHA1。HMAC的全称是Hash-based message authentication code(哈希运算消息认证码),以一个密钥和一个消息为输入,生成一个消息摘要作为输出,这里以SHA1作为消息输入。使用HMAC的原因是:只有用户本身知道正确的输入密钥,因此会得到唯一的输出。其算法可以简单表示为:

hmac = SHA1(secret + SHA1(secret + input))   

事实上,TOTP是HMAC-OTP(基于HMAC的一次密码生成)的超集,区别是TOTP以当前时间作为输入,而HMAC-OTP以自增计算器作为输入,该计数器使用时需要进行同步。

算法:

首先,要进行密钥的base32加密。虽然谷歌上的密钥格式是带空格的,不过base32拒绝空格输入,并只允许大写。所以要作如下处理:

original_secret = xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx  

secret = BASE32_DECODE(TO_UPPERCASE(REMOVE_SPACES(original_secret)))  

第二步要获取当前时间值,这里使用的是UNIX time函数,或者可以用纪元秒。

input = CURRENT_UNIX_TIME()  

在Google Authenticator中,input值拥有一个有效期。因为如果直接根据时间进行计算,结果将时刻发生改变,那么将很难进行复用。Google Authenticator默认使用30秒作为有效期(时间片),最后input的取值为从Unix epoch(1970年1月1日 00:00:00)来经历的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))  

至此,2FA所需的两个因子都已准备就绪了。但是HMAC运算后的结果会是20字节即40位16进制数,应该没有人会愿意每次都输入这么长的密码。我们需要的是常规6位数字密码!

要实现这个愿望,首先要对20字节的SHA1进行瘦身。我们把SHA1的最后4个比特数(每个数的取值是0~15)用来做索引号,然后用另外的4个字节进行索引。因此,索引号的操作范围是15+4=19,加上是以零开始,所以能完整表示20字节的信息。4字节的获取方法是

four_bytes = hmac[LAST_BYTE(hmac):LAST_BYTE(hmac) + 4]  

然后将它转化为标准的32bit无符号整数(4 bytes = 32 bit):

large_integer = INT(four_bytes)

最后再进行7位数(1百万)取整,就可得到6位数字了:

large_integer = INT(four_bytes)  

small_integer = large_integer % 1,000,000 

这也是我们最后要的目标结果,整个过程总结如下:

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))  

four_bytes = hmac[LAST_BYTE(hmac):LAST_BYTE(hmac) + 4]  

large_integer = INT(four_bytes)  

small_integer = large_integer % 1,000,000

一、服务端实现

使用php类,直接下载 https://github.com/PHPGangsta/GoogleAuthenticator/raw/master/PHPGangsta/GoogleAuthenticator.php

在自己的业务逻辑中引用此php类

require_once '../PHPGangsta/GoogleAuthenticator.php';
$ga = new PHPGangsta_GoogleAuthenticator();
//创建一个新的"安全密匙SecretKey"
//把本次的"安全密匙SecretKey" 入库,和账户关系绑定,客户端也是绑定这同一个"安全密匙SecretKey"
$secret = $ga->createSecret();
echo "安全密匙SecretKey: ".$secret."\n\n";
$qrCodeUrl = $ga->getQRCodeGoogleUrl('www.iamle.com', $secret); //第一个参数是"标识",第二个参数为"安全密匙SecretKey" 生成二维码信息
echo "Google Charts URL for the QR-Code: ".$qrCodeUrl."\n\n"; //Google Charts接口 生成的二维码图片,方便手机端扫描绑定安全密匙SecretKey
$oneCode = $ga->getCode($secret); //服务端计算"一次性验证码"
echo "服务端计算的验证码是:".$oneCode."\n\n";
//把提交的验证码和服务端上生成的验证码做对比
// $secret 服务端的 "安全密匙SecretKey"
// $oneCode 手机上看到的 "一次性验证码"
// 最后一个参数 为容差时间,这里是2 那么就是 2* 30 sec 一分钟.
// 这里改成自己的业务逻辑
$checkResult = $ga->verifyCode($secret, $oneCode, 2);
if ($checkResult) {
    echo '匹配! OK';
} else {
    echo '不匹配! FAILED';
}

二、服务端例子

安全密匙SecretKey: VICQ3MPLHPWT72D7

Google Charts URL for the QR-Code: https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth%3A%2F%2Ftotp%2Fwww.iamle.com%3Fsecret%3DVICQ3MPLHPWT72D7

服务端计算的验证码是:583222

匹配! OK


客户端实现

python实现:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import hmac, base64, struct, hashlib, time ,sys , os
  
def get_hotp_token(secret, intervals_no):
  key = base64.b32decode(secret)
  msg = struct.pack(">Q", intervals_no)
  h = hmac.new(key, msg, hashlib.sha1).digest()
  o = ord(h[19]) & 15
  h = (struct.unpack(">I", h[o:o+4])[0] & 0x7fffffff) % 1000000
  return h
  
def get_totp_token(secret):
  return get_hotp_token(secret, intervals_no=int(time.time())//30)
  
  #Sec = str(sys.argv[1])
Sec = 'xxxxxxx'    #这里是密钥共16位
validation_code = str(get_totp_token(Sec))
print  validation_code

ruby实现: 

#!/usr/bin/ruby
  
require 'rubygems'
require 'base32'
require 'openssl'
  
int = 30
  
now = Time.now.to_i / int
key = Base32.decode 'xxxxxxxx'   #这里是密钥共16位
sha = OpenSSL::Digest::Digest.new('sha1')
  
(-1..1).each do |x|
  bytes  = [ now + x ].pack('>q').reverse
  hmac   = OpenSSL::HMAC.digest(sha, key.to_s, bytes)
  offset = hmac[-1] & 0x0F
  hash   = hmac[offset...offset + 4]
  
  code   = hash.reverse.unpack('L')[0]
  code  &= 0x7FFFFFFF
  code  %= 1000000
  
  puts code
end

需要安装require要求的3个模块才能运行,base32在安装好ruby后,用一下命令安装:

gem install base32

然后就可以执行了。

三、手机端安装

1、Android移动设备

在您手机的应用市场搜索“Google身份验证器”或“Google Authenticator”或“谷歌动态口令”,下载安装即可。拥有Google身份验证器的市场有:Google Play应用汇安卓市场百度手机助手优亿市场安智市场 等。

安卓手机还需要下载“条码扫描器”,Google身份验证器才能够识别二维码,用的的二维码工具不行。版本越新越好,旧版本焦距有问题,要站到很远的地方才能扫描,可以从这里下载

anroid01.png

anroid02.png

手机端设置,可以扫描二维码,也可以手工输入密钥,如果是手工输出密钥,要选择基于时间。配置好密钥后,手机上就会显示一组6位的动态数组,每隔30秒变化一次,这样就可以使用了。

2、iOS移动设备

进入应用市场,搜索“Google Authenticator”,下载安装即可。

3、其他平台:

Windows Phone:点击这里

WebOS:点击这里

Symbian或者其他支持Java ME的设备:点击这里


本站文章如未注明,均为原创丨本网站采用BY-NC-SA协议进行授权,转载请注明转自:http://blog.chopmoon.com/favorites/204.html
喜欢 (0)
发表我的评论
取消评论

表情 代码 贴图 加粗 链接 私信 删除线 签到

Hi,请填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
正在加载中……