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?
- Jak mogę wygenerować HOTP i / lub TOTP w Pythonie?
- 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.
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ć
- Wygeneruj
secret
(musi to być poprawny parametr dlabase64.b32decode()
) - najlepiej 16-char( bez znaków=
), ponieważ na pewno zadziałało zarówno dla script, jak i Google Authenticator. - 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ściintervals_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ć). - 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).
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).
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)
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