misuzu/src/TOTPGenerator.php
2023-07-29 20:18:41 +00:00

53 lines
1.6 KiB
PHP

<?php
namespace Misuzu;
use InvalidArgumentException;
use Index\Serialisation\Base32;
class TOTPGenerator {
public const DIGITS = 6;
public const INTERVAL = 30000;
public function __construct(private string $secretKey) {}
public static function generateKey(): string {
return Base32::encode(random_bytes(16));
}
public static function timecode(?int $timestamp = null): int {
$timestamp ??= time();
return (int)(($timestamp * 1000) / self::INTERVAL);
}
public function generate(?int $timecode = null): string {
$timecode ??= self::timecode();
$hash = hash_hmac('sha1', pack('J', $timecode), Base32::decode($this->secretKey), true);
$offset = ord($hash[strlen($hash) - 1]) & 0x0F;
$bin = 0;
$bin |= (ord($hash[$offset]) & 0x7F) << 24;
$bin |= (ord($hash[$offset + 1]) & 0xFF) << 16;
$bin |= (ord($hash[$offset + 2]) & 0xFF) << 8;
$bin |= (ord($hash[$offset + 3]) & 0xFF);
$otp = $bin % pow(10, self::DIGITS);
return str_pad((string)$otp, self::DIGITS, '0', STR_PAD_LEFT);
}
public function generateRange(int $range = 1, ?int $timecode = null): array {
if($range < 1)
throw new InvalidArgumentException('$range must be greater than 0.');
$timecode ??= self::timecode();
$tokens = [$this->generate($timecode)];
for($i = 1; $i <= $range; ++$i)
$tokens[] = $this->generate($timecode - $i);
for($i = 1; $i <= $range; ++$i)
$tokens[] = $this->generate($timecode + $i);
return $tokens;
}
}