To validate a Mobile OTP Token, you can use this class. It keeps a list of used tokens in a serialized file so that a token can only be used once.
<?php /** * Mobile-OTP implementation * http://motp.sourceforge.net/ * This class has been rewritten after the model of Ralf Neumann and K9Barry's PHP script. * @author Markus Birth <markus@birth-online.de> * @todo Implement user(=$initsecret)-lockout after 8 failed attempts */ class mOTP { const CHECKFILE = 'motp.used.dat'; const GRACEPERIOD = 3; // ±3 minutes const LOCKPERIOD = 10; // lock used token for 10 minutes protected $offset = 0; // time offset in 10s of seconds for inexact token generators protected $used = array(); // holds used tokens function __construct( $offset = 0 ) { if ( file_exists( self::CHECKFILE ) ) $this->used = unserialize( file_get_contents( self::CHECKFILE ) ); } function __destruct() { if ( is_writable('.') ) file_put_contents( self::CHECKFILE, serialize( $this->used ) ); } function checkOTP( $pin, $otp, $initsecret ) { $time = time(); // old: gmdate('U'); if ( isset( $this->used[$otp] ) && $this->used[$otp]>=$time ) return false; // has been used before else unset( $this->used[$otp] ); // cleanup $otime = floor($time / 10) + $this->offset; $grace = self::GRACEPERIOD * 6; // grace period in 10s of seconds (default: ±18) for ($i=$otime-$grace; $i<=$otime+$grace; $i++) { $md5 = substr( md5( $i . $initsecret . $pin ), 0, 6); if ($otp == $md5) { $this->used[$otp] = $time + self::LOCKPERIOD*60; return true; } } return false; } } ?>
If you use this class on a server with a bad clock, you can replace the line:
$time = time(); // old: gmdate('U');
to use the NTP_TIME class this way:
$time = NTP_TIME::query(); if ( $time === false ) $time = time();