At the beginning of the year Google released 2 Factor Authentication (2FA) for G-Mail providing an application for Android, IPhone and Blackberry called Google Authenticator to generate one time login tokens. This post will show how to implement Google 2FA to protect web applications from stolen credentials.

Google Authenticator is based on RFC 4226 – a Time based One Time Password (TOTP) which is initialised using a 16 digit base 32 (RFC 4648) encoded seed value. Initial seeds used for the TOTP can be entered into the Google Authenticator via a camera using QR codes or via the keyboard. Google has also provided a PAM module allowing users to integrate 2FA for sshd.
A module can be written to support the Google TOTP in any language – the only caveat with writing a library for PHP is a lack of an RFC 4648 compliant base 32 decoding function. A base 32 function is needed to decode the initial seed. This is probably the most tricky part of implementing Google’s 2FA. The following function can be used:
function base32_decode($b32) {
$lut = array("A" => 0, "B" => 1,
"C" => 2, "D" => 3,
"E" => 4, "F" => 5,
"G" => 6, "H" => 7,
"I" => 8, "J" => 9,
"K" => 10, "L" => 11,
"M" => 12, "N" => 13,
"O" => 14, "P" => 15,
"Q" => 16, "R" => 17,
"S" => 18, "T" => 19,
"U" => 20, "V" => 21,
"W" => 22, "X" => 23,
"Y" => 24, "Z" => 25,
"2" => 26, "3" => 27,
"4" => 28, "5" => 29,
"6" => 30, "7" => 31
);
$b32 = strtoupper($b32);
$l = strlen($b32);
$n = 0;
$j = 0;
$binary = "";
for ($i = 0; $i < $l; $i++) {
$n = $n << 5;
$n = $n + $lut[$b32[$i]];
$j = $j + 5;
if ($j >= 8) {
$j = $j - 8;
$binary .= chr(($n & (0xFF << $j)) >> $j);
}
}
return $binary;
}
This binary seed value will be used in a SHA1 hash along with the current Unix time-stamp to generate one time tokens. The Unix time-stamp is divided by 30 so that the one time password changes every 30 seconds.
function get_timestamp() {
return floor(microtime(true)/30);
}
Sadly you cant just pass the number from get_timestamp straight into the sha1 function. The time-stamp first needs to be reduced into a binary string of 8 bytes. Since pack doesn’t support 64bit integers we use two unsigned 32 bit integers to make up the binary form.
$binary_timestamp = pack('N*', 0) . pack('N*', $timestamp);
Once you have the binary seed and the binary timestamp you have to pass them into the “hash_mhac” function. This gives you a 20 byte sha1 string.
$hash = hash_hmac ('sha1', $binary_timestamp, $binary_key, true);
This hash is then processed in accordance with RFC 4226 to obtain the one time password.
$offset = ord($hash[19]) & 0xf;
$OTP = (
((ord($hash[$offset+0]) & 0x7f) << 24 ) |
((ord($hash[$offset+1]) & 0xff) << 16 ) |
((ord($hash[$offset+2]) & 0xff) << 8 ) |
(ord($hash[$offset+3]) & 0xff)
) % pow(10, 6);
Now $OTP should contain your one time password. There are however still a couple of small issues to overcome if you want to use this within an application:
-
Your client and server clocks may not be in sync – this could means that when you come to check your token generated by the user that it will fail. To combat this a you can either stipulate that the client and server clocks must be in perfect sync or you need to create a function which checks the tokens against those +/- 2 minutes of the current server time. This will allow your client and server to be up to 2 minutes out but obviously increases the chance that an attacker could correctly guess a one time token.
-
If there is no upper limit on the number of attempts a user can make at guessing a token it may be possible to brute-force the one-time token.
-
If the seed is too small and an attacker can intercept a few tokens it may be possible to brute-force the seed value allowing the attacker to generate new one-time tokens. For this reason Google enforces a minimum seed length of 16 characters or 80-bits.
-
If a token is not marked as invalid as soon as it has been used an attacker who has intercepted the token may be able to quickly replay it to obtain access.

Seed value 'PEHMPSDNLXIOG65U'
A class for PHP that implements Google TOTP can be downloaded here. Its missing protection against brute-force attacks but otherwise fully functional.
You can check if its working by installing the Google Authenticator application and scanning the QR code to the right – codes generated by the application should match codes generated by the class. The function Google2FA::verify_key should be used to validate the users one time token as it allows the clients clock to drift either side of the server time by 2 minutes.
Custom QR codes can be generated using the Google QR generator at https://www.google.com/chart?chs=200×200&chld=M|0&cht=qr&chl=otpauth://totp/idontplaydarts?secret=SECRETVALUEHERE
This doesn’t work. I believe there are issues from the timestamp but no matter what I do the one-time password does not match my Google Authenticator application.
You need to ensure both are clocks are in Sync (the Google App and the server time) – they need to be within 30 seconds of each other, if they are not then the generated keys wont match. Try this, scan in the QR code in this post using the Google App, then go to http://xqi.cc/google.php – as long as the time on your device is correct it should work. Just tested it on my Android with a couple of different keys and it seems fine.
Thank you! I really appreciate the work you did and the sync issue was definitely why I kept getting a invalid verification of the key. This definitely helped a lot.
Very useful information , I was googling 2 days to understand how to do this on my website. Now I got it :) ! , I have 2 questions thou:
1. How you generate the SEED VALUE – in upper example: PEHMPSDNLXIOG65U , I noticed if I choose something other – google authenticator gives me an error , saying – this is not working code. Sometimes it works , sometimes is not. I even tried several QR generators , same problem.
2. Second question is – can you offer cheap and working Hardware Token compatible with this example :)
Best Regards!
The seed for Google auth needs to be a 16 char base32 encoded string – if not you might get an error. For cheap hardware tokens you i’m not sure, there are some pretty cheap Android devices out there tho :)
Hi, thanks for this, it works perfectly.
BTW, the Google QR generator URL is 404.
I noticed this too, but turns out the URL has a multiplication symbol (×) instead of the letter x. Changing that worked fine for me, though I suppose Alfabravo’s suggestion might be more future-proof
Hey Phil,
Your article was extremely helpful and your class is very well written,
I’ve changed the key to a new Base32 encoded 16-char string, created a qr code for otpauth://totp/MyProduct?secret=MYSECRET, then scanned it with Google Authenticator, and it scanned it without a problem,
The only issue im having is the keys that Google Authenticator generates never matches the one generated by Google2FA::oath_hotp … Could this be a matter of the timestamp being generately differently on my iPhone and the PHP server?
Would really appreciate your insights on this (even though i will continue digging into Google results for now ;) )
Thanks in advance,
Shai
I just noticed your link (http://xqi.cc/google.php) , and my server has a difference of 12 (seconds?) in the timestamp , i just did -12 in the return of get_timestamp and now everything works.
Is there a place I could check whats the current timestamp on the google servers? I dont think setting this difference manually on every server is a good idea :P
Your clock would be off by 12*30 (the time between each key regeneration) – so 6 minutes – best thing to do is sync your server using NTP (assuming the time on the Google Servers are correct)
@Curio: use
http://chart.googleapis.com/chart?chs=… so the request will always succeed.http://www.google.com/chartlooks like filters requests or handles load in a funny way.@Phil: Also, it would be nice to specify that, if you decide to use SHA256 or SHA512 instead or SHA1, it must be told to the mobile app as a parameter (it is “algorithm=SHA256″, *AFAIK*).
Good post. Thanks
“Custom QR codes can be generated using the Google QR generator at https://www.google.com/chart?chs=200×200&chld=M|0&cht=qr&chl=otpauth://totp/idontplaydarts?secret=SECRETVALUEHERE”
Link is no longer working. Also, is SECRETVALUEHERE just a random string? Should that be a random string for each user in an application? Thanks!
WP seems to have rendered the ‘x’ as a multiplication symbol rather than the letter x – the secret should be a base32 encoded random value (at least 128bits long) try this link.
thanks, it works now!
Thanks for this write up.
Coming to this late I’m afraid.
If the application being protected uses an image for the QR code, or an external service to generate the QR code for the seed value (as suggested above) isn’t the user vulnerable to having the seed discovered in their history or browser cache?
You wouldn’t store passwords in plain text on your machine would you? This is what seems to be at risk of happening here?
yeah its a bad idea to let a 3rd party know the seed. The seed is supposed to be kept secret and not stored on the same device as the user uses to login – e.g the phone. Storing the seed on your PC would defeat the object of 2FA, if your generating the QR code on your own server you need to ensure its delivered over SSL/TLS, has the correct caching headers set and doesn’t appear in the URI :)
I recently setup TOTP on gmail, by scanning the QR code on the screen of my PC. I later visited chrome://cache and searched for ‘chart’. This line was displayed;
https://www.google.com/chart?chs=166×166&chld=L|0&cht=qr&chl=otpauth://totp/mtsnape%40gmail.com%3Fsecret=i6o56vvvvvvvibay
Which is enough for someone to setup a duplicate authenticator (obviously not the real code shown above).
What I’m unsure about, if a site is accessed via https: (as per the above) are the GET parameters visible in the requested URL or do they occur over the encrypted channel?
GET parameters are encrypted if SSL is used
Mark, just use readfile() and ‘read’ the image from google, then display it directly to the user.
That way there is no history of the user browser accessing the image from google.
Wow, I implemented this in our system in about half an hour! Can’t believe how easy this is. Thanks for the great library and description.
thanks for the good examples and library, it’s very clear and interesting!
M.
Thank you for this very informative article.
You mention, that used tokens shall be marked as invalid. My question is: How long shall a token be marked as invalid. In case the same code might get generated twice and is marked as invalid because it was some time ago.
The token should be marked as invalid so it cant be used in a replay attack. This means it would need to be marked as invalid for the same amount of time as the size of the time drift/window you are checking.
Hi,
I have implemented the Google Authenticator on my locale serve It is generating the QR code but generated QR-code not recognised by my Google Authenticator app.
This is giving me an error while I am scanning that “Key not Recognised”
Am I miss something? Can you please help me
The QR code needs to be in the right format e.g the string:
“PEHMPSDNLXIOG65U”