76 lines
2.3 KiB
PHP
76 lines
2.3 KiB
PHP
<?php
|
|
/**
|
|
* Minimale TOTP-Implementierung (RFC 6238) ohne externe Abhängigkeiten.
|
|
* Kompatibel mit Google Authenticator, Authy, etc.
|
|
*/
|
|
|
|
function totp_generate_secret(int $length = 16): string
|
|
{
|
|
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
|
$secret = '';
|
|
$bytes = random_bytes($length);
|
|
for ($i = 0; $i < $length; $i++) {
|
|
$secret .= $chars[ord($bytes[$i]) & 31];
|
|
}
|
|
return $secret;
|
|
}
|
|
|
|
function totp_get_code(string $secret, int $timeSlot = 0): string
|
|
{
|
|
$time = floor(time() / 30) + $timeSlot;
|
|
$key = base32_decode($secret);
|
|
$msg = pack('N*', 0) . pack('N*', $time);
|
|
$hash = hash_hmac('sha1', $msg, $key, true);
|
|
$offset = ord($hash[19]) & 0xF;
|
|
$code = (
|
|
((ord($hash[$offset]) & 0x7F) << 24) |
|
|
((ord($hash[$offset + 1]) & 0xFF) << 16) |
|
|
((ord($hash[$offset + 2]) & 0xFF) << 8) |
|
|
((ord($hash[$offset + 3]) & 0xFF))
|
|
) % 1000000;
|
|
return str_pad((string) $code, 6, '0', STR_PAD_LEFT);
|
|
}
|
|
|
|
function totp_verify(string $secret, string $code, int $window = 1): bool
|
|
{
|
|
$code = preg_replace('/\s+/', '', $code);
|
|
if (!preg_match('/^\d{6}$/', $code)) return false;
|
|
for ($i = -$window; $i <= $window; $i++) {
|
|
if (hash_equals(totp_get_code($secret, $i), $code)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function totp_qr_url(string $secret, string $email, string $issuer = 'DeskHub'): string
|
|
{
|
|
$label = rawurlencode($issuer . ':' . $email);
|
|
$params = http_build_query([
|
|
'secret' => $secret,
|
|
'issuer' => $issuer,
|
|
'algorithm' => 'SHA1',
|
|
'digits' => 6,
|
|
'period' => 30,
|
|
]);
|
|
$otpauth = 'otpauth://totp/' . $label . '?' . $params;
|
|
return 'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=' . rawurlencode($otpauth);
|
|
}
|
|
|
|
function base32_decode(string $input): string
|
|
{
|
|
$map = array_flip(str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'));
|
|
$input = strtoupper(rtrim($input, '='));
|
|
$binary = '';
|
|
$buffer = 0;
|
|
$bits = 0;
|
|
foreach (str_split($input) as $char) {
|
|
if (!isset($map[$char])) continue;
|
|
$buffer = ($buffer << 5) | $map[$char];
|
|
$bits += 5;
|
|
if ($bits >= 8) {
|
|
$binary .= chr(($buffer >> ($bits - 8)) & 0xFF);
|
|
$bits -= 8;
|
|
}
|
|
}
|
|
return $binary;
|
|
}
|