Google Authenticator available as a public service?
Asked Answered
G

10

155

Is there public API for using the Google Authenticator (two factor authentication) on self-running (e.g. LAMP stack) web apps?

Ganymede answered 23/2, 2011 at 4:28 Comment(0)
R
123

The project is open source. I have not used it. But it's using a documented algorithm (noted in the RFC listed on the open source project page), and the authenticator implementations support multiple accounts.

The actual process is straightforward. The one time code is, essentially, a pseudo random number generator. A random number generator is a formula that once given a seed, or starting number, continues to create a stream of random numbers. Given a seed, while the numbers may be random to each other, the sequence itself is deterministic. So, once you have your device and the server "in sync" then the random numbers that the device creates, each time you hit the "next number button", will be the same, random, numbers the server expects.

A secure one time password system is more sophisticated than a random number generator, but the concept is similar. There are also other details to help keep the device and server in sync.

So, there's no need for someone else to host the authentication, like, say OAuth. Instead you need to implement that algorithm that is compatible with the apps that Google provides for the mobile devices. That software is (should be) available on the open source project.

Depending on your sophistication, you should have all you need to implement the server side of this process give the OSS project and the RFC. I do not know if there is a specific implementation for your server software (PHP, Java, .NET, etc.)

But, specifically, you don't need an offsite service to handle this.

Repetitive answered 23/2, 2011 at 4:44 Comment(4)
on the other hand, using an already existing, well known, easy to get solution available on many different mobile devices is of great benefit...(hint hint)Effectuate
You mean SMS ? It is slow, unreliable and costly.Ogden
I blogged about how to implement Google Authenticator/RFC6238 compatible 2fa for websites in pure java: asaph.org/2016/04/google-authenticator-2fa-java.html (shameless plug)Macri
FYI NIST is No Longer Recommending Two-Factor Authentication Using SMS as of August 2016. Nevermind the cost its considered insecure.Alvarado
N
62

The algorithm is documented in RFC6238. Goes a bit like this:

  • your server gives the user a secret to install into Google Authenticator. Google do this as a QR code documented here.
  • Google Authenticator generates a 6 digit code by from a SHA1-HMAC of the Unix time and the secret (lots more detail on this in the RFC)
  • The server also knows the secret / unix time to verify the 6-digit code.

I've had a play implementing the algorithm in javascript here: http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/

Northumberland answered 3/11, 2011 at 5:43 Comment(0)
E
22

There are a variety of libraries for PHP (The LAMP Stack)

PHP

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

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

You should be careful when implementing two-factor auth, you need to ensure your clocks on the server and client are synchronized, that there is protection in place against brute-force attacks on the token and that the initial seed used is suitably large.

Episiotomy answered 29/7, 2011 at 3:49 Comment(1)
The content was great, but anyone using the first link should implement the SQL injection prevention methods, as there's some potential flaws. Look at the issues raised for the first one. Second link is perfect.Slinky
C
9

You can use my solution, posted as the answer to my question (there is full Python code and explanation):

Google Authenticator implementation in Python

It is rather easy to implement it in PHP or Perl, I think. If you have any problems with this, please let me know.

I have also posted my code on GitHub as Python module.

Ctesiphon answered 19/12, 2011 at 11:24 Comment(1)
A little after the fact... I just wanted to follow up by mentioning that there is a Perl module on CPAN: Auth::GoogleAuthenticator ( search.cpan.org/dist/Auth-GoogleAuthenticator ).Xylotomous
G
6

I found this: https://github.com/PHPGangsta/GoogleAuthenticator. I tested it and works fine for me.

Guenther answered 29/1, 2013 at 13:12 Comment(1)
I think you would prefer github.com/RobThree/TwoFactorAuth. It is based on the above library but is a huge improvement on it with more features and clearer documentation.Boer
A
5

Not LAMP but if you use C# this is the code I use:

Code originally from:

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

The Base32Encoding class is from this answer:

https://mcmap.net/q/159683/-base32-decoding

Example program:

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

}
Adeno answered 27/9, 2017 at 8:36 Comment(0)
H
3

Theres: https://www.gauthify.com that offers it as a service

Hortense answered 16/1, 2013 at 0:59 Comment(0)
D
3

Yes, need no network service, because Google Authenticator app won't communicate with the google server, it just keeps synced with the initital secret that your server generate(input into your phone from QR code) while the time pass.

Distaff answered 17/5, 2013 at 16:59 Comment(0)
U
1

For those using Laravel, this https://github.com/sitepoint-editors/google-laravel-2FA is a nice way to solve this problem.

Un answered 12/4, 2017 at 16:15 Comment(0)
L
-1

For C# user, run this simple Console App to understand how to verify the one time token code. Note that we need to install library Otp.Net from Nuget package first.

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!");
            }
        }
}
Linguini answered 16/1, 2019 at 12:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.