Google Authenticator disponibile come servizio pubblico?


Risposte:


121

Il progetto è open source. Non l'ho usato Ma utilizza un algoritmo documentato (indicato nella RFC elencata nella pagina del progetto open source) e le implementazioni dell'autenticatore supportano più account.

Il processo effettivo è semplice. Il time code è essenzialmente un generatore di numeri pseudo casuali. Un generatore di numeri casuali è una formula che una volta dato un seme, o numero iniziale, continua a creare un flusso di numeri casuali. Dato un seme, mentre i numeri possono essere casuali tra loro, la sequenza stessa è deterministica. Quindi, una volta che il dispositivo e il server sono "sincronizzati", i numeri casuali creati dal dispositivo, ogni volta che si preme il "pulsante numero successivo", saranno gli stessi numeri casuali previsti dal server.

Un sistema sicuro di password monouso è più sofisticato di un generatore di numeri casuali, ma il concetto è simile. Ci sono anche altri dettagli per mantenere sincronizzati il ​​dispositivo e il server.

Quindi, non è necessario che qualcun altro ospiti l'autenticazione, come dire OAuth. È invece necessario implementare quell'algoritmo compatibile con le app fornite da Google per i dispositivi mobili. Tale software è (dovrebbe essere) disponibile sul progetto open source.

A seconda della tua raffinatezza, dovresti avere tutto ciò che ti serve per implementare il lato server di questo processo, fornendo il progetto OSS e la RFC. Non so se esiste un'implementazione specifica per il software del tuo server (PHP, Java, .NET, ecc.)

Ma, in particolare, non è necessario un servizio fuori sede per gestire questo.


3
d'altra parte, usare una soluzione già esistente, ben nota, facile da ottenere disponibile su molti dispositivi mobili diversi è di grande beneficio ... (suggerimento suggerimento)
simpleuser

26
Intendi SMS? È lento, inaffidabile e costoso.
Achraf Almouloudi,

Ho scritto un blog su come implementare 2fa compatibile con Google Authenticator / RFC6238 per siti Web in puro Java: asaph.org/2016/04/google-authenticator-2fa-java.html (spina spudorata)
Asaph

2
FYI NIST non raccomanda più l'autenticazione a due fattori tramite SMS a partire da agosto 2016. Non dimenticare mai il costo considerato insicuro.
TheDPQ

57

L'algoritmo è documentato in RFC6238 . Va un po 'così:

  • il tuo server fornisce all'utente un segreto per l'installazione in Google Authenticator. Google lo fa come un codice QR documentato qui .
  • Google Authenticator genera un codice a 6 cifre da un SHA1-HMAC del tempo Unix e il segreto (molti più dettagli su questo nella RFC)
  • Il server conosce anche il tempo segreto / unix per verificare il codice a 6 cifre.

Ho giocato con l'implementazione dell'algoritmo in JavaScript qui: http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/


20

Esistono diverse librerie per PHP (The LAMP Stack)

PHP

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

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

Prestare attenzione quando si implementa l'autenticazione a due fattori, è necessario assicurarsi che i clock sul server e sul client siano sincronizzati, che sia in atto una protezione contro gli attacchi di forza bruta sul token e che il seed iniziale utilizzato sia adeguatamente grande.


Il contenuto è stato ottimo, ma chiunque utilizzi il primo link dovrebbe implementare i metodi di prevenzione dell'iniezione SQL, poiché ci sono alcuni potenziali difetti. Guarda le questioni sollevate per la prima. Il secondo link è perfetto.
Septronic,

9

Puoi usare la mia soluzione , pubblicata come risposta alla mia domanda (c'è un codice Python completo e una spiegazione ):

Implementazione di Google Authenticator in Python

È piuttosto facile implementarlo in PHP o Perl, credo. Se hai problemi con questo, per favore fatemelo sapere.

Ho anche pubblicato il mio codice su GitHub come modulo Python.


1
Poco dopo il fatto ... Volevo solo dare seguito menzionando che esiste un modulo Perl su CPAN: Auth :: GoogleAuthenticator ( search.cpan.org/dist/Auth-GoogleAuthenticator ).
DavidO,



3

Sì, non è necessario alcun servizio di rete, poiché l'app Google Authenticator non comunica con il server Google, si sincronizza semplicemente con il segreto iniziale che il server genera (immissione nel telefono dal codice QR) mentre il tempo passa.


2

Non LAMP, ma se usi C # questo è il codice che uso:

Codice originario di:

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

La classe Base32Encoding proviene da questa risposta:

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

Esempio di programma:

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");
    }

}


-1

Per l'utente C #, eseguire questa semplice app console per capire come verificare il codice token una tantum. Si noti che è necessario installare prima la libreria Otp.Net dal pacchetto 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!");
            }
        }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.