secretKey = $secretKey; } public static function generateKey(): string { return Serialiser::base32()->serialise(random_bytes(16)); } public static function timecode(?int $timestamp = null, int $interval = self::INTERVAL): int { $timestamp = $timestamp ?? time(); return (int)(($timestamp * 1000) / ($interval * 1000)); } public function generate(?int $timestamp = null): string { $hash = hash_hmac(self::HASH_ALGO, pack('J', self::timecode($timestamp)), Serialiser::base32()->deserialise($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); } }