用 Python 实现 Google Authenticator

我试图使用一次性密码,可以生成使用 Google Authenticator 应用程序

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

我面临的问题是,我使用上述代码生成的密码与使用 Google Authenticator 应用为 Android 生成的密码不一样。尽管我尝试了多个 intervals_no值(确切地说是第一个10000,从 intervals_no = 0开始) ,其中 secret等于 GA 应用程序中提供的键。

我有些疑问

我的问题是:

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

总之: 请给我任何线索,将帮助我实现在我的 Python 代码谷歌身份验证认证。

66291 次浏览

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

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

密码

The following code is enough. I have also uploaded it to GitHub as separate module called 一次过 (available here: 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)

It has two functions:

  • get_hotp_token() generates one-time token (that should invalidate after single use),
  • get_totp_token()根据时间生成令牌(每30秒更改一次) ,

参数

说到参数:

  • secret是服务器(上面的脚本)和客户端(Google Authenticator,通过在应用程序中提供它作为密码)都知道的一个秘密值,
  • intervals_no是在每一代令牌之后增加的数字(这可能在服务器上通过检查上一次检查成功的整数之后检查一些有限数目的整数来解决)

How to use it

  1. 生成 secret(它必须是 base64.b32decode()的正确参数)-最好是16个字符(没有 =符号) ,因为它肯定对脚本和 Google Authenticator 都有效。
  2. 如果希望每次使用后一次性密码失效,请使用 get_hotp_token()。在谷歌身份验证器这种类型的密码,我提到的基于计数器。为了在服务器上检查它,你需要检查几个 intervals_no的值(因为你不能保证用户不会因为某种原因在请求之间生成传递) ,但是不能少于最后一个工作的 intervals_no值(因此你可能应该把它存储在某个地方)。
  3. 如果希望令牌以30秒为间隔工作,则使用 get_totp_token()。您必须确保两个系统都有正确的时间设置(这意味着它们在任何给定时刻都生成相同的 Unix 时间戳)。
  4. 一定要保护自己不受穷举法的伤害。如果使用基于时间的密码,那么在30秒内尝试1000000个值将提供100% 的机会猜测密码。对于基于 HMAC 的密码(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 密码。所以,我写了蟒蛇脚本。这是我的实施方案。我在维基百科上有这个 信息和一些关于 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)

By following the correct answer from @tadeck and @Anish-Shah, there is a simpler method to get the code without using struct and avoiding extra imports:

""" TOTP """
import hmac
import time




def totp(key: bytes):
""" Calculate TOTP using time and key """
now = int(time.time() // 30)
msg = now.to_bytes(8, "big")
digest = hmac.new(key, msg, "sha1").digest()
offset = digest[19] & 0xF
code = digest[offset : offset + 4]
code = int.from_bytes(code, "big") & 0x7FFFFFFF
code = code % 1000000
return "{:06d}".format(code)

This works with Python 3.

You can get the current TOTP code by calling totp(key) where the "key" is a bytes (commonly the base 32 decoded key).