mirror of
https://github.com/the-djmaze/snappymail.git
synced 2024-11-15 04:04:50 +08:00
103 lines
2.2 KiB
PHP
103 lines
2.2 KiB
PHP
|
<?php
|
||
|
|
||
|
class TwoFactorAuthTotp implements TwoFactorAuthInterface
|
||
|
{
|
||
|
public function Label() : string
|
||
|
{
|
||
|
return 'Two Factor Authenticator Code';
|
||
|
}
|
||
|
|
||
|
public function VerifyCode(string $sSecret, string $sCode) : bool
|
||
|
{
|
||
|
$key = static::Base32Decode($sSecret);
|
||
|
$algo = 'SHA1'; // Google Authenticator doesn't support SHA256
|
||
|
$digits = 6; // Google Authenticator doesn't support 8
|
||
|
$modulo = \pow(10, $digits);
|
||
|
$timeSlice = \floor(\time() / 30);
|
||
|
$discrepancy = 1;
|
||
|
for ($i = -$discrepancy; $i <= $discrepancy; ++$i) {
|
||
|
// Pack time into binary string
|
||
|
$counter = \str_pad(\pack('N*', $timeSlice + $i), 8, "\x00", STR_PAD_LEFT);
|
||
|
// Hash it with users secret key
|
||
|
$hm = \hash_hmac($algo, $counter, $key, true);
|
||
|
// Unpak 4 bytes of the result, use last nipple of result as index/offset
|
||
|
$value = \unpack('N', \substr($hm, (\ord(\substr($hm, -1)) & 0x0F), 4));
|
||
|
// Only 32 bits
|
||
|
$value = $value[1] & 0x7FFFFFFF;
|
||
|
$value = \str_pad($value % $modulo, $digits, '0', STR_PAD_LEFT);
|
||
|
if (\hash_equals($value, $sCode)) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public function CreateSecret() : string
|
||
|
{
|
||
|
$CHARS = \array_keys(static::$map);
|
||
|
$length = 16;
|
||
|
$secret = '';
|
||
|
while (0 < $length--) {
|
||
|
$secret .= $CHARS[\random_int(0,31)];
|
||
|
}
|
||
|
return $secret;
|
||
|
}
|
||
|
|
||
|
protected static $map = array(
|
||
|
'A' => 0, // ord 65
|
||
|
'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, // ord 90
|
||
|
'2' => 26, // ord 50
|
||
|
'3' => 27,
|
||
|
'4' => 28,
|
||
|
'5' => 29,
|
||
|
'6' => 30,
|
||
|
'7' => 31 // ord 55
|
||
|
);
|
||
|
|
||
|
protected static function Base32Decode(string $data)
|
||
|
{
|
||
|
$data = \strtoupper(\rtrim($data, "=\x20\t\n\r\0\x0B"));
|
||
|
$dataSize = \strlen($data);
|
||
|
$buf = 0;
|
||
|
$bufSize = 0;
|
||
|
$res = '';
|
||
|
for ($i = 0; $i < $dataSize; ++$i) {
|
||
|
$c = $data[$i];
|
||
|
if (isset(static::$map[$c])) {
|
||
|
$buf = ($buf << 5) | static::$map[$c];
|
||
|
$bufSize += 5;
|
||
|
if ($bufSize > 7) {
|
||
|
$bufSize -= 8;
|
||
|
$res .= \chr(($buf & (0xff << $bufSize)) >> $bufSize);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return $res;
|
||
|
}
|
||
|
|
||
|
}
|