Google Authenticator доступен как общедоступный сервис?

Ответы:

121

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

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

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

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

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

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

Уилл Хартунг
источник
3
с другой стороны, использование уже существующего, общеизвестного, легкодоступного решения, доступного на многих мобильных устройствах, очень полезно ... (подсказка)
simpleuser
26
Вы имеете в виду смс? Это медленно, ненадежно и дорого.
Ахраф Альмулуди
Я написал в блоге о том, как реализовать Google Authenticator / RFC6238, совместимый с 2fa для веб-сайтов на чистом Java: asaph.org/2016/04/google-authenticator-2fa-java.html (бесстыдный плагин)
Asaph
2
FYI NIST больше не рекомендует двухфакторную аутентификацию с использованием SMS с августа 2016 года. Не берите в голову стоимость, которую она считает небезопасной.
TheDPQ
57

Алгоритм задокументирован в 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/

russau
источник
20

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

PHP

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

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

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

Джеймс
источник
Содержимое было отличным, но любой, кто использует первую ссылку, должен реализовать методы предотвращения SQL-инъекций, поскольку есть некоторые потенциальные недостатки. Посмотрите на вопросы, поднятые для первого. Вторая ссылка идеальна.
Септроник
9

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

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

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

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

Tadeck
источник
1
Немного после этого ... Я просто хотел продолжить, упомянув, что на CPAN есть модуль Perl: Auth :: GoogleAuthenticator ( search.cpan.org/dist/Auth-GoogleAuthenticator ).
DavidO
6

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

user1893983
источник
1
Я думаю, вы бы предпочли github.com/RobThree/TwoFactorAuth . Она основана на приведенной выше библиотеке, но является ее огромным улучшением с большим количеством функций и более четкой документацией.
Ema4rl
3

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

diyism
источник
2

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

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
источник
-1

Для пользователя 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!");
            }
        }
}
Мин Нгуен
источник