Implementacja Google Authenticator w Pythonie

Próbuję użyć haseł jednorazowych, które można wygenerować za pomocą Aplikacji Google Authenticator .

Co robi Google Authenticator

Zasadniczo Google Authenticator implementuje dwa typy haseł:

  • HOTP - hasło jednorazowe oparte na HMAC, co oznacza, że hasło jest zmieniane przy każdym wywołaniu, zgodnie z RFC4226 i
  • TOTP -oparte na czasie hasło jednorazowe, które zmienia się co 30 sekund kropka (o ile mi wiadomo).

Google Authenticator jest również dostępny jako Open Source tutaj: code.google.com/p/google-authenticator

Bieżący kod

Szukałem istniejących rozwiązań do generowania haseł HOTP i TOTP, ale nie znalazłem zbyt wiele. Kod, który mam, to następujący fragment odpowiedzialny za generowanie 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

Problem, z którym się borykam polega na tym, że hasło generowane za pomocą powyższego kodu nie jest tym samym, co wygenerowane za pomocą Google Aplikacja Authenticator dla Androida. Mimo że próbowałem wielu wartości intervals_no (dokładnie pierwsze 10000, zaczynając od intervals_no = 0), przy czym secret jest równe kluczowi dostarczonemu w aplikacji GA.

Pytania, które mam

Moje pytania to:
    Co robię źle?
  1. Jak mogę wygenerować HOTP i / lub TOTP w Pythonie?
  2. czy są do tego jakieś istniejące biblioteki Pythona?

Podsumowując: proszę podać jakieś wskazówki, które pomogą mi wdrożyć Google Authenticator uwierzytelnianie w kodzie Pythona.

Author: Tadeck, 2011-12-16

2 answers

Chciałem ustawić nagrodę za moje pytanie, ale udało mi się stworzyć rozwiązanie. Mój problem zdawał się być związany z nieprawidłową wartością klucza secret (musi to być prawidłowy parametr dla funkcji base64.b32decode()).

Poniżej zamieszczam pełne rozwiązanie robocze z wyjaśnieniem, jak go używać.

Kod

Poniższy kod wystarczy. Wgrałem go również na GitHub jako oddzielny moduł o nazwie onetimepass (dostępny tutaj: 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)

Ma dwie funkcje:

  • W tym celu należy wykonać następujące czynności:]}
  • get_totp_token() generuje token na podstawie czasu (zmieniany w odstępach 30-sekundowych),

Parametry

Jeśli chodzi o parametry:

  • secret jest tajną wartością znaną serwerowi (powyższy skrypt) i Klientowi (Google Authenticator, podając ją jako hasło w zastosowanie),
  • intervals_no jest liczbą zwiększaną po każdym wygenerowaniu tokena (powinno to być prawdopodobnie rozwiązane na serwerze przez sprawdzenie pewnej skończonej liczby całkowitej po ostatniej sprawdzonej w przeszłości)

Jak go używać

  1. Wygeneruj secret (musi to być poprawny parametr dla base64.b32decode()) - najlepiej 16-char( bez znaków =), ponieważ na pewno zadziałało zarówno dla script, jak i Google Authenticator.
  2. Użyj get_hotp_token() jeśli chcesz hasła jednorazowe unieważnione po każdym użyciu. W Google Authenticator tego typu hasła wymieniłem jako oparte na liczniku. Do sprawdzenia na serwerze trzeba będzie sprawdzić kilka wartości intervals_no (ponieważ nie masz gwarancji, że użytkownik nie wygenerował przejścia między żądaniami z jakiegoś powodu), ale nie mniej niż ostatnia działająca wartość intervals_no (dlatego prawdopodobnie powinieneś ją gdzieś przechowywać).
  3. użyj get_totp_token(), Jeśli chcesz, aby token działał w 30-sekundowych odstępach. Musisz upewnić się, że oba systemy mają ustawiony prawidłowy czas (co oznacza, że oba generują ten sam uniksowy znacznik czasu w danym momencie w czasie).
  4. Zabezpiecz się przed brutalnym atakiem. Jeśli używane jest hasło oparte na czasie, próba 1000000 wartości w czasie krótszym niż 30 sekund daje 100% szansę na odgadnięcie hasła. W przypadku HMAC-based passowrds (HOTP) wydaje się być jeszcze gorzej.

Przykład

Przy użyciu poniższego kodu dla jednorazowego hasła opartego na HMAC:

secret = 'MZXW633PN5XW6MZX'
for i in xrange(1, 10):
    print i, get_hotp_token(secret, intervals_no=i)

Ty otrzyma następujący wynik:

1 448400
2 656122
3 457125
4 35022
5 401553
6 581333
7 16329
8 529359
9 171710

, który odpowiada tokenom generowanym przez aplikację Google Authenticator(z wyjątkiem krótszych niż 6 znaków, aplikacja dodaje zera do początku, aby osiągnąć długość 6 znaków).

 126
Author: Tadeck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-13 00:49:17

Chciałem skrypt Pythona do generowania hasła TOTP. Napisałem skrypt Pythona. To moja realizacja. Mam to info na Wikipedii i trochę wiedzy o HOTP i TOTP do napisania tego skryptu.

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)
 6
Author: Anish Shah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-22 18:22:17