Implementación de Google Authenticator en Python
security authentication (2)
Quería establecer una recompensa por mi pregunta, pero he logrado crear una solución. Mi problema parecía estar relacionado con el valor incorrecto de la clave secret
(debe ser el parámetro correcto para la función base64.b32decode()
).
A continuación, publico una solución de trabajo completa con explicaciones sobre cómo usarla.
Código
El siguiente código es suficiente. También lo he subido a GitHub como un 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 un token único (que debe invalidar después de un uso único), -
get_totp_token()
genera token en función del tiempo (cambiado en intervalos de 30 segundos),
Parámetros
Cuando se trata de parámetros:
-
secret
es un valor secreto conocido por el servidor (la secuencia de comandos anterior) y el cliente (Google Authenticator, proporcionándolo como contraseña dentro de la aplicación), -
intervals_no
es el número incremeneted después de cada generación del token (esto probablemente debería resolverse en el servidor mediante la comprobación de un número finito de enteros después de la última exitosa marcada en el pasado)
Cómo usarlo
- Generate
secret
(debe ser el parámetro correcto parabase64.b32decode()
) - preferiblemente 16-char (no=
signs), ya que seguramente funcionó tanto para script como para Google Authenticator. - Use
get_hotp_token()
si quiere que las contraseñas de un solo uso se invaliden después de cada uso. En Google Authenticator, este tipo de contraseñas las mencioné como basadas en el contador. Para verificarlo en el servidor deberá verificar varios valores deintervals_no
(ya que no tiene ninguna garantía de que el usuario no generó el pase entre las solicitudes por algún motivo), pero no menos que el últimointervals_no
valor (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. Debes asegurarte de que ambos sistemas tengan la hora correcta establecida (lo que significa que ambos generan la misma marca de tiempo de Unix en cualquier momento dado en el tiempo). - Asegúrate de protegerte del ataque de la fuerza bruta. Si se usa una contraseña basada en el tiempo, entonces intentar 1000000 valores en menos de 30 segundos brinda un 100% de probabilidad de adivinar la contraseña. En el caso de los 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)
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 son más cortos que 6 signos, la aplicación agrega ceros al principio para alcanzar una longitud de 6 caracteres).
Estoy tratando de usar contraseñas de un solo uso que se pueden generar con la aplicación Google Authenticator .
Lo que Google Authenticator hace
Básicamente, Google Authenticator implementa dos tipos de contraseñas:
- HOTP - Contraseña de una sola vez basada en HMAC, lo que significa que la contraseña se cambia con cada llamada, de conformidad con RFC4226 , y
- TOTP : Contraseña de una sola vez basada en el tiempo, que cambia cada 30 segundos (hasta donde yo sé).
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 utilizando el código anterior no es la misma que la que se generó con la aplicación Google Authenticator para Android. Aunque probé varios valores de intervals_no
(exactamente los primeros 10000, comenzando con intervals_no = 0
), siendo el secret
igual a la clave proporcionada en la aplicación GA.
Preguntas que tengo
Mis preguntas son:
- ¿Qué estoy haciendo mal?
- ¿Cómo puedo generar HOTP y / o TOTP en Python?
- ¿Hay alguna biblioteca de Python existente para esto?
Para resumir: por favor, denme alguna pista que me ayude a implementar la autenticación de Google Authenticator dentro de mi código Python.
Quería una secuencia de comandos python para generar la contraseña de TOTP. Entonces, escribí el script de python. Esta es mi implementación. Tengo esta info en wikipedia y algunos conocimientos sobre HOTP y TOTP para escribir este guión.
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)