Google Authenticator доступен как общедоступная служба?

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

Ответ 1

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

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

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

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

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

Но, в частности, вам не нужен внешний сервис, чтобы справиться с этим.

Ответ 2

Алгоритм описан в 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/

Ответ 3

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

PHP

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

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

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

Ответ 4

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

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

Это довольно просто реализовать на PHP или Perl, я думаю. Если у вас есть какие-либо проблемы с этим, сообщите мне.

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

Ответ 7

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

Ответ 8

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

Код изначально:

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

Класс Base32Encoding из этого ответа:

fooobar.com/questions/46553/...

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

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

}

Ответ 10

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