Реализация Google Authenticator в Python - PullRequest
97 голосов
/ 16 декабря 2011

Я пытаюсь использовать одноразовые пароли, которые можно сгенерировать с помощью приложения Google Authenticator .

Что делает Google Authenticator

По сути, Google Authenticator реализует два типапаролей:

  • HOTP - Одноразовый пароль на основе HMAC, что означает, что пароль изменяется при каждом вызове в соответствии с RFC4226 , и
  • TOTP - Одноразовый пароль, основанный на времени, который меняется каждые 30 секунд (насколько мне известно).

Google Authenticator isтакже доступно в виде открытого исходного кода здесь: 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. Как я могу генерировать HOTP и / или TOTP в Python?
  3. Существуют ли какие-либо библиотеки Python дляthis?

Подводя итог: пожалуйста, дайте мне любые подсказки, которые помогут мне реализовать аутентификацию Google Authenticator в моем коде Python.

Ответы [ 2 ]

146 голосов
/ 18 декабря 2011

Я хотел назначить награду за мой вопрос, но мне удалось создать решение.Кажется, моя проблема связана с неправильным значением клавиши 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. Используйте get_totp_token(), если хотите, чтобы токен работал с 30-секундными интервалами.Вы должны убедиться, что в обеих системах установлено правильное время (это означает, что они генерируют одну и ту же метку времени Unix в любой данный момент времени).
  4. Обязательно защитите себя от атаки методом перебора.Если используется основанный на времени пароль, то попытка 1000000 значений менее чем за 30 секунд дает 100% шанс угадать пароль.В случае паролей на основе HMAC (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 Authenticator (кроме случаев, когда короче 6 знаков, приложение добавляет нули к началу, чтобы достичьдлиной 6 символов).

6 голосов
/ 22 апреля 2014

Я хотел, чтобы скрипт 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)
...