Google Authenticator jest dostępny jako usługa publiczna?


Odpowiedzi:


121

Projekt open source. Nie używałem tego. Ale używa udokumentowanego algorytmu (odnotowanego w RFC wymienionym na stronie projektu open source), a implementacje uwierzytelniające obsługują wiele kont.

Rzeczywisty proces jest prosty. Kod jednorazowy to zasadniczo generator liczb pseudolosowych. Generator liczb losowych to formuła, która po podaniu ziarna lub liczby początkowej kontynuuje tworzenie strumienia liczb losowych. Biorąc pod uwagę ziarno, podczas gdy liczby mogą być losowe względem siebie, sama sekwencja jest deterministyczna. Tak więc, gdy urządzenie i serwer są „zsynchronizowane”, losowe liczby tworzone przez urządzenie, za każdym razem, gdy naciśniesz przycisk „następny numer”, będą takie same, losowe liczby, których oczekuje serwer.

Bezpieczny system haseł jednorazowych jest bardziej wyrafinowany niż generator liczb losowych, ale koncepcja jest podobna. Istnieją również inne szczegóły ułatwiające synchronizację urządzenia i serwera.

Nie ma więc potrzeby, aby ktoś inny obsługiwał uwierzytelnianie, na przykład OAuth. Zamiast tego musisz wdrożyć ten algorytm, który jest kompatybilny z aplikacjami dostarczanymi przez Google dla urządzeń mobilnych. To oprogramowanie jest (powinno być) dostępne w projekcie open source.

W zależności od Twojego wyrafinowania, powinieneś mieć wszystko, czego potrzebujesz, aby zaimplementować ten proces po stronie serwera, podając projekt OSS i RFC. Nie wiem, czy istnieje konkretna implementacja oprogramowania serwera (PHP, Java, .NET itp.)

Ale w szczególności nie potrzebujesz usługi zewnętrznej, aby sobie z tym poradzić.


3
z drugiej strony korzystanie z już istniejącego, dobrze znanego, łatwego do zdobycia rozwiązania dostępnego na wielu różnych urządzeniach mobilnych jest bardzo korzystne ... (podpowiedź)
simpleuser

26
Masz na myśli SMS-y? Jest powolny, zawodny i kosztowny.
Achraf Almouloudi,

Pisałem na blogu o tym, jak zaimplementować 2fa kompatybilny z Google Authenticator / RFC6238 dla stron internetowych w czystej Javie: asaph.org/2016/04/google-authenticator-2fa-java.html (bezwstydna wtyczka)
Asaph

2
FYI NIST nie zaleca już uwierzytelniania dwuskładnikowego za pomocą wiadomości SMS od sierpnia 2016 r. Nieważne, jaki koszt jest uważany za niepewny.
TheDPQ

57

Algorytm jest udokumentowany w RFC6238 . Trochę tak:

  • Twój serwer daje użytkownikowi sekret do zainstalowania w Google Authenticator. Google robi to jako kod QR udokumentowany tutaj .
  • Google Authenticator generuje 6-cyfrowy kod z SHA1-HMAC czasu uniksowego i tajnego (dużo więcej szczegółów na ten temat w RFC)
  • Serwer zna również czas tajny / unix, aby zweryfikować 6-cyfrowy kod.

Miałem zabawę z implementacją algorytmu w javascript tutaj: http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/


20

Istnieje wiele bibliotek dla PHP (The LAMP Stack)

PHP

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

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

Należy zachować ostrożność podczas implementacji uwierzytelniania dwuskładnikowego, należy upewnić się, że zegary na serwerze i kliencie są zsynchronizowane, że istnieje ochrona przed atakami siłowymi na token oraz że początkowe użyte ziarno jest odpowiednio duże.


Treść była świetna, ale każdy, kto korzysta z pierwszego linku, powinien zaimplementować metody zapobiegania iniekcji SQL, ponieważ istnieje kilka potencjalnych błędów. Spójrz na kwestie poruszone w pierwszym. Drugi link jest doskonały.
Septronic

9

Możesz skorzystać z mojego rozwiązania , zamieszczonego jako odpowiedź na moje pytanie (jest pełny kod Pythona i wyjaśnienie ):

Implementacja Google Authenticator w Pythonie

Myślę, że zaimplementowanie go w PHP lub Perlu jest raczej łatwe. Jeśli masz z tym jakieś problemy, daj mi znać.

Opublikowałem również swój kod na GitHub jako moduł Pythona.


1
Chwilę po fakcie ... Chciałem tylko kontynuować, wspominając, że na CPAN: Auth :: GoogleAuthenticator ( search.cpan.org/dist/Auth-GoogleAuthenticator ) jest moduł Perla .
DavidO



3

Tak, nie potrzebujesz usługi sieciowej, ponieważ aplikacja Google Authenticator nie komunikuje się z serwerem Google, po prostu synchronizuje się z początkowym sekretem, który generuje Twój serwer (wprowadzany do telefonu z kodu QR) w miarę upływu czasu.


2

Nie LAMP, ale jeśli używasz C #, to jest kod, którego używam:

Kod pierwotnie od:

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

Klasa Base32Encoding pochodzi z tej odpowiedzi:

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

Przykładowy program:

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

W przypadku użytkownika C # uruchom tę prostą aplikację konsolową, aby zrozumieć, jak zweryfikować kod jednorazowego tokenu. Zauważ, że najpierw musimy zainstalować bibliotekę Otp.Net z pakietu 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!");
            }
        }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.