Implementación de Google Authenticator en Python


Estoy tratando de usar contraseñas de un solo uso que se pueden generar utilizando La aplicación Google Authenticator.

Lo que hace Google Authenticator

Básicamente, Google Authenticator implementa dos tipos de contraseñas:

  • HOTP - Contraseña de un solo uso basada en HMAC, lo que significa que la contraseña se cambia con cada llamada, de acuerdo con RFC4226 , y
  • TOTP - Contraseña de un solo uso basada en el tiempo, que cambia cada 30 segundos punto (que yo sepa).

Google Authenticator también está disponible como Código abierto aquí: code.google.com/p/google-authenticator

Código actual

Estaba buscando soluciones existentes para generar contraseñas HOTP y TOTP, pero no encontré mucho. El código que tengo es el siguiente fragmento responsable de generar 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

El problema al que me enfrento es que la contraseña que genero usando el código anterior no es la misma que la generada usando Google Aplicación de autenticación para Android. A pesar de que probé múltiples valores intervals_no (exactamente los primeros 10000, comenzando con intervals_no = 0), con secret siendo igual a la clave proporcionada dentro de la aplicación GA.

Preguntas que tengo

Mis preguntas son:

  1. ¿Qué estoy haciendo mal?
  2. ¿Cómo puedo generar HOTP y/o TOTP en Python?
  3. ¿Existen bibliotecas Python para esto?

Para resumir: por favor, dame cualquier pista que me ayude a implementar Google Authenticator autenticación dentro de mi código Python.

Author: Tadeck, 2011-12-16

2 answers

Quería establecer una recompensa por mi pregunta, pero he logrado crear una solución. Mi problema parecía estar conectado con el valor incorrecto de la tecla secret (debe ser el parámetro correcto para la función base64.b32decode()).

A continuación posteo una solución de trabajo completa con una explicación sobre cómo usarla.

Código

El siguiente código es suficiente. También lo he subido a GitHub como módulo separado llamado onetimepass (disponible aquí: 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)

Tiene dos funciones:

  • get_hotp_token() genera token de una sola vez (que debe invalidarse después de un solo uso),
  • get_totp_token() genera token basado en el tiempo (cambiado en intervalos de 30 segundos),

Parámetros

Cuando se trata de parámetros:

  • secret es un valor secreto conocido por el servidor (el script anterior) y el cliente (Google Authenticator, proporcionándolo como contraseña dentro solicitud),
  • intervals_no es el número incrementado después de cada generación del token (esto probablemente debería resolverse en el servidor comprobando algún número finito de enteros después del último comprobado con éxito en el pasado)

Cómo usarlo

  1. Generar secret (debe ser el parámetro correcto para base64.b32decode()) - preferiblemente 16-char (sin signos =), ya que seguramente funcionó tanto para script como para Google Authenticator.
  2. Use get_hotp_token() si desea contraseñas de un solo uso invalidado después de cada uso. En Google Authenticator este tipo de contraseñas mencioné como basadas en el contador. Para comprobarlo en el servidor, necesitará comprobar varios valores de intervals_no (ya que no tiene garantía de que el usuario no haya generado el pase entre las solicitudes por alguna razón), pero no menos que el último valor intervals_no que funcione (por lo tanto, probablemente debería guardarlo en algún lugar).
  3. Use get_totp_token(), si desea que un token funcione en intervalos de 30 segundos. Usted tiene que asegurarse de que ambos sistemas tener la hora correcta establecida (lo que significa que ambos generan la misma marca de tiempo Unix en cualquier momento dado en el tiempo).
  4. Asegúrate de protegerte del ataque de fuerza bruta. Si se usa una contraseña basada en el tiempo, probar 1000000 valores en menos de 30 segundos da un 100% de probabilidad de adivinar la contraseña. En el caso de passowrds basados en HMAC (HOTPs) parece ser aún peor.

Ejemplo

Cuando se utiliza el siguiente código para una contraseña única basada en HMAC:

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

Usted obtendrá el siguiente resultado:

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

Que corresponde a los tokens generados por la aplicación Google Authenticator (excepto si tiene menos de 6 signos, la aplicación agrega ceros al principio para alcanzar una longitud de 6 caracteres).

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-13 00:49:17

Quería un script python para generar la contraseña TOTP. Así que escribí el script python. Esta es mi implementación. Tengo esta info en wikipedia y algunos conocimientos sobre HOTP y TOTP para escribir este 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
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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-22 18:22:17