Python中Google认证器的实现

2024-05-15 15:35:11 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图使用可以使用Google Authenticator application生成的一次性密码。

Google Authenticator的作用

基本上,Google Authenticator实现两种类型的密码:

  • HOTP-基于HMAC的一次性密码,这意味着根据RFC4226每次调用都会更改密码,并且
  • TOTP基于时间的一次性密码,每30秒更改一次(据我所知)。

Google Authenticator也可以在这里作为开放源代码使用: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

我面临的问题是,我使用上述代码生成的密码与使用googleauthenticatorappforandroid生成的密码不同。尽管我尝试了多个intervals_no值(正是前10000个,以intervals_no = 0开头),但是secret等于GA应用程序中提供的密钥。

我有问题

我的问题是:

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

总结一下:请给我一些线索,帮助我在Python代码中实现Google Authenticator身份验证。


Tags: no代码authenticator密码secrettimegooglehmac
2条回答

我想给我的问题定个赏金,但我已经成功地找到了解决办法。我的问题似乎与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是每次生成令牌后递增的数目(这可能应该在服务器上通过在过去检查的最后一个成功的整数之后检查一些有限的整数来解决)

如何使用

  1. 生成secret(它必须是base64.b32decode()的正确参数)-最好是16个字符(没有=符号),因为它对脚本和Google Authenticator都有效。
  2. 如果希望一次性密码在每次使用后失效,请使用get_hotp_token()。在Google Authenticator中,我提到的这种类型的密码是基于计数器的。要在服务器上检查它,您需要检查几个值intervals_no(因为您没有保证用户出于某种原因没有在请求之间生成传递),但不少于最后一个工作的intervals_no值(因此您可能应该将它存储在某个地方)。
  3. 如果您希望令牌以30秒的间隔工作,请使用get_totp_token()。您必须确保两个系统都有正确的时间设置(这意味着它们在任何给定的时刻都会生成相同的Unix时间戳)。
  4. 一定要保护自己免受暴力攻击。如果使用基于时间的密码,则在30秒内尝试1000000个值将提供100%的猜测密码的机会。对于基于HMAC的passowrds(HOTPs),情况似乎更糟。

示例

对基于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 Authenticator应用程序生成的令牌相对应(除非短于6个符号,否则应用程序会在开始处添加0以达到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)

相关问题 更多 >