背景
增加双因子认证,提高网站登录的安全性。利用谷歌身份验证器绑定密钥,从而进行动态MFA验证。
原理
核心内容
Google Authenticator采用的算法是TOTP(Time-Based One-Time Password基于时间的一次性密码),其核心内容包括以下三点:
- 一个共享密钥
- 当前时间输入
- 一个签名函数
加密原理和步骤
Step1:base32 secret
- Key:共享密钥,在Google Authenticator中是通过将一段字符串进行base32解码成bytes得到的。但由于此密钥在不够32位或超过32位时会用’=’表示,故用的pyotp随机生成的密钥。
1
2Secret = pyotp.random_base32()
Key = base64.b32decode(Secret, True)
- Key:共享密钥,在Google Authenticator中是通过将一段字符串进行base32解码成bytes得到的。但由于此密钥在不够32位或超过32位时会用’=’表示,故用的pyotp随机生成的密钥。
Step2:get current timestamp
- Count:计数器,通过当前时间戳除以30然后将得到的整数转换成一个大端序的字节。
1
2
3# int(time.time()) // 30 到当前经历了多少个30秒
# 将间隔时间转为big-endian(大端序)并且为长整型的字节
Count = struct.pack(">Q", int(time.time()) // 30)
- Count:计数器,通过当前时间戳除以30然后将得到的整数转换成一个大端序的字节。
Step3:start hmac-sha1
- Hmac:将K和C做HMAC-SHA-1加密然后以字节方式保存,因为后期需要进行与运算,而str是不能和int进行与运算的。
1
2
3
4
5# hmac = SHA1(secret + SHA1(secret + input))
# 为了方便演示,将字节转换成了字符串显示
Hmac = hmac.new(K, C, hashlib.sha1).digest()
# 取出最后一位和数字15做与运算
O = H[19] & 15
- Hmac:将K和C做HMAC-SHA-1加密然后以字节方式保存,因为后期需要进行与运算,而str是不能和int进行与运算的。
Step4:get DynamicPasswd
- 通过计算出来的O在H中取出4个16进制的字节,然后将字节转换成正整数,因转换后的正整数是放在数组里面的,所以需要使用[0]取出。最后与一个全为1的二进制与运算然后与10^6做取余运算,最终会得到一个6位数的TOTP
1
2
3
4
5DynamicPasswd = str((struct.unpack(">I", H[O:O + 4])[0] & 0x7fffffff) % 1000000)
# struct.unpack('>I',h[o:o+4])[0] :转为big-endian(大端序)并且不为负数的数字(整数),因为转换完是一个数组,类似"(2828101188,)",所以需要[0]取出
# h[o:o+4] :取其中4个字节 o=10 则取索引分别为 10,11,12,13的字节
# & 0x7fffffff = 11111111 :与字节转换的数字做与运算
# % 1000000 :得出的数字与1000000相除然后取余
- 通过计算出来的O在H中取出4个16进制的字节,然后将字节转换成正整数,因转换后的正整数是放在数组里面的,所以需要使用[0]取出。最后与一个全为1的二进制与运算然后与10^6做取余运算,最终会得到一个6位数的TOTP
Step5:get MFA
- 最后计算出的6位数字最左边的一位可能为0,所以需要判断如果DynamicPasswd得到的是一个5位数的数字,那就在最左边加上一个0。
1
TOTP = str(0) + str(DynamicPasswd) if len(DynamicPasswd) < 6 else DynamicPasswd
- 最后计算出的6位数字最左边的一位可能为0,所以需要判断如果DynamicPasswd得到的是一个5位数的数字,那就在最左边加上一个0。
实现步骤
- 1.使用python的pyotp模块生成谷歌认证需要的密钥
- 2.根据密钥生成二维码图片以及计算出6位动态验证码
- 3.使用谷歌的身份验证器app,扫描二维码或者手动输入密钥
- 4.平台二次认证通过对输入的动态验证码进行校验
相关代码
1 | import base64, time, struct, hmac, hashlib |
封装为接口
- resful.py
1 | # 新建一个包,包下创建resful.py文件 |
- views.py
1 | from utils import restful # 自定义的restful |
- 主urls.py
1 | from django.urls import path,include |
- apps的urls.py
1 | from django.urls import path |
测试
验证码生成测试
接口测试