misuzu/src/otp.php

73 lines
1.9 KiB
PHP
Raw Normal View History

<?php
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
define('MSZ_TOTP_DEFAULT_DIGITS', 6);
define('MSZ_TOTP_DEFAULT_ALGO', 'sha1');
define('MSZ_TOTP_DEFAULT_TOTP_INTERVAL', 30);
function otp_generate(
int $data,
string $secret,
int $digits = MSZ_TOTP_DEFAULT_DIGITS,
string $algo = MSZ_TOTP_DEFAULT_ALGO
): ?string {
$hash = hash_hmac($algo, pack('J', $data), base32_decode($secret), 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, $digits);
return str_pad($otp, $digits, STR_PAD_LEFT);
}
function totp_timecode(int $timestamp, int $interval = MSZ_OTP_DEFAULT_TOTP_INTERVAL): int
{
return ($timestamp * 1000) / ($interval * 1000);
}
function totp_generate(
string $secret,
?int $time = null,
int $interval = MSZ_TOTP_DEFAULT_TOTP_INTERVAL,
int $digits = MSZ_TOTP_DEFAULT_DIGITS,
string $algo = MSZ_TOTP_DEFAULT_ALGO
): string {
return otp_generate(totp_timecode($time ?? time(), $interval), $secret, $digits, $algo);
}
function totp_uri(string $name, string $secret, string $issuer = ''): string
{
$query = [
'secret' => $secret,
];
if (!empty($issuer)) {
$query['issuer'] = $issuer;
}
return sprintf('otpauth://totp/%s?%s', $name, http_build_query($query));
}
function totp_qrcode(string $uri): string
{
$options = new QROptions([
'version' => 5,
'outputType' => QRCode::OUTPUT_IMAGE_PNG,
'eccLevel' => QRCode::ECC_L,
]);
$qrcode = new QRCode($options);
return $qrcode->render($uri);
}
// will generate a 26 character code
function totp_generate_key(): string
{
return base32_encode(random_bytes(16));
}