| 1 |
<?php |
| 2 |
if (!defined('ABSPATH')) exit; |
| 3 |
if (!class_exists('WPRWP2FATimeOTP')) : |
| 4 |
|
| 5 |
class WPRWP2FATimeOTP |
| 6 |
{ |
| 7 |
private static $code_length = 6; |
| 8 |
|
| 9 |
private static function getCode($secret_key, $time_slice) { |
| 10 |
$time = chr(0).chr(0).chr(0).chr(0).pack('N*', $time_slice); |
| 11 |
$hm = hash_hmac('SHA1', $time, $secret_key, true); |
| 12 |
$offset = ord(substr($hm, -1)) & 0x0F; |
| 13 |
$hashpart = substr($hm, $offset, 4); |
| 14 |
|
| 15 |
$value = unpack('N', $hashpart); |
| 16 |
$value = $value[1]; |
| 17 |
$value = $value & 0x7FFFFFFF; |
| 18 |
|
| 19 |
$modulo = pow(10, self::$code_length); |
| 20 |
|
| 21 |
return str_pad($value % $modulo, self::$code_length, '0', STR_PAD_LEFT); |
| 22 |
} |
| 23 |
|
| 24 |
public static function verifyCode($secret, $code, $discrepancy = 1, $current_time_slice = null) { |
| 25 |
return self::matchingSlice($secret, $code, $discrepancy, $current_time_slice) !== null; |
| 26 |
} |
| 27 |
|
| 28 |
# Returns the time slice the code belongs to, so the caller can refuse one it |
| 29 |
# has already accepted. Null when nothing matches. |
| 30 |
public static function matchingSlice($secret, $code, $discrepancy = 1, $current_time_slice = null) { |
| 31 |
if ($current_time_slice === null) { |
| 32 |
$current_time_slice = intdiv(time(), 30); |
| 33 |
} |
| 34 |
|
| 35 |
if (strlen($code) != self::$code_length) { |
| 36 |
return null; |
| 37 |
} |
| 38 |
|
| 39 |
# A secret that will not decode must never fall through to hash_hmac(), |
| 40 |
# which would take the false as an empty key and produce a code anyone |
| 41 |
# can compute from the clock alone. |
| 42 |
$secret_key = WPRWP2FAUtils::base32Decode($secret); |
| 43 |
if (!is_string($secret_key) || $secret_key === '') { |
| 44 |
return null; |
| 45 |
} |
| 46 |
|
| 47 |
for ($i = -$discrepancy; $i <= $discrepancy; ++$i) { |
| 48 |
$slice = intval($current_time_slice) + $i; |
| 49 |
if (hash_equals(self::getCode($secret_key, $slice), $code)) { |
| 50 |
return $slice; |
| 51 |
} |
| 52 |
} |
| 53 |
|
| 54 |
return null; |
| 55 |
} |
| 56 |
} |
| 57 |
endif; |