2024-05-09 13:48:45 +00:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
readonly class TOTP
|
|
|
|
|
|
{
|
Update TOTP.php
- Добавлен метод `verify` для верификации TOTP с учётом возможных временных расхождений (например, из-за задержек в синхронизации времени). *Нужно проверять OTP не только для текущего временного интервала, но и для соседних интервалов (±1 или ±2 временных шага). Это стандартная практика, описанная в RFC 6238, чтобы учесть небольшие рассинхронизации между сервером и клиентским устройством (телефоном).*
- Кэширование декодированного секрета:
- Добавлено свойство `private string $decodedSecret`.
- В конструкторе теперь вызывается `$this->base32decode($secret)` один раз, и результат сохраняется в `$decodedSecret`.
- В методах `generate` и `verify` (через `computeOtp`) используется `$this->decodedSecret` вместо повторного вызова `base32decode`. Это снижает вычислительные затраты, так как декодирование Base32 выполняется только при создании объекта.
- Вынос логики OTP и константы:
- Добавлен приватный метод computeOtp(int $timeCounter, int $digits): string, который содержит логику вычисления OTP (упаковка времени, HMAC-SHA1, извлечение и форматирование). Это устраняет дублирование кода из generate и verify.
- В `generate` теперь просто вычисляется `$timeCounter` и вызывается `computeOtp`.
- В `verify` используется `computeOtp` для генерации OTP на каждой итерации цикла.
- Добавлены константы `DEFAULT_DIGITS = 6`, `DEFAULT_TIME_STEP = 30`, `DEFAULT_SECRET_LENGTH = 20` для параметров по умолчанию. Они используются в методах `generate`, `verify` и `generateSecret`, что делает код более декларативным и упрощает изменение значений в будущем.
2025-08-27 15:06:19 +00:00
|
|
|
|
private const string BASE32CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; // RFC 4648 Base32
|
|
|
|
|
|
private const int DEFAULT_DIGITS = 6; // Количество цифр в OTP по умолчанию
|
|
|
|
|
|
private const int DEFAULT_TIME_STEP = 30; // Временной шаг в секундах по умолчанию
|
|
|
|
|
|
private const int DEFAULT_SECRET_LENGTH = 20; // Длина секретного ключа по умолчанию
|
|
|
|
|
|
|
|
|
|
|
|
private string $decodedSecret; // Кэшированный декодированный секрет
|
2024-05-09 13:48:45 +00:00
|
|
|
|
|
2024-05-09 14:02:54 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Конструктор для инициализации объекта TOTP с секретным ключом.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $secret Секретный ключ в кодировке Base32.
|
|
|
|
|
|
*/
|
Update TOTP.php
- Добавлен метод `verify` для верификации TOTP с учётом возможных временных расхождений (например, из-за задержек в синхронизации времени). *Нужно проверять OTP не только для текущего временного интервала, но и для соседних интервалов (±1 или ±2 временных шага). Это стандартная практика, описанная в RFC 6238, чтобы учесть небольшие рассинхронизации между сервером и клиентским устройством (телефоном).*
- Кэширование декодированного секрета:
- Добавлено свойство `private string $decodedSecret`.
- В конструкторе теперь вызывается `$this->base32decode($secret)` один раз, и результат сохраняется в `$decodedSecret`.
- В методах `generate` и `verify` (через `computeOtp`) используется `$this->decodedSecret` вместо повторного вызова `base32decode`. Это снижает вычислительные затраты, так как декодирование Base32 выполняется только при создании объекта.
- Вынос логики OTP и константы:
- Добавлен приватный метод computeOtp(int $timeCounter, int $digits): string, который содержит логику вычисления OTP (упаковка времени, HMAC-SHA1, извлечение и форматирование). Это устраняет дублирование кода из generate и verify.
- В `generate` теперь просто вычисляется `$timeCounter` и вызывается `computeOtp`.
- В `verify` используется `computeOtp` для генерации OTP на каждой итерации цикла.
- Добавлены константы `DEFAULT_DIGITS = 6`, `DEFAULT_TIME_STEP = 30`, `DEFAULT_SECRET_LENGTH = 20` для параметров по умолчанию. Они используются в методах `generate`, `verify` и `generateSecret`, что делает код более декларативным и упрощает изменение значений в будущем.
2025-08-27 15:06:19 +00:00
|
|
|
|
public function __construct(private string $secret)
|
|
|
|
|
|
{
|
|
|
|
|
|
$this->decodedSecret = $this->base32decode($secret); // Декодируем секрет один раз при создании объекта
|
|
|
|
|
|
}
|
2024-05-09 13:48:45 +00:00
|
|
|
|
|
2024-05-09 14:02:54 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Генерация случайного секретного ключа OTP заданной длины.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $uniqueString Уникальная строка для создания сида.
|
|
|
|
|
|
* @param int $length Длина секретного ключа (по умолчанию: 20).
|
|
|
|
|
|
* @return string Сгенерированный секретный ключ OTP.
|
|
|
|
|
|
*/
|
Update TOTP.php
- Добавлен метод `verify` для верификации TOTP с учётом возможных временных расхождений (например, из-за задержек в синхронизации времени). *Нужно проверять OTP не только для текущего временного интервала, но и для соседних интервалов (±1 или ±2 временных шага). Это стандартная практика, описанная в RFC 6238, чтобы учесть небольшие рассинхронизации между сервером и клиентским устройством (телефоном).*
- Кэширование декодированного секрета:
- Добавлено свойство `private string $decodedSecret`.
- В конструкторе теперь вызывается `$this->base32decode($secret)` один раз, и результат сохраняется в `$decodedSecret`.
- В методах `generate` и `verify` (через `computeOtp`) используется `$this->decodedSecret` вместо повторного вызова `base32decode`. Это снижает вычислительные затраты, так как декодирование Base32 выполняется только при создании объекта.
- Вынос логики OTP и константы:
- Добавлен приватный метод computeOtp(int $timeCounter, int $digits): string, который содержит логику вычисления OTP (упаковка времени, HMAC-SHA1, извлечение и форматирование). Это устраняет дублирование кода из generate и verify.
- В `generate` теперь просто вычисляется `$timeCounter` и вызывается `computeOtp`.
- В `verify` используется `computeOtp` для генерации OTP на каждой итерации цикла.
- Добавлены константы `DEFAULT_DIGITS = 6`, `DEFAULT_TIME_STEP = 30`, `DEFAULT_SECRET_LENGTH = 20` для параметров по умолчанию. Они используются в методах `generate`, `verify` и `generateSecret`, что делает код более декларативным и упрощает изменение значений в будущем.
2025-08-27 15:06:19 +00:00
|
|
|
|
public static function generateSecret(string $uniqueString, int $length = self::DEFAULT_SECRET_LENGTH): string
|
2024-05-09 13:48:45 +00:00
|
|
|
|
{
|
|
|
|
|
|
// Уникальная строка + текущее время в микросекундах
|
|
|
|
|
|
$seed = $uniqueString . microtime(true);
|
|
|
|
|
|
|
|
|
|
|
|
// Хэшируем сид для более случайного распределения
|
|
|
|
|
|
$seed = hash('sha256', $seed);
|
|
|
|
|
|
|
|
|
|
|
|
$secret = '';
|
|
|
|
|
|
$max = strlen(self::BASE32CHARS) - 1;
|
|
|
|
|
|
|
|
|
|
|
|
// Генерируем случайную строку нужной длины
|
|
|
|
|
|
for ($i = 0; $i < $length; $i++) {
|
|
|
|
|
|
$index = hexdec(substr($seed, $i, 2)) % $max; // Получаем индекс символа из хэшированного сида
|
|
|
|
|
|
$secret .= self::BASE32CHARS[$index];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $secret;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-09 14:02:54 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Генерация одноразового пароля (OTP) на основе времени с использованием секретного ключа.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param int $digits Количество цифр в OTP (по умолчанию: 6).
|
|
|
|
|
|
* @param int $timeStep Временной шаг в секундах (по умолчанию: 30).
|
|
|
|
|
|
* @return string Сгенерированный OTP.
|
|
|
|
|
|
*/
|
Update TOTP.php
- Добавлен метод `verify` для верификации TOTP с учётом возможных временных расхождений (например, из-за задержек в синхронизации времени). *Нужно проверять OTP не только для текущего временного интервала, но и для соседних интервалов (±1 или ±2 временных шага). Это стандартная практика, описанная в RFC 6238, чтобы учесть небольшие рассинхронизации между сервером и клиентским устройством (телефоном).*
- Кэширование декодированного секрета:
- Добавлено свойство `private string $decodedSecret`.
- В конструкторе теперь вызывается `$this->base32decode($secret)` один раз, и результат сохраняется в `$decodedSecret`.
- В методах `generate` и `verify` (через `computeOtp`) используется `$this->decodedSecret` вместо повторного вызова `base32decode`. Это снижает вычислительные затраты, так как декодирование Base32 выполняется только при создании объекта.
- Вынос логики OTP и константы:
- Добавлен приватный метод computeOtp(int $timeCounter, int $digits): string, который содержит логику вычисления OTP (упаковка времени, HMAC-SHA1, извлечение и форматирование). Это устраняет дублирование кода из generate и verify.
- В `generate` теперь просто вычисляется `$timeCounter` и вызывается `computeOtp`.
- В `verify` используется `computeOtp` для генерации OTP на каждой итерации цикла.
- Добавлены константы `DEFAULT_DIGITS = 6`, `DEFAULT_TIME_STEP = 30`, `DEFAULT_SECRET_LENGTH = 20` для параметров по умолчанию. Они используются в методах `generate`, `verify` и `generateSecret`, что делает код более декларативным и упрощает изменение значений в будущем.
2025-08-27 15:06:19 +00:00
|
|
|
|
public function generate(int $digits = self::DEFAULT_DIGITS, int $timeStep = self::DEFAULT_TIME_STEP): string
|
|
|
|
|
|
{
|
|
|
|
|
|
$timeCounter = floor(time() / $timeStep); // Текущее время, разделенное на временной шаг
|
|
|
|
|
|
return $this->computeOtp($timeCounter, $digits); // Вычисляем OTP
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Проверка одноразового пароля (OTP) с учётом временного окна.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $otp Введённый пользователем OTP.
|
|
|
|
|
|
* @param int $digits Количество цифр в OTP (по умолчанию: 6).
|
|
|
|
|
|
* @param int $timeStep Временной шаг в секундах (по умолчанию: 30).
|
|
|
|
|
|
* @param int $window Количество интервалов до и после текущего для проверки (по умолчанию: 1).
|
|
|
|
|
|
* @return bool Возвращает true, если OTP валиден в указанном временном окне.
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function verify(string $otp, int $digits = self::DEFAULT_DIGITS, int $timeStep = self::DEFAULT_TIME_STEP, int $window = 1): bool
|
|
|
|
|
|
{
|
|
|
|
|
|
$currentTimeCounter = floor(time() / $timeStep); // Текущий временной счётчик
|
|
|
|
|
|
|
|
|
|
|
|
// Проверяем OTP для текущего времени и соседних интервалов (±window)
|
|
|
|
|
|
for ($i = -$window; $i <= $window; $i++) {
|
|
|
|
|
|
$timeCounter = $currentTimeCounter + $i;
|
|
|
|
|
|
$generatedOtp = $this->computeOtp($timeCounter, $digits); // Вычисляем OTP
|
|
|
|
|
|
if ($otp === $generatedOtp) {
|
|
|
|
|
|
return true; // OTP совпал
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false; // OTP не совпал ни в одном интервале
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Вычисление OTP для заданного временного счётчика.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param int $timeCounter Временной счётчик (время, разделённое на timeStep).
|
|
|
|
|
|
* @param int $digits Количество цифр в OTP.
|
|
|
|
|
|
* @return string Сгенерированный OTP.
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function computeOtp(int $timeCounter, int $digits): string
|
2024-05-09 13:48:45 +00:00
|
|
|
|
{
|
Update TOTP.php
- Добавлен метод `verify` для верификации TOTP с учётом возможных временных расхождений (например, из-за задержек в синхронизации времени). *Нужно проверять OTP не только для текущего временного интервала, но и для соседних интервалов (±1 или ±2 временных шага). Это стандартная практика, описанная в RFC 6238, чтобы учесть небольшие рассинхронизации между сервером и клиентским устройством (телефоном).*
- Кэширование декодированного секрета:
- Добавлено свойство `private string $decodedSecret`.
- В конструкторе теперь вызывается `$this->base32decode($secret)` один раз, и результат сохраняется в `$decodedSecret`.
- В методах `generate` и `verify` (через `computeOtp`) используется `$this->decodedSecret` вместо повторного вызова `base32decode`. Это снижает вычислительные затраты, так как декодирование Base32 выполняется только при создании объекта.
- Вынос логики OTP и константы:
- Добавлен приватный метод computeOtp(int $timeCounter, int $digits): string, который содержит логику вычисления OTP (упаковка времени, HMAC-SHA1, извлечение и форматирование). Это устраняет дублирование кода из generate и verify.
- В `generate` теперь просто вычисляется `$timeCounter` и вызывается `computeOtp`.
- В `verify` используется `computeOtp` для генерации OTP на каждой итерации цикла.
- Добавлены константы `DEFAULT_DIGITS = 6`, `DEFAULT_TIME_STEP = 30`, `DEFAULT_SECRET_LENGTH = 20` для параметров по умолчанию. Они используются в методах `generate`, `verify` и `generateSecret`, что делает код более декларативным и упрощает изменение значений в будущем.
2025-08-27 15:06:19 +00:00
|
|
|
|
$binaryTime = pack('N*', 0) . pack('N*', $timeCounter); // Упаковываем время в бинарный формат (big endian)
|
|
|
|
|
|
$hash = hash_hmac('sha1', $binaryTime, $this->decodedSecret, true); // Вычисляем HMAC-SHA1 хеш
|
2024-05-09 14:02:54 +00:00
|
|
|
|
$offset = ord($hash[strlen($hash) - 1]) & 0x0F; // Получаем смещение из последнего полубайта хеша
|
2024-05-09 13:48:45 +00:00
|
|
|
|
$otp = (
|
|
|
|
|
|
((ord($hash[$offset]) & 0x7F) << 24) |
|
|
|
|
|
|
((ord($hash[$offset + 1]) & 0xFF) << 16) |
|
|
|
|
|
|
((ord($hash[$offset + 2]) & 0xFF) << 8) |
|
|
|
|
|
|
(ord($hash[$offset + 3]) & 0xFF)
|
Update TOTP.php
- Добавлен метод `verify` для верификации TOTP с учётом возможных временных расхождений (например, из-за задержек в синхронизации времени). *Нужно проверять OTP не только для текущего временного интервала, но и для соседних интервалов (±1 или ±2 временных шага). Это стандартная практика, описанная в RFC 6238, чтобы учесть небольшие рассинхронизации между сервером и клиентским устройством (телефоном).*
- Кэширование декодированного секрета:
- Добавлено свойство `private string $decodedSecret`.
- В конструкторе теперь вызывается `$this->base32decode($secret)` один раз, и результат сохраняется в `$decodedSecret`.
- В методах `generate` и `verify` (через `computeOtp`) используется `$this->decodedSecret` вместо повторного вызова `base32decode`. Это снижает вычислительные затраты, так как декодирование Base32 выполняется только при создании объекта.
- Вынос логики OTP и константы:
- Добавлен приватный метод computeOtp(int $timeCounter, int $digits): string, который содержит логику вычисления OTP (упаковка времени, HMAC-SHA1, извлечение и форматирование). Это устраняет дублирование кода из generate и verify.
- В `generate` теперь просто вычисляется `$timeCounter` и вызывается `computeOtp`.
- В `verify` используется `computeOtp` для генерации OTP на каждой итерации цикла.
- Добавлены константы `DEFAULT_DIGITS = 6`, `DEFAULT_TIME_STEP = 30`, `DEFAULT_SECRET_LENGTH = 20` для параметров по умолчанию. Они используются в методах `generate`, `verify` и `generateSecret`, что делает код более декларативным и упрощает изменение значений в будущем.
2025-08-27 15:06:19 +00:00
|
|
|
|
) % 10 ** $digits; // Вычисляем значение OTP
|
2024-05-09 14:02:54 +00:00
|
|
|
|
return str_pad((string)$otp, $digits, '0', STR_PAD_LEFT); // Форматируем OTP до указанной длины
|
2024-05-09 13:48:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-05-09 14:02:54 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Декодирование строки, закодированной в Base32.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $input Входная строка, закодированная в Base32.
|
|
|
|
|
|
* @return string Декодированная бинарная строка.
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function base32decode(string $input): string
|
2024-05-09 13:48:45 +00:00
|
|
|
|
{
|
|
|
|
|
|
$base32charsFlipped = array_flip(str_split(self::BASE32CHARS));
|
|
|
|
|
|
$output = '';
|
|
|
|
|
|
$v = 0;
|
|
|
|
|
|
$vbits = 0;
|
|
|
|
|
|
|
|
|
|
|
|
for ($i = 0, $j = strlen($input); $i < $j; $i++) {
|
|
|
|
|
|
$v <<= 5;
|
Update TOTP.php
- Добавлен метод `verify` для верификации TOTP с учётом возможных временных расхождений (например, из-за задержек в синхронизации времени). *Нужно проверять OTP не только для текущего временного интервала, но и для соседних интервалов (±1 или ±2 временных шага). Это стандартная практика, описанная в RFC 6238, чтобы учесть небольшие рассинхронизации между сервером и клиентским устройством (телефоном).*
- Кэширование декодированного секрета:
- Добавлено свойство `private string $decodedSecret`.
- В конструкторе теперь вызывается `$this->base32decode($secret)` один раз, и результат сохраняется в `$decodedSecret`.
- В методах `generate` и `verify` (через `computeOtp`) используется `$this->decodedSecret` вместо повторного вызова `base32decode`. Это снижает вычислительные затраты, так как декодирование Base32 выполняется только при создании объекта.
- Вынос логики OTP и константы:
- Добавлен приватный метод computeOtp(int $timeCounter, int $digits): string, который содержит логику вычисления OTP (упаковка времени, HMAC-SHA1, извлечение и форматирование). Это устраняет дублирование кода из generate и verify.
- В `generate` теперь просто вычисляется `$timeCounter` и вызывается `computeOtp`.
- В `verify` используется `computeOtp` для генерации OTP на каждой итерации цикла.
- Добавлены константы `DEFAULT_DIGITS = 6`, `DEFAULT_TIME_STEP = 30`, `DEFAULT_SECRET_LENGTH = 20` для параметров по умолчанию. Они используются в методах `generate`, `verify` и `generateSecret`, что делает код более декларативным и упрощает изменение значений в будущем.
2025-08-27 15:06:19 +00:00
|
|
|
|
if ($input[$i] === '=') {continue;} // Пропускаем символы заполнения
|
2024-05-09 14:02:54 +00:00
|
|
|
|
$v += $base32charsFlipped[$input[$i]]; // Декодируем символ Base32
|
2024-05-09 13:48:45 +00:00
|
|
|
|
$vbits += 5;
|
|
|
|
|
|
|
|
|
|
|
|
if ($vbits >= 8) {
|
|
|
|
|
|
$vbits -= 8;
|
2024-05-09 14:02:54 +00:00
|
|
|
|
$output .= chr(($v & (0xFF << $vbits)) >> $vbits); // Добавляем декодированный байт
|
2024-05-09 13:48:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return $output;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|