¿Autenticador de Google disponible como servicio público?

Respuestas:

121

El proyecto es de código abierto. No lo he usado. Pero está utilizando un algoritmo documentado (indicado en el RFC que figura en la página del proyecto de código abierto), y las implementaciones del autenticador admiten varias cuentas.

El proceso real es sencillo. El código de un tiempo es, esencialmente, un generador de números pseudoaleatorios. Un generador de números aleatorios es una fórmula que, una vez dada una semilla, o número inicial, continúa creando una secuencia de números aleatorios. Dada una semilla, mientras que los números pueden ser aleatorios entre sí, la secuencia en sí es determinista. Entonces, una vez que tenga su dispositivo y el servidor "sincronizados", los números aleatorios que crea el dispositivo, cada vez que presione el "botón de siguiente número", serán los mismos números aleatorios que el servidor espera.

Un sistema seguro de contraseña única es más sofisticado que un generador de números aleatorios, pero el concepto es similar. También hay otros detalles para ayudar a mantener el dispositivo y el servidor sincronizados.

Por lo tanto, no es necesario que otra persona aloje la autenticación, como, por ejemplo, OAuth. En su lugar, debe implementar ese algoritmo que sea compatible con las aplicaciones que Google proporciona para los dispositivos móviles. Ese software está (debería estar) disponible en el proyecto de código abierto.

Dependiendo de su sofisticación, debe tener todo lo que necesita para implementar el lado del servidor de este proceso para proporcionar el proyecto OSS y el RFC. No sé si hay una implementación específica para el software de su servidor (PHP, Java, .NET, etc.)

Pero, específicamente, no necesita un servicio externo para manejar esto.

Will Hartung
fuente
3
por otro lado, usar una solución ya existente, conocida y fácil de obtener disponible en muchos dispositivos móviles diferentes es de gran beneficio ... (pista)
usuario simple
26
¿Te refieres a SMS? Es lento, poco confiable y costoso.
Achraf Almouloudi
Escribí en un blog sobre cómo implementar Google Authenticator / RFC6238 compatible con 2fa para sitios web en Java puro: asaph.org/2016/04/google-authenticator-2fa-java.html (conector descarado)
Asaph
2
FYI NIST ya no recomienda la autenticación de dos factores con SMS a partir de agosto de 2016. No importa el costo que se considera inseguro.
TheDPQ
57

El algoritmo está documentado en RFC6238 . Va un poco así:

  • su servidor le da al usuario un secreto para instalar en Google Authenticator. Google hace esto como un código QR documentado aquí .
  • Google Authenticator genera un código de 6 dígitos a partir de un SHA1-HMAC del tiempo Unix y el secreto (muchos más detalles sobre esto en el RFC)
  • El servidor también conoce el tiempo secreto / unix para verificar el código de 6 dígitos.

He tenido un juego implementando el algoritmo en javascript aquí: http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/

russau
fuente
20

Hay una variedad 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/

Debe tener cuidado al implementar la autenticación de dos factores, debe asegurarse de que sus relojes en el servidor y el cliente estén sincronizados, que haya protección contra ataques de fuerza bruta en el token y que la semilla inicial utilizada sea adecuadamente grande.

James
fuente
El contenido fue excelente, pero cualquiera que use el primer enlace debe implementar los métodos de prevención de inyección SQL, ya que hay algunos defectos potenciales. Mire los problemas planteados para el primero. El segundo enlace es perfecto.
Septronic
9

Puede usar mi solución , publicada como respuesta a mi pregunta (hay un código completo de Python y una explicación ):

Implementación de Google Authenticator en Python

Creo que es bastante fácil implementarlo en PHP o Perl. Si tiene algún problema con esto, hágamelo saber.

También publiqué mi código en GitHub como módulo de Python.

Tadeck
fuente
1
Un poco después del hecho ... Solo quería seguir mencionando que hay un módulo Perl en CPAN: Auth :: GoogleAuthenticator ( search.cpan.org/dist/Auth-GoogleAuthenticator ).
DavidO
3

Sí, no necesita ningún servicio de red, porque la aplicación Google Authenticator no se comunicará con el servidor de Google, simplemente se sincroniza con el secreto inicial que genera su servidor (ingrese a su teléfono desde el código QR) mientras pasa el tiempo.

diyism
fuente
2

No es LAMP, pero si usa C #, este es el código que uso:

Código originario de:

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

La clase Base32Encoding es de esta respuesta:

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

Programa de ejemplo:

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
fuente
-1

Para usuarios de C #, ejecute esta aplicación de consola simple para comprender cómo verificar el código de token único. Tenga en cuenta que primero necesitamos instalar la biblioteca Otp.Net del paquete Nuget.

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
fuente