Google Authenticator est-il disponible en tant que service public?

Réponses:

121

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

Le processus réel est simple. Le code temporel unique est, essentiellement, un générateur de nombres pseudo aléatoires. Un générateur de nombres aléatoires est une formule qui, une fois donnée une graine, ou un nombre de départ, continue de 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 nombres aléatoires créés par l'appareil, chaque fois que vous appuyez sur le bouton "numéro suivant", seront les mêmes, aléatoires, 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 pour vous aider à synchroniser l'appareil et le serveur.

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

En fonction de votre sophistication, vous devriez avoir tout ce dont vous avez besoin pour implémenter le côté serveur de ce processus, donner 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, en particulier, vous n'avez pas besoin d'un service hors site pour gérer cela.

Will Hartung
la source
3
d'autre part, utiliser une solution déjà existante, bien connue et facile à obtenir disponible sur de nombreux appareils mobiles différents est d'un grand avantage ... (indice)
simpleuser
26
Vous voulez dire SMS? C'est lent, peu fiable et coûteux.
Achraf Almouloudi
J'ai blogué sur la façon d'implémenter Google Authenticator / RFC6238 compatible 2fa pour les sites Web en java pur: asaph.org/2016/04/google-authenticator-2fa-java.html (plug sans vergogne)
Asaph
2
FYI NIST ne recommande plus l'authentification à deux facteurs à l'aide de SMS à partir d'août 2016. Peu importe le coût, il est considéré comme non sécurisé.
TheDPQ
57

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 sous forme de 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 la RFC)
  • Le serveur connaît également l'heure secrète / unix pour vérifier le code à 6 chiffres.

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

russau
la source
20

Il existe 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 être prudent lors de la mise en œuvre de l'authentification à deux facteurs, vous devez vous assurer que vos horloges sur le serveur et le client sont synchronisées, qu'une protection est en place contre les attaques par force brute sur le jeton et que la valeur de départ initiale utilisée est suffisamment grande.

James
la source
Le contenu était excellent, mais toute personne utilisant le premier lien devrait implémenter les méthodes de prévention des injections SQL, car il y a des failles potentielles. Regardez les problèmes soulevés pour le premier. Le deuxième lien est parfait.
Septronic
9

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

Implémentation de Google Authenticator en Python

Il est plutôt facile de l'implémenter en PHP ou Perl, je pense. Si vous rencontrez des problèmes, merci de me le faire savoir.

J'ai également publié mon code sur GitHub en tant que module Python.

Tadeck
la source
1
Un peu après le fait ... Je voulais juste poursuivre en mentionnant qu'il existe un module Perl sur CPAN: Auth :: GoogleAuthenticator ( search.cpan.org/dist/Auth-GoogleAuthenticator ).
DavidO
3

Oui, pas besoin de service réseau, car l'application Google Authenticator ne communiquera pas avec le serveur Google, elle se synchronise simplement avec le secret initial que votre serveur génère (entrée dans votre téléphone à partir du code QR) pendant que le temps passe.

diyisme
la source
2

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

}

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

}
Ogglas
la source
-1

Pour l'utilisateur C #, exécutez cette application console simple pour comprendre comment vérifier le code de jeton à usage unique. Notez que nous devons d'abord installer la bibliothèque Otp.Net à partir du package 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!");
            }
        }
}
Minh Nguyen
la source