我正在尝试使用可以使用Google Authenticator application生成的一次性密码。
基本上,Google身份验证器实现了两种类型的密码:
Google身份验证器也可在此处以开源形式提供:code.google.com/p/google-authenticator
我一直在寻找能够生成HOTP和TOTP密码的现有解决方案,但没有找到太多。我的代码是以下代码片段,负责生成HOTP:
import hmac, base64, struct, hashlib, time
def get_token(secret, digest_mode=hashlib.sha1, intervals_no=None):
if intervals_no == None:
intervals_no = int(time.time()) // 30
key = base64.b32decode(secret)
msg = struct.pack(">Q", intervals_no)
h = hmac.new(key, msg, digest_mode).digest()
o = ord(h[19]) & 15
h = (struct.unpack(">I", h[o:o+4])[0] & 0x7fffffff) % 1000000
return h
我遇到的问题是,我使用上述代码生成的密码与使用Google身份验证器Android应用生成的密码不同。即使我尝试了多个intervals_no
值(恰好是前10000个,以intervals_no = 0
开头),但secret
等于GA应用中提供的密钥。
我的问题是:
总结一下:请给我任何线索,以帮助我在我的Python代码中实现Google身份验证器身份验证。
答案 0 :(得分:142)
我想在我的问题上设置一个赏金,但我已经成功地创建了解决方案。我的问题似乎与secret
密钥的值不正确(它必须是base64.b32decode()
函数的正确参数)。
下面我发布完整的工作解决方案,并说明如何使用它。
以下代码就足够了。我还将它作为单独的模块 onetimepass 上传到GitHub(可在此处获取:https://github.com/tadeck/onetimepass)。
import hmac, base64, struct, hashlib, time
def get_hotp_token(secret, intervals_no):
key = base64.b32decode(secret, True)
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)
它有两个功能:
get_hotp_token()
生成一次性令牌(单次使用后应无效),get_totp_token()
根据时间生成令牌(以30秒为间隔更改),参数:
secret
是服务器(上述脚本)和客户端(Google身份验证器,在应用程序中提供密码)所知的秘密值,intervals_no
是在每一代令牌之后递增的数字(这应该可能在服务器上通过检查过去检查过的最后一次成功后的有限数量的整数来解决)secret
(它必须是base64.b32decode()
的正确参数) - 最好是16个字符(无=
个标志),因为它肯定适用于脚本和Google身份验证器。get_hotp_token()
。在Google身份验证器中,我提到这种类型的密码基于计数器。要在服务器上检查它,您需要检查intervals_no
的几个值(因为您没有保证用户由于某种原因没有在请求之间生成传递),但不低于上一次工作{{1值(因此你应该将它存储在某处)。intervals_no
。您必须确保两个系统都设置了正确的时间(这意味着它们在任何给定的时刻都会生成相同的Unix时间戳)。将以下代码用于基于HMAC的一次性密码时:
get_totp_token()
您将获得以下结果:
secret = 'MZXW633PN5XW6MZX'
for i in xrange(1, 10):
print i, get_hotp_token(secret, intervals_no=i)
与Google身份验证器应用生成的令牌相对应(除非短于6个标志,应用程序会在开头添加零以达到6个字符的长度)。
答案 1 :(得分:6)
我想要一个python脚本来生成TOTP密码。所以,我写了python脚本。这是我的实施。我在维基百科上有这个info以及有关HOTP和TOTP的一些知识来编写这个脚本。
import hmac, base64, struct, hashlib, time, array
def Truncate(hmac_sha1):
"""
Truncate represents the function that converts an HMAC-SHA-1
value into an HOTP value as defined in Section 5.3.
http://tools.ietf.org/html/rfc4226#section-5.3
"""
offset = int(hmac_sha1[-1], 16)
binary = int(hmac_sha1[(offset * 2):((offset * 2) + 8)], 16) & 0x7fffffff
return str(binary)
def _long_to_byte_array(long_num):
"""
helper function to convert a long number into a byte array
"""
byte_array = array.array('B')
for i in reversed(range(0, 8)):
byte_array.insert(0, long_num & 0xff)
long_num >>= 8
return byte_array
def HOTP(K, C, digits=6):
"""
HOTP accepts key K and counter C
optional digits parameter can control the response length
returns the OATH integer code with {digits} length
"""
C_bytes = _long_to_byte_array(C)
hmac_sha1 = hmac.new(key=K, msg=C_bytes, digestmod=hashlib.sha1).hexdigest()
return Truncate(hmac_sha1)[-digits:]
def TOTP(K, digits=6, window=30):
"""
TOTP is a time-based variant of HOTP.
It accepts only key K, since the counter is derived from the current time
optional digits parameter can control the response length
optional window parameter controls the time window in seconds
returns the OATH integer code with {digits} length
"""
C = long(time.time() / window)
return HOTP(K, C, digits=digits)