it-swarm-pt.tech

Implementação do Google Authenticator em Python

Estou tentando usar senhas únicas que podem ser geradas usando aplicativo Google Authenticator .

O que o Google Authenticator faz

Basicamente, o Google Authenticator implementa dois tipos de senhas:

  • HOTP - Senha descartável baseada em HMAC, que significa que a senha é alterada a cada chamada, em conformidade com RFC4226 = e
  • TOTP - Senha descartável baseada em tempo, que muda a cada período de 30 segundos (tanto quanto eu sei).

O Google Authenticator também está disponível como código aberto aqui: code.google.com/p/google-authenticator

Código atual

Eu estava procurando por soluções existentes para gerar senhas HOTP e TOTP, mas não encontrei muito. O código que tenho é o seguinte snippet responsável pela geração do 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
_

O problema que enfrento é que a senha que eu gero usando o código acima não é a mesma que gerada usando o aplicativo Google Authenticator para Android. Embora eu tenha tentado vários valores de _intervals_no_ (exatamente primeiro 10000, começando com _intervals_no = 0_), com secret sendo igual à chave fornecida no aplicativo GA.

Perguntas que eu tenho

Minhas perguntas são:

  1. O que estou fazendo errado?
  2. Como posso gerar HOTP e/ou TOTP em Python?
  3. Existe alguma biblioteca Python para isso?

Para resumir: forneça pistas que me ajudem a implementar a autenticação do Google Authenticator no meu código Python.

97
Tadeck

Eu queria definir uma recompensa na minha pergunta, mas consegui criar uma solução. Meu problema parecia estar conectado com o valor incorreto da tecla secret (ele deve ser o parâmetro correto para a função base64.b32decode()).

Abaixo, eu posto a solução de trabalho completa, com explicações sobre como usá-la.

Código

O código a seguir é suficiente. Também o enviei para o GitHub como módulo separado chamado onetimepass (disponível aqui: 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)

Tem duas funções:

  • get_hotp_token() gera token único (que deve ser invalidado após o uso único),
  • get_totp_token() gera token com base no tempo (alterado em intervalos de 30 segundos),

Parâmetros

Quando se trata de parâmetros:

  • secret é um valor secreto conhecido pelo servidor (o script acima) e pelo cliente (Google Authenticator, fornecendo-o como senha no aplicativo),
  • intervals_no é o número incrementado após cada geração do token (isso provavelmente deve ser resolvido no servidor verificando-se um número finito de números inteiros após a última verificação bem-sucedida no passado)

Como usá-lo

  1. Gere secret (deve ser o parâmetro correto para base64.b32decode()) - de preferência 16 caracteres (sem = sinais), pois certamente funcionou para o script e o Google Authenticator.
  2. Use get_hotp_token() se desejar que as senhas descartadas sejam invalidadas após cada uso. No Google Authenticator, esse tipo de senha que eu mencionei como baseado no contador. Para verificá-lo no servidor, você precisará verificar vários valores de intervals_no (como você não tem garantia de que o usuário não gerou a passagem entre as solicitações por algum motivo), mas não menos que o último intervals_no value (portanto, você provavelmente deve armazená-lo em algum lugar).
  3. Use get_totp_token(), se desejar que um token funcione em intervalos de 30 segundos. Você precisa garantir que os dois sistemas tenham o tempo definido corretamente (o que significa que ambos geram o mesmo registro de data e hora do Unix em um determinado momento).
  4. Certifique-se de se proteger de ataques de força bruta. Se a senha baseada em tempo for usada, tentar 1000000 valores em menos de 30 segundos oferece 100% de chance de adivinhar a senha. No caso de senhas baseadas em HMAC (HOTPs), parece ser ainda pior.

Exemplo

Ao usar o código a seguir para senha única baseada em HMAC:

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

você obterá o seguinte resultado:

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

que corresponde aos tokens gerados pelo aplicativo Google Authenticator (exceto se for menor que 6 sinais, o aplicativo adiciona zeros no início para atingir um comprimento de 6 caracteres).

146
Tadeck

Eu queria um script python para gerar a senha TOTP. Então, eu escrevi o script python. Esta é a minha implementação. Eu tenho este info na wikipedia e algum conhecimento sobre o HOTP e o TOTP para escrever 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
Anish Shah