Google TOTP Two-factor Authentication for PHP

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:

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.

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.

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.