web-dev-qa-db-fra.com

Implémentation de Google Authenticator dans Python

J'essaie d'utiliser des mots de passe à usage unique qui peuvent être générés à l'aide de application Google Authenticator .

Que fait Google Authenticator

Fondamentalement, Google Authenticator implémente deux types de mots de passe:

  • [~ # ~] hotp [~ # ~] - Mot de passe unique basé sur HMAC, ce qui signifie que le mot de passe est modifié à chaque appel, conformément à RFC4226 , et
  • [~ # ~] totp [~ # ~] - Mot de passe unique basé sur le temps, qui change pour chaque période de 30 secondes (pour autant que Je sais).

Google Authenticator est également disponible en Open Source ici: code.google.com/p/google-authenticator

Code actuel

Je cherchais des solutions existantes pour générer des mots de passe HOTP et TOTP, mais je n'ai pas trouvé grand-chose. Le code que j'ai est l'extrait suivant responsable de la génération de 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

Le problème auquel je suis confronté est que le mot de passe que je génère à l'aide du code ci-dessus n'est pas le même que celui généré à l'aide de l'application Google Authenticator pour Android. Même si j'ai essayé plusieurs intervals_no valeurs (exactement les premiers 10000, commençant par intervals_no = 0), secret étant égal à la clé fournie dans l'application GA.

Questions que j'ai

Mes questions sont:

  1. Qu'est-ce que je fais mal?
  2. Comment puis-je générer HOTP et/ou TOTP en Python?
  3. Existe-t-il des bibliothèques Python pour cela?

Pour résumer: veuillez me donner des indices qui m'aideront à implémenter l'authentification Google Authenticator dans mon code Python.

97
Tadeck

Je voulais mettre une prime sur ma question, mais j'ai réussi à créer une solution. Mon problème semblait être lié à une valeur incorrecte de la touche secret (il doit s'agir d'un paramètre correct pour la fonction base64.b32decode()).

Ci-dessous, je poste une solution de travail complète avec des explications sur la façon de l'utiliser.

Code

Le code suivant suffit. Je l'ai également téléchargé sur GitHub en tant que module séparé appelé onetimepass (disponible ici: 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)

Il a deux fonctions:

  • get_hotp_token() génère un jeton unique (qui devrait être invalidé après une seule utilisation),
  • get_totp_token() génère un jeton basé sur le temps (changé par intervalles de 30 secondes),

Paramètres

En ce qui concerne les paramètres:

  • secret est une valeur secrète connue du serveur (le script ci-dessus) et du client (Google Authenticator, en le fournissant comme mot de passe dans l'application),
  • intervals_no Est le nombre incrémenté après chaque génération du jeton (cela devrait probablement être résolu sur le serveur en vérifiant un nombre fini d'entiers après la dernière vérification réussie dans le passé)

Comment l'utiliser

  1. Générez secret (ce doit être un paramètre correct pour base64.b32decode()) - de préférence 16 caractères (pas de signe =), Car cela a sûrement fonctionné pour le script et Google Authenticator.
  2. Utilisez get_hotp_token() si vous souhaitez que les mots de passe à usage unique soient invalidés après chaque utilisation. Dans Google Authenticator, ce type de mots de passe que j'ai mentionné est basé sur le compteur. Pour le vérifier sur le serveur, vous devrez vérifier plusieurs valeurs de intervals_no (Car vous n'avez aucune garantie que l'utilisateur n'a pas généré le passage entre les demandes pour une raison quelconque), mais pas moins que le dernier intervals_no (Vous devriez donc probablement le stocker quelque part).
  3. Utilisez get_totp_token(), si vous souhaitez qu'un jeton fonctionne à des intervalles de 30 secondes. Vous devez vous assurer que les deux systèmes ont une heure correcte (ce qui signifie qu'ils génèrent tous les deux le même horodatage Unix à un moment donné).
  4. Assurez-vous de vous protéger contre les attaques par force brute. Si un mot de passe basé sur le temps est utilisé, essayer des valeurs 1000000 en moins de 30 secondes donne 100% de chances de deviner le mot de passe. Dans le cas des mots de passe basés sur HMAC (HOTP), cela semble encore pire.

Exemple

Lorsque vous utilisez le code suivant pour un mot de passe unique basé sur HMAC:

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

vous obtiendrez le résultat suivant:

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

ce qui correspond aux jetons générés par l'application Google Authenticator (sauf si moins de 6 signes, l'application ajoute des zéros au début pour atteindre une longueur de 6 caractères).

146
Tadeck

Je voulais un script python pour générer le mot de passe TOTP. Donc, j'ai écrit le script python. Ceci est mon implémentation. J'ai ceci info sur wikipedia et quelques connaissances sur HOTP et TOTP pour écrire ce script.

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
Anish Shah