Google Authenticator доступен как общедоступный сервис? - PullRequest
143 голосов
/ 23 февраля 2011

Существует ли общедоступный API для использования Google Authenticator (двухфакторная аутентификация) в автономных (например, в стеке LAMP) веб-приложениях?

Ответы [ 10 ]

116 голосов
/ 23 февраля 2011

проект с открытым исходным кодом. Я не использовал это. Но он использует документированный алгоритм (отмеченный в RFC, указанном на странице проекта с открытым исходным кодом), и реализации аутентификатора поддерживают несколько учетных записей.

Фактический процесс прост. Одноразовый код, по сути, является генератором псевдослучайных чисел. Генератор случайных чисел - это формула, которая после получения начального или начального числа продолжает создавать поток случайных чисел. При заданном числе, хотя числа могут быть случайными друг для друга, сама последовательность является детерминированной. Таким образом, когда ваше устройство и сервер синхронизированы, случайные числа, которые создает устройство, каждый раз, когда вы нажимаете «кнопку следующего номера», будут такими же, случайными, числами, которые ожидает сервер.

Безопасная система одноразовых паролей более сложна, чем генератор случайных чисел, но концепция аналогична. Есть и другие сведения, которые помогут синхронизировать устройство и сервер.

Так что нет необходимости, чтобы кто-то еще размещал аутентификацию, как, например, OAuth. Вместо этого вам нужно реализовать этот алгоритм, который совместим с приложениями, которые Google предоставляет для мобильных устройств. Это программное обеспечение доступно (должно быть) в проекте с открытым исходным кодом.

В зависимости от вашей сложности, у вас должно быть все необходимое для реализации серверной части этого процесса, включая проект OSS и RFC. Я не знаю, есть ли конкретная реализация для вашего серверного программного обеспечения (PHP, Java, .NET и т. Д.)

Но, в частности, вам не нужен внешний сервис для этого.

55 голосов
/ 03 ноября 2011

Алгоритм задокументирован в RFC6238 . Идет примерно так:

  • ваш сервер дает пользователю секрет для установки в Google Authenticator. Google делает это в виде задокументированного QR-кода здесь .
  • Google Authenticator генерирует 6-значный код из SHA1-HMAC времени и секрета Unix (более подробно об этом в RFC)
  • Сервер также знает время секретности / unix для проверки 6-значного кода.

У меня была игра, реализующая алгоритм в javascript здесь: http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/

21 голосов
/ 29 июля 2011

Существует множество библиотек для PHP (стек LAMP)

PHP

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

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

Вы должны быть осторожны при реализации двухфакторной аутентификации, вам нужно убедиться, что ваши часы на сервере и клиенте синхронизированы, что есть защита от атак методом перебора на токене и что исходное начальное число используется соответствующим образом.большой.

10 голосов
/ 19 декабря 2011

Вы можете использовать мое решение , опубликованное как ответ на мой вопрос (есть полный код Python и объяснение ):

Реализация Google Authenticator в Python

Я думаю, это довольно легко реализовать на PHP или Perl. Если у вас есть какие-либо проблемы с этим, пожалуйста, дайте мне знать.

Я также разместил свой код на GitHub как модуль Python.

7 голосов
/ 29 января 2013

Я нашел это: https://github.com/PHPGangsta/GoogleAuthenticator. Я проверил это и прекрасно работает для меня.

4 голосов
/ 16 января 2013

Theres: https://www.gauthify.com, которая предлагает его в качестве услуги

3 голосов
/ 27 сентября 2017

Не LAMP, но если вы используете C #, это код, который я использую:

Код от:

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

Класс Base32Encoding отэтот ответ:

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

Пример программы:

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

Всего:

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

}

Base32 Кодировка:

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

}
3 голосов
/ 17 мая 2013

Да, сетевая служба не нужна, поскольку приложение Google Authenticator не связывается с сервером Google, оно просто синхронизируется с исходным секретом, который ваш сервер генерирует (вводит в ваш телефон с помощью QR-кода), пока проходит время.

1 голос
/ 12 апреля 2017

Для тех, кто использует Laravel, этот https://github.com/sitepoint-editors/google-laravel-2FA является хорошим способом решения этой проблемы.

0 голосов
/ 16 января 2019

Для пользователя C #: запустите это простое консольное приложение, чтобы понять, как проверить однократный код токена.Обратите внимание, что сначала нам нужно установить библиотеку Otp.Net из пакета 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!");
            }
        }
}
...