= 8) { $bufferBits -= 8; $binary .= chr(($buffer >> $bufferBits) & 0xFF); } } return $binary; } /** * Berekent de 6-cijferige TOTP code voor een bepaald tijdsinterval */ public static function getCode(string $secret, ?int $timestamp = null): string { if ($timestamp === null) { $timestamp = time(); } $timeCounter = floor($timestamp / 30); $binaryKey = self::base32Decode($secret); // Converteer tijdsinterval naar 8-byte big-endian binaire string $binaryTime = pack('N*', 0) . pack('N*', $timeCounter); $hash = hash_hmac('sha1', $binaryTime, $binaryKey, true); $offset = ord($hash[strlen($hash) - 1]) & 0x0F; $binaryCode = ( ((ord($hash[$offset]) & 0x7F) << 24) | ((ord($hash[$offset + 1]) & 0xFF) << 16) | ((ord($hash[$offset + 2]) & 0xFF) << 8) | (ord($hash[$offset + 3]) & 0xFF) ); $otp = $binaryCode % 1000000; return str_pad((string)$otp, 6, '0', STR_PAD_LEFT); } /** * Verifieert een ingevoerde 6-cijferige 2FA code (met 1 tijdstap speling naar voor/achter) */ public static function verifyCode(string $secret, string $userInputCode, int $window = 1): bool { $userInputCode = trim(str_replace(' ', '', $userInputCode)); if (strlen($userInputCode) !== 6 || !ctype_digit($userInputCode)) { return false; } $currentTime = time(); for ($i = -$window; $i <= $window; $i++) { $calcCode = self::getCode($secret, $currentTime + ($i * 30)); if (hash_equals($calcCode, $userInputCode)) { return true; } } return false; } /** * Maakt de otpauth:// URL aan voor authenticator apps */ public static function getOtpAuthUrl(string $email, string $secret, string $issuer = 'Fociz'): string { return sprintf( 'otpauth://totp/%s:%s?secret=%s&issuer=%s&algorithm=SHA1&digits=6&period=30', rawurlencode($issuer), rawurlencode($email), $secret, rawurlencode($issuer) ); } /** * Genereert een QR code afbeelding URL (via QuickChart HTTPS) */ public static function getQrCodeUrl(string $otpAuthUrl, int $size = 220): string { return sprintf( 'https://quickchart.io/qr?text=%s&size=%d&margin=1&ecLevel=M', urlencode($otpAuthUrl), $size ); } /** * Genereert een lijst met eenmalige herstelcodes */ public static function generateBackupCodes(int $count = 8): array { $codes = []; for ($i = 0; $i < $count; $i++) { $bytes = bin2hex(random_bytes(4)); $codes[] = strtoupper(substr($bytes, 0, 4) . '-' . substr($bytes, 4, 4)); } return $codes; } }