该博文用于记录一下Google二次验证的生成及验证过程,以便以后需要用的时候不用到处查资料×

密钥生成

在文档中查询到二次验证密钥属于Base32编码,所以在服务端生成密钥时使用的是随机字符串通过Base32后得到的字符串截取前16个字符作为密钥,实际上只要是Base32编码字符串,不管长度都可以。为了保证安全性可以取更长的编码字符串。

实际上这一过程也可以直接放在客户端生成,但是写的时候还是直接放在的服务端,主要也是为了通过进行字符串的混淆添加一些密钥信息到随机时间戳中保证一定的安全性

1
2
3
4
5
6
7
8
def generate_secret():
timestamp = int(time.time())
half = len(secretString) // 2
import_str = secretString[:half] + str(timestamp) + secretString[half:] #添加其他信息到随机时间戳中
digest = hashlib.sha256(import_str.encode('utf-8')).hexdigest()
base32code = base64.b32encode(bytes.fromhex(digest))
sec_code = bytes.decode(base32code[:16])
return sec_code

通过密钥生成对应二维码:通过字符串otpauth://TYPE/LABEL?PARAMETERS进行格式化后得到文本信息,基于该文本信息直接生成二维码

文档给出的样例:

otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example

除了这些基本的信息,还可以指定周期period以及验证码位数digits

otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30

验证码生成

验证码通过HMAC算法生成,其计算公式如下
$$
HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))
$$
这里C是一个用于同步的计数器,在实际使用过程中通过秒级时间戳整除30得到一个每30s更新一次的计数器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def get_hotp_token(secret, intervals_no):
key = base64.b32decode(normalize(secret), True) # True is to fold lower into uppercase
msg = struct.pack(">Q", intervals_no)
h = bytearray(hmac.new(key, msg, hashlib.sha1).digest())
o = h[19] & 15
h = str((struct.unpack(">I", h[o:o + 4])[0] & 0x7fffffff) % 1000000)
return prefix0(h)

def generate_pin(secret_code):
return get_hotp_token(secret_code, intervals_no=int(time.time()) // 30)

def normalize(key):
k2 = key.strip().replace(' ', '')
if len(k2) % 8 != 0:
k2 += '=' * (8 - len(k2) % 8)
return k2


def prefix0(h):
if len(h) < 6:
h = '0' * (6 - len(h)) + h
return h

实际使用时,由于存在传输延迟等问题,所以会选取一个生成区间T,生成$[-T, +T]$区间内的验证码,如果用户传输的验证码在里面则为验证成功