Files
Fociz/includes/totp.php
T
NiekRabelink 9262b09c4d Add roadmap management, service worker, and user invitation features
- Implemented roadmap.php for managing roadmap items with filtering options by quarter and status.
- Added a service worker (sw.js) to handle push notifications and click events for calendar appointments.
- Created users.php for admin to manage team members and invitations, including user creation and deletion functionalities.
2026-09-09 15:54:24 +02:00

133 lines
4.0 KiB
PHP

<?php
/**
* Pure PHP TOTP (RFC 6238) & 2FA Implementatie
* Volledig compatibel met Google Authenticator, Microsoft Authenticator & 1Password
*/
class TOTP {
private static string $base32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
/**
* Genereert een willekeurige 16-teken Base32 geheime sleutel
*/
public static function generateSecret(int $length = 16): string {
$secret = '';
$bytes = random_bytes($length);
for ($i = 0; $i < $length; $i++) {
$secret .= self::$base32Chars[ord($bytes[$i]) % 32];
}
return $secret;
}
/**
* Decodeert een Base32 string naar binaire data
*/
private static function base32Decode(string $b32): string {
$b32 = strtoupper(trim($b32));
$buffer = 0;
$bufferBits = 0;
$binary = '';
for ($i = 0; $i < strlen($b32); $i++) {
$char = $b32[$i];
$val = strpos(self::$base32Chars, $char);
if ($val === false) continue; // Negeer spaties/ongeldige tekens
$buffer = ($buffer << 5) | $val;
$bufferBits += 5;
if ($bufferBits >= 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;
}
}