web-dev-qa-db-fra.com

Google Authenticator disponible en tant que service public?

Existe-t-il une API publique permettant d’utiliser le Google Authenticator (authentification à deux facteurs) sur des applications Web à exécution automatique (par exemple, une pile LAMP)?

142
ohho

Le projet est open source. Je ne l'ai pas utilisé. Mais il utilise un algorithme documenté (indiqué dans la RFC répertoriée sur la page de projet open source) et les implémentations de l'authentificateur prennent en charge plusieurs comptes.

Le processus actuel est simple. Le code temporel unique est essentiellement un générateur de nombre pseudo aléatoire. Un générateur de nombre aléatoire est une formule qui, une fois donnée, commence à créer un flux de nombres aléatoires. Étant donné une graine, alors que les nombres peuvent être aléatoires les uns par rapport aux autres, la séquence elle-même est déterministe. Ainsi, une fois que votre appareil et le serveur sont "synchronisés", les numéros aléatoires créés par l'appareil, chaque fois que vous appuierez sur le "bouton du numéro suivant", seront identiques, au hasard, au nombre que le serveur attend.

Un système de mot de passe à usage unique sécurisé est plus sophistiqué qu'un générateur de nombres aléatoires, mais le concept est similaire. Il existe également d'autres détails permettant de garder le périphérique et le serveur synchronisés.

Donc, il n'est pas nécessaire que quelqu'un d'autre héberge l'authentification, comme, par exemple, dit OAuth. Au lieu de cela, vous devez implémenter cet algorithme compatible avec les applications fournies par Google pour les appareils mobiles. Ce logiciel est (devrait être) disponible sur le projet open source.

Selon votre niveau de sophistication, vous devriez avoir tout ce dont vous avez besoin pour implémenter le côté serveur de ce processus, à savoir le projet OSS et le RFC. Je ne sais pas s'il existe une implémentation spécifique pour votre logiciel serveur (PHP, Java, .NET, etc.)

Mais, spécifiquement, vous n'avez pas besoin d'un service externe pour gérer cela.

114
Will Hartung

L'algorithme est documenté dans RFC6238 . Va un peu comme ça:

  • votre serveur donne à l'utilisateur un secret à installer dans Google Authenticator. Google le fait comme un code QR documenté ici .
  • Google Authenticator génère un code à 6 chiffres à partir d'un SHA1-HMAC de l'heure Unix et du secret (beaucoup plus de détails à ce sujet dans le RFC)
  • Le serveur connaît également l'heure secrète/unix pour vérifier le code à 6 chiffres.

J'ai eu un jeu implémentant l'algorithme en javascript ici: http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/

51
russau

Il y a une variété de bibliothèques pour PHP (The LAMP Stack)

PHP

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

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

Vous devez faire attention lorsque vous implémentez une authentification à deux facteurs, vous devez vous assurer que vos horloges sur le serveur et le client sont synchronisées, qu'il existe une protection contre les attaques brutales sur le jeton et que la valeur initiale utilisée est suffisamment grande.

21
James

Vous pouvez utiliser ma solution, posté comme réponse à ma question (il y a le code Python complet et explication):

_ { Implémentation de Google Authenticator en Python } _

Je pense que c'est assez facile à implémenter dans PHP ou dans Perl. Si vous avez des problèmes avec cela, s'il vous plaît faites le moi savoir.

J'ai aussi posté mon code sur GitHub en tant que module Python.

9
Tadeck

J'ai trouvé ceci: https://github.com/PHPGangsta/GoogleAuthenticator . Je l'ai testé et fonctionne bien pour moi.

7
user1893983

Theres: https://www.gauthify.com qui l'offre en tant que service

4
NoviceCoding

Pas LAMP mais si vous utilisez C # c'est le code que j'utilise:

Code originaire de:

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

La classe Base32Encoding provient de cette réponse:

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

Exemple de programme:

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

}
3
Ogglas

Oui, aucun service réseau n'est nécessaire, car Google Authenticator app ne communiquera pas avec le serveur Google. Elle restera synchronisée avec le secret initial généré par votre serveur (entrée dans votre téléphone à partir du code QR) pendant que le temps passe.

3
diyism

Pour ceux qui utilisent Laravel, ceci https://github.com/sitepoint-editors/google-laravel-2FA est un moyen agréable de résoudre ce problème.

1
briankip

Pour les utilisateurs C #, exécutez cette application console simple pour comprendre comment vérifier le code de jeton ponctuel. Notez que nous devons d’abord installer la bibliothèque Otp.Net à partir du paquet 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!");
            }
        }
}
0
Minh Nguyen