安全性 - Python中的Google身份验证器实现

我正在尝试使用可以使用Google身份验证器应用程序生成的一次性密码。

Google身份验证器的功能

基本上,Google身份验证器实现了两种类型的密码:

  • HOTP - 基于HMAC的一次性密码,这意味着密码随每次呼叫而改变,符合RFC4226,并且
  • TOTP - 基于时间的一次性密码,每30秒更改一次(据我所知)。

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

我面临的问题是,我使用上述代码生成的密码与使用适用于Android的Google身份验证器应用生成的密码不同。 即使我尝试了多个intervals_no值(正好是第一个10000,从intervals_no = 0开始),secret等于GA应用程序中提供的密钥。

我有问题

我的问题是:

  1. 我究竟做错了什么?
  2. 如何在Python中生成HOTP和/或TOTP?
  3. 是否有任何现有的Python库?

总结一下:请给我任何有助于我在Python代码中实现Google身份验证器身份验证的线索。

2个解决方案
139 votes

我想在我的问题上设置一个赏金,但我已经成功地创建了解决方案。 我的问题似乎与get_totp_token()键的错误值有关(它必须是intervals_no函数的正确参数)。

下面我发布完整的工作解决方案,并解释如何使用它。

以下代码就足够了。 我还将它作为单独的模块上传到GitHub,名为onetimepass(可在此处获取:[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_totp_token()生成一次性令牌(单次使用后应该无效),
  • get_totp_token()根据时间生成令牌(以30秒为间隔更改),

参数

说到参数:

  • get_totp_token()是服务器(上述脚本)和客户端(Google Authenticator,通过在应用程序中提供密码)已知的秘密值,
  • get_totp_token()是在每一代令牌之后递增的数字(这应该可能在服务器上通过检查过去检查的最后一次成功之后的一些有限数量的整数来解决)

如何使用它

  1. 生成get_totp_token()(它必须是intervals_no的正确参数) - 最好是16-char(没有intervals_no标志),因为它确实适用于脚本和Google身份验证器。
  2. 如果您希望每次使用后一次性密码无效,请使用get_totp_token()。 在Google身份验证器中,我提到这种类型的密码基于计数器。 要在服务器上检查它,您需要检查几个值intervals_no(因为您没有保证用户由于某种原因没有在请求之间生成传递),但不低于最后一个工作intervals_no值(因此您可能应该 把它存放在某处)。
  3. 如果您希望令牌以30秒的间隔工作,请使用get_totp_token()。 您必须确保两个系统都有正确的时间设置(这意味着它们在任何给定的时刻都生成相同的Unix时间戳)。
  4. 确保自己免受暴力攻击。 如果使用基于时间的密码,则在不到30秒的时间内尝试1000000值会有100%的机会猜测密码。 在基于HMAC的Passowrds(HOTP)的情况下,它似乎更糟。

使用以下代码进行一次性基于HMAC的密码时:

secret = 'MZXW633PN5XW6MZX'
for i in xrange(1, 10):
    print i, get_hotp_token(secret, intervals_no=i)

你会得到以下结果:

1 448400
2 656122
3 457125
4 35022
5 401553
6 581333
7 16329
8 529359
9 171710

这与Google身份验证器应用生成的令牌相对应(除非短于6个标志,应用程序会在开头添加零以达到6个字符的长度)。

Tadeck answered 2019-08-13T19:39:16Z
6 votes

我想要一个python脚本来生成TOTP密码。 所以,我写了python脚本。 这是我的实施。 我在维基百科上有这个信息,并且有关于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)
Anish Shah answered 2019-08-13T19:39:41Z
translate from https://stackoverflow.com:/questions/8529265/google-authenticator-implementation-in-python