首页 文章

Python中的Google身份验证器实现

提问于
浏览
92

我正在尝试使用可以使用Google Authenticator application生成的一次性密码 .

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应用程序中提供的密钥 .

我有问题

我的问题是:

  • 我做错了什么?

  • 如何在Python中生成HOTP和/或TOTP?

  • 是否有任何现有的Python库?

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

2 回答

  • 6

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

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

    代码

    以下代码就足够了 . 我还将它作为单独的模块上传到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_hotp_token() 生成一次性令牌(单次使用后应该无效),

    • get_totp_token() 根据时间生成令牌(以30秒为间隔更改),

    参数

    说到参数:

    • secret 是服务器(上述脚本)和客户端(Google Authenticator,通过在应用程序中提供密码)已知的秘密值,

    • intervals_no 是在每一代令牌之后递增的数字(这应该可能在服务器上通过检查过去检查的最后一次成功之后的一些有限数量的整数来解决)

    如何使用它

    • 生成 secret (它必须是 base64.b32decode() 的正确参数) - 最好是16-char(没有 = 标志),因为它确实适用于脚本和Google身份验证器 .

    • 如果您希望每次使用后无效的一次性密码,请使用 get_hotp_token() . 在Google身份验证器中,我提到这种类型的密码基于计数器 . 要在服务器上检查它,您需要检查 intervals_no 的几个值(因为您没有保证用户由于某种原因没有在请求之间生成传递),但是不小于最后一个工作 intervals_no 值(因此您可能应该把它存放在某个地方) .

    • 如果您希望令牌以30秒的间隔工作,请使用 get_totp_token() . 您必须确保两个系统都具有正确的时间设置(这意味着它们在任何给定的时刻都生成相同的Unix时间戳) .

    • 确保自己免受暴力攻击 . 如果使用基于时间的密码,则在不到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个字符的长度) .

  • 130

    我想要一个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)
    

相关问题