Google Authenticator disponível como serviço público?

Respostas:

121

O projeto é de código aberto. Eu não usei isso. Mas está usando um algoritmo documentado (observado na RFC listada na página do projeto de código aberto), e as implementações do autenticador suportam várias contas.

O processo real é direto. O código único é, essencialmente, um gerador de números pseudo-aleatórios. Um gerador de números aleatórios é uma fórmula que uma vez fornecida uma semente, ou número inicial, continua a criar um fluxo de números aleatórios. Dada uma semente, enquanto os números podem ser aleatórios entre si, a própria sequência é determinística. Assim, quando você tiver o dispositivo e o servidor "sincronizados", os números aleatórios criados pelo dispositivo, sempre que você pressionar o "botão do próximo número", serão os mesmos números aleatórios que o servidor espera.

Um sistema de senha de uso único seguro é mais sofisticado do que um gerador de números aleatórios, mas o conceito é semelhante. Há também outros detalhes para ajudar a manter o dispositivo e o servidor sincronizados.

Portanto, não há necessidade de alguém para hospedar a autenticação, como, por exemplo, OAuth. Em vez disso, você precisa implementar esse algoritmo que seja compatível com os aplicativos que o Google fornece para os dispositivos móveis. Esse software está (deveria estar) disponível no projeto de código aberto.

Dependendo da sua sofisticação, você deve ter tudo o que precisa para implementar o lado do servidor desse processo, fornecer ao projeto OSS e ao RFC. Não sei se há uma implementação específica para o software do servidor (PHP, Java, .NET etc.)

Mas, especificamente, você não precisa de um serviço externo para lidar com isso.

Will Hartung
fonte
3
por outro lado, usar uma solução já existente, conhecida e fácil de obter disponível em muitos dispositivos móveis diferentes é de grande benefício ... (dica dica)
simpleuser
26
Você quer dizer SMS? É lento, pouco confiável e caro.
Achraf Almouloudi
Eu escrevi sobre como implementar Google Authenticator / RFC6238 2FA compatível para sites em java puro: asaph.org/2016/04/google-authenticator-2fa-java.html (plug descarado)
Asaph
2
O NIST da FYI não recomenda mais a autenticação de dois fatores usando o SMS a partir de agosto de 2016. Não importa o custo considerado inseguro.
TheDPQ 12/08
57

O algoritmo está documentado na RFC6238 . É um pouco assim:

  • seu servidor fornece ao usuário um segredo para instalar no Google Authenticator. O Google faz isso como um código QR documentado aqui .
  • O Google Authenticator gera um código de 6 dígitos a partir de um SHA1-HMAC da hora e do segredo do Unix (muito mais detalhes sobre isso na RFC)
  • O servidor também sabe o tempo secreto / unix para verificar o código de 6 dígitos.

Eu já joguei implementando o algoritmo em javascript aqui: http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/

russau
fonte
20

Há uma variedade de bibliotecas para PHP (The LAMP Stack)

PHP

https://code.google.com/p/ga4php/

http://www.idontplaydarts.com/2011/07/google-totp-two-factor-authentication-for-php/

Você deve ter cuidado ao implementar a autenticação de dois fatores, garantir que seus relógios no servidor e no cliente estejam sincronizados, que exista proteção contra ataques de força bruta no token e que a semente inicial usada seja adequadamente grande.

James
fonte
O conteúdo foi ótimo, mas qualquer pessoa que use o primeiro link deve implementar os métodos de prevenção de injeção de SQL, pois há algumas falhas em potencial. Veja as questões levantadas para o primeiro. O segundo link é perfeito.
Septronic 24/10/2015
9

Você pode usar minha solução , postada como resposta à minha pergunta (há código e explicação completos do Python ):

Implementação do Google Authenticator em Python

É bastante fácil implementá-lo em PHP ou Perl, eu acho. Se você tiver algum problema com isso, entre em contato.

Também publiquei meu código no GitHub como módulo Python.

Tadeck
fonte
1
Um pouco depois do fato ... Eu só queria acompanhar mencionando que existe um módulo Perl no CPAN: Auth :: GoogleAuthenticator ( search.cpan.org/dist/Auth-GoogleAuthenticator ).
DaVido
3

Sim, não precisa de serviço de rede, porque o aplicativo Google Authenticator não se comunica com o servidor do Google, ele apenas se mantém sincronizado com o segredo inicial que seu servidor gera (insira no telefone a partir do código QR) enquanto o tempo passa.

diyism
fonte
2

Não é LAMP, mas se você usa C #, este é o código que eu uso:

Código originalmente de:

https://github.com/kspearrin/Otp.NET

A classe Base32Encoding é desta resposta:

https://stackoverflow.com/a/7135008/3850405

Exemplo de programa:

class Program
{
    static void Main(string[] args)
    {
        var bytes = Base32Encoding.ToBytes("JBSWY3DPEHPK3PXP");

        var totp = new Totp(bytes);

        var result = totp.ComputeTotp();
        var remainingTime = totp.RemainingSeconds();
    }
}

Totp:

public class Totp
{
    const long unixEpochTicks = 621355968000000000L;

    const long ticksToSeconds = 10000000L;

    private const int step = 30;

    private const int totpSize = 6;

    private byte[] key;

    public Totp(byte[] secretKey)
    {
        key = secretKey;
    }

    public string ComputeTotp()
    {
        var window = CalculateTimeStepFromTimestamp(DateTime.UtcNow);

        var data = GetBigEndianBytes(window);

        var hmac = new HMACSHA1();
        hmac.Key = key;
        var hmacComputedHash = hmac.ComputeHash(data);

        int offset = hmacComputedHash[hmacComputedHash.Length - 1] & 0x0F;
        var otp = (hmacComputedHash[offset] & 0x7f) << 24
               | (hmacComputedHash[offset + 1] & 0xff) << 16
               | (hmacComputedHash[offset + 2] & 0xff) << 8
               | (hmacComputedHash[offset + 3] & 0xff) % 1000000;

        var result = Digits(otp, totpSize);

        return result;
    }

    public int RemainingSeconds()
    {
        return step - (int)(((DateTime.UtcNow.Ticks - unixEpochTicks) / ticksToSeconds) % step);
    }

    private byte[] GetBigEndianBytes(long input)
    {
        // Since .net uses little endian numbers, we need to reverse the byte order to get big endian.
        var data = BitConverter.GetBytes(input);
        Array.Reverse(data);
        return data;
    }

    private long CalculateTimeStepFromTimestamp(DateTime timestamp)
    {
        var unixTimestamp = (timestamp.Ticks - unixEpochTicks) / ticksToSeconds;
        var window = unixTimestamp / (long)step;
        return window;
    }

    private string Digits(long input, int digitCount)
    {
        var truncatedValue = ((int)input % (int)Math.Pow(10, digitCount));
        return truncatedValue.ToString().PadLeft(digitCount, '0');
    }

}

Base32Encoding:

public static class Base32Encoding
{
    public static byte[] ToBytes(string input)
    {
        if (string.IsNullOrEmpty(input))
        {
            throw new ArgumentNullException("input");
        }

        input = input.TrimEnd('='); //remove padding characters
        int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
        byte[] returnArray = new byte[byteCount];

        byte curByte = 0, bitsRemaining = 8;
        int mask = 0, arrayIndex = 0;

        foreach (char c in input)
        {
            int cValue = CharToValue(c);

            if (bitsRemaining > 5)
            {
                mask = cValue << (bitsRemaining - 5);
                curByte = (byte)(curByte | mask);
                bitsRemaining -= 5;
            }
            else
            {
                mask = cValue >> (5 - bitsRemaining);
                curByte = (byte)(curByte | mask);
                returnArray[arrayIndex++] = curByte;
                curByte = (byte)(cValue << (3 + bitsRemaining));
                bitsRemaining += 3;
            }
        }

        //if we didn't end with a full byte
        if (arrayIndex != byteCount)
        {
            returnArray[arrayIndex] = curByte;
        }

        return returnArray;
    }

    public static string ToString(byte[] input)
    {
        if (input == null || input.Length == 0)
        {
            throw new ArgumentNullException("input");
        }

        int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
        char[] returnArray = new char[charCount];

        byte nextChar = 0, bitsRemaining = 5;
        int arrayIndex = 0;

        foreach (byte b in input)
        {
            nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
            returnArray[arrayIndex++] = ValueToChar(nextChar);

            if (bitsRemaining < 4)
            {
                nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
                returnArray[arrayIndex++] = ValueToChar(nextChar);
                bitsRemaining += 5;
            }

            bitsRemaining -= 3;
            nextChar = (byte)((b << bitsRemaining) & 31);
        }

        //if we didn't end with a full char
        if (arrayIndex != charCount)
        {
            returnArray[arrayIndex++] = ValueToChar(nextChar);
            while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding
        }

        return new string(returnArray);
    }

    private static int CharToValue(char c)
    {
        int value = (int)c;

        //65-90 == uppercase letters
        if (value < 91 && value > 64)
        {
            return value - 65;
        }
        //50-55 == numbers 2-7
        if (value < 56 && value > 49)
        {
            return value - 24;
        }
        //97-122 == lowercase letters
        if (value < 123 && value > 96)
        {
            return value - 97;
        }

        throw new ArgumentException("Character is not a Base32 character.", "c");
    }

    private static char ValueToChar(byte b)
    {
        if (b < 26)
        {
            return (char)(b + 65);
        }

        if (b < 32)
        {
            return (char)(b + 24);
        }

        throw new ArgumentException("Byte is not a value Base32 value.", "b");
    }

}
Ogglas
fonte
-1

Para usuários de C #, execute este aplicativo simples do console para entender como verificar o código do token único. Observe que precisamos instalar a biblioteca Otp.Net a partir do pacote Nuget primeiro.

static string secretKey = "JBSWY3DPEHPK3PXP"; //add this key to your Google Authenticator app  

private static void Main(string[] args)
{
        var bytes = Base32Encoding.ToBytes(secretKey);

        var totp = new Totp(bytes);

        while (true)
        {
            Console.Write("Enter your code from Google Authenticator app: ");
            string userCode = Console.ReadLine();

            //Generate one time token code
            string tokenInApp = totp.ComputeTotp();
            int remainingSeconds = totp.RemainingSeconds();

            if (userCode.Equals(tokenInApp)
                && remainingSeconds > 0)
            {
                Console.WriteLine("Success!");
            }
            else
            {
                Console.WriteLine("Failed. Try again!");
            }
        }
}
Minh Nguyen
fonte