在前面的文章中我们介绍的工具实现了如下的功能:
- 防止信息被篡改(摘要算法)
- 防止未经授权第三方获取信息(加密算法)
- 确保信息来自特定的来源(数字签名)
现在还留下来一个问题,如何确认消息的发送/接收方就是我们要发送去的那个人呢?这个就是身份识别问题
密码学认为有三类要素可以确认"你就是你":
-
你知道的要素,比如密码、暗号等等.这件事只有你知道别人不知道
-
你拥有的要素,一把钥匙、一块手表、一件信物等等.这件东西只有你有别人没有.
-
你的生理要素如指纹、面部特征、DNA等等.这些生理特征每个人都不一样
上面的这三类要素,任何单独的一个都可以确认"你就是你",但不够安全.如果能够两个要素联合确认,那么安全系数就大大提高了.
密码属于第一类要素,比如很多银行会向用户提供U盾,要求使用时插入,这属于第二类要素,这个U盾只有你有别人没有.再比如,新开业的网上银行很可能要求用户使用摄像头"刷脸",与身份证比对,这属于第三类要素,你的脸来证明"你就是你".
这篇文章我们讲的是第二类要素的一种实现方式--动态口令,其实不少U盾也是使用的动态口令,但恐怕应用更广的是google authenticator
常见的动态口令生成器都使用的是TOTP算法,TOTP的全称是"基于时间的一次性密码"(Time-based One-time Password)它是公认的可靠解决方案,已经写入国际标准RFC6238
它的步骤如下:
-
用户开启双因素认证后服务器生成一个密钥.
-
服务器提示用户扫描二维码(或者使用其他方式)把密钥保存到用户的手机或者在出厂是秘钥拷贝到U盾.也就是说服务器和U盾/用户的手机现在都有了同一把密钥.注意,密钥必须跟U盾/手机绑定.一旦用户更换就必须生成全新的密钥.也就是说U盾/手机就成了你有别人没有的信物.
-
用户登录时U盾/手机客户端使用这个密钥和当前时间戳,生成一个哈希,其有效期默认为30秒.用户在有效期内把这个哈希提交给服务器.
-
服务器也使用密钥和当前时间戳生成一个哈希,跟用户提交的哈希比对.只要两者不一致.就拒绝登录.就可以说明自己是自己了
其hash算法如下:
其中TC
表示一个时间计数器,其计算方式如下:
TS
为间隔也就是默认的30s,unixtime(now)
为当前的unix时间戳,unixtime(T0)
通常是unix时间戳的起始时间也就是1970年1月1日.
python中要使用TOTP算法可以使用开源库pyotp
import pyotp
import time
secret = pyotp.random_base32() #获取随机密钥,存于用户表中
secret
'3AC3UWMVU4BSCKBA'
totp = pyotp.TOTP(secret)
totp.now()
'062759'
totp.verify('492039') # => True
time.sleep(30)
totp.verify('492039') # => False
False
google authenticator
接收一种形式为'otpauth://totp/{issuer_name}:{username}?secret={secret}&issuer={issuer_name}'
的url作为参数,它会通过二维码获取到这种url然后解析了保存在客户端上.
import pyotp
import time
secret = pyotp.random_base32() #获取随机密钥,存于用户表中
secret
'LF5Q4HGEX5FCGTQO'
otpurl = pyotp.TOTP(secret).provisioning_uri('hsz1273327', issuer_name="Verfiy_Code")
otpurl
'otpauth://totp/Verfiy_Code:hsz1273327?secret=LF5Q4HGEX5FCGTQO&issuer=Verfiy_Code'
我们可以借助qrcode包创建一个二维码
from qrcode import QRCode,constants
from pathlib import Path
def get_qrcode(secret_key,username):
data = pyotp.TOTP(secret_key).provisioning_uri(username, issuer_name="Verfiy_Code")
qr = QRCode(
version=1,
error_correction=constants.ERROR_CORRECT_L,
box_size=6,
border=4
)
try:
qr.add_data(data)
qr.make(fit=True)
img = qr.make_image()
img.save(f"./{secret_key}.png") #保存条形码图片
return True
except Exception as e:
return False
get_qrcode(secret_key=secret,username="hsz1273327")
True
之后打开google authenticator扫码添加即可,我们就会多一个叫Verfiy_Code的项目,我们将其中的动态令牌拿来验证
import pyotp
def Google_Verify_Result(secret_key,verifycode):
t = pyotp.TOTP(secret_key)
result = t.verify(verifycode) #对输入验证码进行校验,正确返回True
return result
Google_Verify_Result(secret,verifycode="525787")
True