diff --git a/TOTP.php b/TOTP.php index ad621b6..556d457 100644 --- a/TOTP.php +++ b/TOTP.php @@ -6,9 +6,21 @@ readonly class TOTP { private const BASE32CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; // RFC 4648 Base32 + /** + * Конструктор для инициализации объекта TOTP с секретным ключом. + * + * @param string $secret Секретный ключ в кодировке Base32. + */ public function __construct(private string $secret) {} - public static function otpSecret($uniqueString, $length = 20): string + /** + * Генерация случайного секретного ключа OTP заданной длины. + * + * @param string $uniqueString Уникальная строка для создания сида. + * @param int $length Длина секретного ключа (по умолчанию: 20). + * @return string Сгенерированный секретный ключ OTP. + */ + public static function generateSecret($uniqueString, $length = 20): string { // Уникальная строка + текущее время в микросекундах $seed = $uniqueString . microtime(true); @@ -28,23 +40,36 @@ readonly class TOTP return $secret; } + /** + * Генерация одноразового пароля (OTP) на основе времени с использованием секретного ключа. + * + * @param int $digits Количество цифр в OTP (по умолчанию: 6). + * @param int $timeStep Временной шаг в секундах (по умолчанию: 30). + * @return string Сгенерированный OTP. + */ public function generate(int $digits = 6, int $timeStep = 30): string { - $time = floor(time() / $timeStep); - $secretKey = $this->base32_decode($this->secret); - $binaryTime = pack('N*', 0) . pack('N*', $time); - $hash = hash_hmac('sha1', $binaryTime, $secretKey, true); - $offset = ord($hash[strlen($hash) - 1]) & 0x0F; + $time = floor(time() / $timeStep); // Текущее время, разделенное на временной шаг + $secretKey = $this->base32decode($this->secret); // Декодируем секретный ключ из Base32 + $binaryTime = pack('N*', 0) . pack('N*', $time); // Упаковываем время в бинарный формат (big endian) + $hash = hash_hmac('sha1', $binaryTime, $secretKey, true); // Вычисляем HMAC-SHA1 хеш + $offset = ord($hash[strlen($hash) - 1]) & 0x0F; // Получаем смещение из последнего полубайта хеша $otp = ( ((ord($hash[$offset]) & 0x7F) << 24) | ((ord($hash[$offset + 1]) & 0xFF) << 16) | ((ord($hash[$offset + 2]) & 0xFF) << 8) | (ord($hash[$offset + 3]) & 0xFF) - ) % 10 ** $digits; - return str_pad((string)$otp, $digits, '0', STR_PAD_LEFT); + ) % 10 ** $digits; // Вычисляем значение OTP + return str_pad((string)$otp, $digits, '0', STR_PAD_LEFT); // Форматируем OTP до указанной длины } - private function base32_decode(string $input): string + /** + * Декодирование строки, закодированной в Base32. + * + * @param string $input Входная строка, закодированная в Base32. + * @return string Декодированная бинарная строка. + */ + private function base32decode(string $input): string { $base32charsFlipped = array_flip(str_split(self::BASE32CHARS)); $output = ''; @@ -53,13 +78,13 @@ readonly class TOTP for ($i = 0, $j = strlen($input); $i < $j; $i++) { $v <<= 5; - if ($input[$i] == '=') continue; - $v += $base32charsFlipped[$input[$i]]; + if ($input[$i] == '=') continue; // Пропускаем символы заполнения + $v += $base32charsFlipped[$input[$i]]; // Декодируем символ Base32 $vbits += 5; if ($vbits >= 8) { $vbits -= 8; - $output .= chr(($v & (0xFF << $vbits)) >> $vbits); + $output .= chr(($v & (0xFF << $vbits)) >> $vbits); // Добавляем декодированный байт } } return $output;