Реалізація Google Authenticator в Python


104

Я намагаюся використовувати одноразові паролі, які можна створити за допомогою програми Google Authenticator .

Що робить Google Authenticator

В основному, Google Authenticator реалізує два типи паролів:

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

Google Authenticator також доступний як Open Source тут: 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?

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

Відповіді:


152

Я хотів встановити щедрості за своє питання, але мені вдалося створити рішення. Моя проблема, здавалося, пов'язана з неправильним значенням 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 символів).


3
@burhan: Якщо вам потрібен код, я завантажив його також у GitHub (тут: https://github.com/tadeck/onetimepass ), тому використовувати його в проектах як окремий модуль слід досить просто. Насолоджуйтесь!
Тадек

1
У мене виникли проблеми з цим кодом, тому що "секрет", який мені надає послуга, в яку я намагаюся увійти, - це малі, а не великі регістри. Змінення рядка 4, щоб прочитати "key = base64.b32decode (secret, True)", вирішило для мене проблему.
Кріс Мур

1
@ChrisMoore: Я оновив код, casefold=Trueтому люди зараз не повинні мати подібних проблем. Дякуємо за ваш внесок
Тадек

3
Мені щойно дали секрет на 23 символи. Ваш код не відповідає "TypeError: Неправильне прокладка", коли я передаю це секрет. Прошивка секрету, як це, вирішила проблему: key = base64.b32decode (secret + '====' [: 3 - ((len (secret) -1)% 4)], True)
Кріс Мур,

3
для python 3: зміни: ord(h[19]) & 15в: o = h[19] & 15 Спасибі BTW
Орвіль,

6

Я хотів, щоб сценарій 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)

Цікаво, але, можливо, ви хочете зробити його більш зрозумілим для читача. Будь ласка, зробіть імена змінних більш значущими або додайте документи. Крім того, дотримання PEP8 може отримати більшу підтримку. Чи порівняли ви ефективність між цими двома рішеннями? Останнє запитання: чи сумісне ваше рішення з Google Authenticator (оскільки питання стосувалося цього конкретного рішення)?
Тадек

@Tadeck Я додав кілька коментарів. І я все зробив, використовуючи цей сценарій. так що так, це повинно працювати ідеально.
Аніш Шах
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.