Reputation: 51941
Is there public API for using the Google Authenticator (two factor authentication) on self-running (e.g. LAMP stack) web apps?
Upvotes: 155
Views: 125451
Reputation: 988
Google Authenticator uses an open-source algorithm, which you can check here: RFC 6238.
The following steps show an overview (over-simplified) of what goes on when you use Google Authenticator (TOTP) for 2FA
Implementing the parts of the above steps that should be in your app depends on you and the technology you are using to build your app.
If you wish to show a fancy QR code on your app that users can scan and share the secret with Google Authenticator, it's also up to you to implement that. However, avoid using online QR generators or libraries that you can not verify their safety as it is important to truly keep the secret a secret.
Another option would be to use an authentication service like Authgear that offers TOTP solution and SDKs that you can use in your code.
Upvotes: 0
Reputation: 12362
Beeceptor's 2FA Code Generator - Beeceptor provides an easy-to-use API where you can upload the screenshot with QR code, and get the next 2FA-TOTP code. It also gives you an ID that you can use to query future codes. These APIs are free for the community, with some rate limits.
A) Sample API to configure using QR code or screenshot upload:
curl --location 'https://tools.beeceptor.com/2FA/TOTP/' \
--form 'qrcode=@"qrcode.png"'
B) Sample query next code API:
GET https://tools.beeceptor.com/2FA/TOTP/a2cb7165-55a5-44ce-9f18-a4ff093bcd21
C) Sample response API response:
{
"id": "a2cb7165-55a5-44ce-9f18-a4ff093bcd21",
"otp": "968027",
"expiresAt": "2024-05-21T10:32:30.000Z"
}
Shameless plug: I am the author of Beeceptor, and we have been building tools to simplify dev and QA processes. This API is free for the community, with some rate limits.
Upvotes: 0
Reputation: 2201
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!");
}
}
}
Upvotes: -1
Reputation: 69998
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://stackoverflow.com/a/7135008/3850405
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");
}
}
Upvotes: 5
Reputation: 2640
For those using Laravel, this https://github.com/sitepoint-editors/google-laravel-2FA is a nice way to solve this problem.
Upvotes: 1
Reputation: 9098
The algorithm is documented in RFC6238. Goes a bit like this:
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/
Upvotes: 62
Reputation: 229
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.
Upvotes: 22
Reputation: 12935
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.
Upvotes: 3
Reputation: 121
I found this: https://github.com/PHPGangsta/GoogleAuthenticator. I tested it and works fine for me.
Upvotes: 6
Reputation: 137370
You can use my solution, posted as the answer to my question (there is full Python code and explanation):
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.
Upvotes: 9
Reputation: 118661
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.
Upvotes: 123