Implementierung von Google Authenticator in Python


103

Ich versuche, Einmalkennwörter zu verwenden, die mit der Google Authenticator-Anwendung generiert werden können .

Was macht Google Authenticator?

Grundsätzlich implementiert Google Authenticator zwei Arten von Kennwörtern:

  • HOTP - HMAC-basiertes Einmalpasswort, dh das Passwort wird bei jedem Anruf gemäß RFC4226 und geändert
  • TOTP - Zeitbasiertes Einmalpasswort, das sich alle 30 Sekunden ändert (soweit ich weiß).

Google Authenticator ist hier auch als Open Source verfügbar: code.google.com/p/google-authenticator

Aktueller Code

Ich suchte nach vorhandenen Lösungen, um HOTP- und TOTP-Passwörter zu generieren, fand aber nicht viel. Der Code, den ich habe, ist das folgende Snippet, das für die Generierung von HOTP verantwortlich ist:

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

Das Problem, mit dem ich konfrontiert bin, ist, dass das Passwort, das ich mit dem obigen Code generiere, nicht mit dem übereinstimmt, das mit der Google Authenticator-App für Android generiert wurde. Obwohl ich mehrere intervals_noWerte ausprobiert habe (genau die ersten 10000, beginnend mit intervals_no = 0), wobei secretsie dem in der GA-App bereitgestellten Schlüssel entsprechen.

Fragen, die ich habe

Meine Fragen sind:

  1. Was mache ich falsch?
  2. Wie kann ich HOTP und / oder TOTP in Python generieren?
  3. Gibt es dafür Python-Bibliotheken?

Zusammenfassend: Geben Sie mir alle Hinweise, die mir bei der Implementierung der Google Authenticator-Authentifizierung in meinem Python-Code helfen.

Antworten:


151

Ich wollte eine Prämie auf meine Frage setzen, aber es ist mir gelungen, eine Lösung zu finden. Mein Problem schien mit einem falschen secretSchlüsselwert verbunden zu sein (es muss der richtige Parameter für die base64.b32decode()Funktion sein).

Unten poste ich eine vollständige Arbeitslösung mit Erläuterungen zur Verwendung.

Code

Der folgende Code reicht aus. Ich habe es auch als separates Modul namens onetimepass auf GitHub hochgeladen (hier verfügbar: 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)

Es hat zwei Funktionen:

  • get_hotp_token() generiert ein einmaliges Token (das nach einmaliger Verwendung ungültig werden sollte),
  • get_totp_token() generiert ein Token basierend auf der Zeit (geändert in 30-Sekunden-Intervallen),

Parameter

Wenn es um Parameter geht:

  • secret ist ein geheimer Wert, der dem Server (das obige Skript) und dem Client (Google Authenticator bekannt ist, indem er in der Anwendung als Kennwort angegeben wird).
  • intervals_no ist die Anzahl, die nach jeder Generation des Tokens erhöht wird (dies sollte wahrscheinlich auf dem Server behoben werden, indem eine endliche Anzahl von Ganzzahlen nach der letzten erfolgreichen Überprüfung in der Vergangenheit überprüft wird).

Wie man es benutzt

  1. Generieren secret(es muss der richtige Parameter für sein base64.b32decode()) - vorzugsweise 16 =Zeichen (keine Vorzeichen), da dies sicherlich sowohl für das Skript als auch für Google Authenticator funktioniert hat.
  2. Verwenden get_hotp_token()Sie diese Option, wenn Einmalkennwörter nach jeder Verwendung ungültig werden sollen. In Google Authenticator wurde diese Art von Passwörtern basierend auf dem Zähler erwähnt. Um es auf dem Server zu überprüfen, müssen Sie mehrere Werte von überprüfen intervals_no(da Sie nicht garantieren können, dass der Benutzer den Durchgang zwischen den Anforderungen aus irgendeinem Grund nicht generiert hat), aber nicht weniger als den letzten Arbeitswert intervals_no(daher sollten Sie ihn wahrscheinlich speichern irgendwo).
  3. Verwenden get_totp_token()Sie diese Option , wenn ein Token in Intervallen von 30 Sekunden ausgeführt werden soll. Sie müssen sicherstellen, dass für beide Systeme die richtige Zeit eingestellt ist (was bedeutet, dass beide zu jedem Zeitpunkt denselben Unix-Zeitstempel generieren).
  4. Schützen Sie sich vor Brute-Force-Angriffen. Wenn ein zeitbasiertes Kennwort verwendet wird, besteht eine 100% ige Chance, das Kennwort zu erraten, wenn 1000000-Werte in weniger als 30 Sekunden versucht werden. Bei HMAC-basierten Passowrds (HOTPs) scheint es noch schlimmer zu sein.

Beispiel

Bei Verwendung des folgenden Codes für ein einmaliges HMAC-basiertes Kennwort:

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

Sie erhalten folgendes Ergebnis:

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

Dies entspricht den von der Google Authenticator-App generierten Token (außer wenn die Zeichen kürzer als 6 Zeichen sind, fügt die App am Anfang Nullen hinzu, um eine Länge von 6 Zeichen zu erreichen).


3
@burhan: Wenn Sie den Code benötigen, habe ich ihn auch auf GitHub hochgeladen (hier: https://github.com/tadeck/onetimepass ), daher sollte es recht einfach sein, ihn in Projekten als separates Modul zu verwenden. Genießen!
Tadeck

1
Ich hatte ein Problem mit diesem Code, weil das 'Geheimnis', das mir von dem Dienst bereitgestellt wurde, bei dem ich mich anmelden möchte, Kleinbuchstaben und keine Großbuchstaben waren. Das Ändern von Zeile 4 in "key = base64.b32decode (secret, True)" hat das Problem für mich behoben.
Chris Moore

1
@ChrisMoore: Ich habe den Code mit aktualisiert, casefold=Truedamit die Leute jetzt keine ähnlichen Probleme haben sollten. Danke für deinen Beitrag.
Tadeck

3
Ich habe gerade ein 23-Zeichen-Geheimnis von einer Site erhalten. Ihr Code schlägt mit einem "TypeError: Incorrect Padding" fehl, wenn ich ihm dieses Geheimnis gebe. Das Auffüllen des Geheimnisses auf diese Weise behebt das Problem: key = base64.b32decode (secret + '====' [: 3 - ((len (secret) -1)% 4)], True)
Chris Moore

3
für Python 3: ändern: ord(h[19]) & 15in: o = h[19] & 15 Danke BTW
Watata

6

Ich wollte ein Python-Skript, um ein TOTP-Passwort zu generieren. Also habe ich das Python-Skript geschrieben. Dies ist meine Implementierung. Ich habe diese Informationen auf Wikipedia und einige Kenntnisse über HOTP und TOTP, um dieses Skript zu schreiben.

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)

Interessant, aber vielleicht möchten Sie es für den Leser verständlicher machen. Bitte machen Sie Variablennamen aussagekräftiger oder fügen Sie Dokumentzeichenfolgen hinzu. Wenn Sie PEP8 folgen, erhalten Sie möglicherweise mehr Unterstützung. Haben Sie die Leistung zwischen diesen beiden Lösungen verglichen? Letzte Frage: Ist Ihre Lösung mit Google Authenticator kompatibel (da es sich um eine spezielle Lösung handelte)?
Tadeck

@ Tadeck Ich habe einige Kommentare hinzugefügt. Und ich habe meine Sachen mit diesem Skript erledigt. Also ja, es sollte perfekt funktionieren.
Anish Shah
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.