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:
- ¿Qué estoy haciendo mal?
- ¿Cómo puedo generar HOTP y/o TOTP en Python?
- ¿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.
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
- Generar
secret
(debe ser el parámetro correcto parabase64.b32decode()
) - preferiblemente 16-char (sin signos=
), ya que seguramente funcionó tanto para script como para Google Authenticator. - 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 deintervals_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 valorintervals_no
que funcione (por lo tanto, probablemente debería guardarlo en algún lugar). - 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). - 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).
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)
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