Google Authenticator als öffentlicher Dienst verfügbar?

Antworten:

121

Das Projekt ist Open Source. Ich habe es nicht benutzt. Es wird jedoch ein dokumentierter Algorithmus verwendet (siehe RFC auf der Open Source-Projektseite), und die Authentifizierungsimplementierungen unterstützen mehrere Konten.

Der eigentliche Vorgang ist unkompliziert. Der Einmalcode ist im Wesentlichen ein Pseudozufallszahlengenerator. Ein Zufallszahlengenerator ist eine Formel, die nach Angabe eines Startwerts oder einer Startnummer weiterhin einen Strom von Zufallszahlen erzeugt. Bei einem gegebenen Startwert ist die Sequenz selbst deterministisch, obwohl die Zahlen zufällig zueinander sein können. Sobald Sie also Ihr Gerät und den Server "synchron" haben, sind die Zufallszahlen, die das Gerät erstellt, jedes Mal, wenn Sie auf die Schaltfläche "Nächste Nummer" klicken, dieselben Zufallszahlen, die der Server erwartet.

Ein sicheres Einmalkennwortsystem ist ausgefeilter als ein Zufallszahlengenerator, aber das Konzept ist ähnlich. Es gibt auch andere Details, um das Gerät und den Server synchron zu halten.

Es ist also nicht erforderlich, dass jemand anderes die Authentifizierung hostet, z. B. OAuth. Stattdessen müssen Sie diesen Algorithmus implementieren, der mit den Apps kompatibel ist, die Google für die Mobilgeräte bereitstellt. Diese Software ist (sollte) im Open Source-Projekt verfügbar.

Abhängig von Ihrer Raffinesse sollten Sie über alles verfügen, was Sie zur Implementierung der Serverseite dieses Prozesses benötigen. Geben Sie das OSS-Projekt und den RFC an. Ich weiß nicht, ob es eine bestimmte Implementierung für Ihre Serversoftware gibt (PHP, Java, .NET usw.).

Insbesondere benötigen Sie jedoch keinen externen Service, um dies zu erledigen.

Will Hartung
quelle
3
Auf der anderen Seite ist die Verwendung einer bereits vorhandenen, bekannten und leicht zu
beschaffenden
26
Du meinst SMS? Es ist langsam, unzuverlässig und teuer.
Achraf Almouloudi
Ich habe darüber gebloggt, wie Google Authenticator / RFC6238-kompatibles 2fa für Websites in reinem Java implementiert werden kann : asaph.org/2016/04/google-authenticator-2fa-java.html (schamloser Plug)
Asaph
2
FYI NIST empfiehlt ab August 2016 keine Zwei-Faktor-Authentifizierung mehr per SMS. Bedenken Sie jedoch, dass die Kosten als unsicher gelten.
TheDPQ
57

Der Algorithmus ist in RFC6238 dokumentiert . Geht ein bisschen so:

  • Ihr Server gibt dem Nutzer ein Geheimnis für die Installation in Google Authenticator. Google tut dies als QR-Code, der hier dokumentiert ist .
  • Google Authenticator generiert einen 6-stelligen Code aus einem SHA1-HMAC der Unix-Zeit und des Geheimnisses (mehr Details dazu im RFC).
  • Der Server kennt auch die geheime / Unix-Zeit, um den 6-stelligen Code zu überprüfen.

Ich hatte ein Spiel mit der Implementierung des Algorithmus in Javascript hier: http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/

russau
quelle
20

Es gibt eine Vielzahl von Bibliotheken für PHP (The LAMP Stack)

PHP

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

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

Sie sollten bei der Implementierung der Zwei-Faktor-Authentifizierung vorsichtig sein. Sie müssen sicherstellen, dass Ihre Uhren auf dem Server und dem Client synchronisiert sind, dass ein Schutz gegen Brute-Force-Angriffe auf das Token vorhanden ist und dass der ursprünglich verwendete Startwert angemessen groß ist.

James
quelle
Der Inhalt war großartig, aber jeder, der den ersten Link verwendet, sollte die Methoden zur Verhinderung der SQL-Injektion implementieren, da es einige potenzielle Fehler gibt. Schauen Sie sich die Themen an, die für das erste aufgeworfen wurden. Der zweite Link ist perfekt.
Septronic
9

Sie können meine Lösung verwenden , die als Antwort auf meine Frage veröffentlicht wurde (es gibt vollständigen Python-Code und eine Erklärung ):

Implementierung von Google Authenticator in Python

Es ist ziemlich einfach, es in PHP oder Perl zu implementieren, denke ich. Wenn Sie Probleme damit haben, lassen Sie es mich bitte wissen.

Ich habe meinen Code auch als Python-Modul auf GitHub gepostet .

Tadeck
quelle
1
Kurz danach ... Ich wollte nur erwähnen, dass es auf CPAN ein Perl-Modul gibt: Auth :: GoogleAuthenticator ( search.cpan.org/dist/Auth-GoogleAuthenticator ).
DavidO
3

Ja, Sie benötigen keinen Netzwerkdienst, da die Google Authenticator-App nicht mit dem Google-Server kommuniziert. Sie wird nur mit dem ursprünglichen Geheimnis synchronisiert, das Ihr Server im Laufe der Zeit generiert (Eingabe über QR-Code in Ihr Telefon).

DIYismus
quelle
2

Nicht LAMP, aber wenn Sie C # verwenden, ist dies der Code, den ich verwende:

Code ursprünglich von:

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

Die Base32Encoding-Klasse stammt aus dieser Antwort:

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

Beispielprogramm:

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

Führen Sie für C # -Benutzer diese einfache Konsolen-App aus, um zu verstehen, wie der einmalige Token-Code überprüft wird. Beachten Sie, dass wir zuerst die Bibliothek Otp.Net aus dem Nuget-Paket installieren müssen .

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
quelle