====== Validate mOTP Token ======
To validate a [[http://motp.sf.net/|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.
* @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;
}
}
?>
===== Exact time =====
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 [[query-time-server|NTP_TIME]] class this way:
$time = NTP_TIME::query();
if ( $time === false ) $time = time();
===== See Also =====
* [[:software:php:dw-motp-auth]]
* [[:software:python:motp-token-generator]]