diff --git a/src/LaravelOTP.php b/src/LaravelOTP.php index 706b47d..f63e60a 100644 --- a/src/LaravelOTP.php +++ b/src/LaravelOTP.php @@ -33,95 +33,51 @@ final public function now(): string return $this->at(); } - /** - * @return string - * return TOTP at previous timeframe - */ final public function last(): string { return $this->at(-1); } - /** - * @return string - * return TOTP at next timeframe - */ final public function next(): string { return $this->at(1); } - /** - * @param int $offset - * @return string - * return TOTP at custom timeframe - */ final public function at(int $offset = 0): string { return $this->generateTOTP($this->secret, $offset); } - /** - * @param int $length - * @return string - * Generate Secret Key - */ final public function generateSecretKey(): string { return substr(str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'), 0, 32); } - /** - * @return int - * Return current time - */ private function getCurrentTimestamp(): int { - return floor(time() / 30); // Time steps of 30 seconds + return floor(time() / 30); } - /** - * @param int $counter - * @return string - * Return HOTP at specific counter - */ final public function atCounter(int $counter): string { return $this->generateHOTP($this->secret, $counter); } - /** - * @param string $secretKey - * @param int $timeStepOffset - * @return string - * Generate TOTP at specific timeframe - */ private function generateTOTP(string $secretKey, int $timeStepOffset = 0): string { $timestamp = $this->getCurrentTimestamp() + $timeStepOffset; - return $this->generateHOTP($secretKey, $timestamp); // TOTP is HOTP with a time-based counter + return $this->generateHOTP($secretKey, $timestamp); } - /** - * @param string $secretKey - * @param int $counter - * @return string - * Generate HOTP at specific counter - */ private function generateHOTP(string $secretKey, int $counter): string { - // Decode the base32 secret key $key = $this->base32_decode($secretKey); + $counter = pack('J', $counter); - // Pack the counter into an 8-byte binary string (big endian) - $counter = pack('N*', 0) . pack('N*', $counter); + $hash = hash_hmac('sha512', $counter, $key, true); - // Generate HMAC-SHA1 hash using the secret key and packed counter - $hash = hash_hmac('sha1', $counter, $key, true); - - // Extract dynamic binary code (truncated hash) - $offset = ord($hash[19]) & 0xf; + $offset = ord($hash[63]) & 0xf; $binaryCode = ( ((ord($hash[$offset]) & 0x7f) << 24) | ((ord($hash[$offset + 1]) & 0xff) << 16) | @@ -129,14 +85,9 @@ private function generateHOTP(string $secretKey, int $counter): string (ord($hash[$offset + 3]) & 0xff) ); - // Convert binary code into a 6-digit OTP return str_pad($binaryCode % 1000000, 6, '0', STR_PAD_LEFT); } - /** - * @param string $input - * @return string - */ private function base32_decode(string $input): string { $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; @@ -147,7 +98,6 @@ private function base32_decode(string $input): string foreach (str_split($input) as $char) { $buffer = ($buffer << 5) | strpos($alphabet, $char); $bitsLeft += 5; - if ($bitsLeft >= 8) { $bitsLeft -= 8; $output .= chr(($buffer >> $bitsLeft) & 0xff); @@ -157,63 +107,34 @@ private function base32_decode(string $input): string return $output; } - /** - * @param string $otp - * @param string|null $secret - * @return bool - * Verify TOTP - */ final public function verifyTOTP(string $otp, string $secret = null): bool { if ($secret !== null) { $this->secret = $secret; } - $secretKey = $this->secret; - - // Check the OTP for the current time step, the previous, and the next one for ($i = -1; $i <= 1; $i++) { - $calculatedOtp = $this->generateTOTP($secretKey, $i); - if ($calculatedOtp === $otp) { - return true; // OTP is valid + if ($this->generateTOTP($this->secret, $i) === $otp) { + + return true; } } - return false; // OTP is invalid + return false; } - /** - * @param string $otp - * @param int $counter - * @param string|null $secret - * @return bool - * Verify HOTP at specific counter - */ final public function verifyHOTP(string $otp, int $counter, string $secret = null): bool { if ($secret !== null) { $this->secret = $secret; } - $calculatedOtp = $this->generateHOTP($this->secret, $counter); - return $calculatedOtp === $otp; // Return true if OTP matches, otherwise false + return $this->generateHOTP($this->secret, $counter) === $otp; } - - /** - * @param string $label - * @param string $issuer - * @param string|null $secretKey - * @param int|null $counter - * @return string - * Generate URL to be used on QR Codes for Authenticator apps - */ final public function generateUrl(string $label, string $issuer, string $secretKey = null, int $counter = null): string { - $method = 'totp'; - if ($counter !== null) { - $method = 'hotp'; - } + $method = $counter !== null ? 'hotp' : 'totp'; return "otpauth://" . $method . '/' . $label . "?secret=" . ($secretKey ?? $this->secret) . '&issuer=' . $issuer . ($counter ? '&counter=' . $counter : ''); } } diff --git a/src/LaravelOTPServiceProvider.php b/src/LaravelOTPServiceProvider.php index c7f18a2..7d31816 100644 --- a/src/LaravelOTPServiceProvider.php +++ b/src/LaravelOTPServiceProvider.php @@ -4,12 +4,12 @@ use Illuminate\Support\ServiceProvider; -class LaravelOTPServiceProvider extends ServiceProvider +final class LaravelOTPServiceProvider extends ServiceProvider { /** * Bootstrap the application services. */ - public function boot() + public function boot(): void { if ($this->app->runningInConsole()) { @@ -24,7 +24,7 @@ public function boot() /** * Register the application services. */ - public function register() + public function register(): void { // Register the main class to use with the facade $this->app->singleton('laravel-otp', function () {