Files
Piwigo/include/totp.class.php
Linty 9d2565465e (cp 9ac99be1d) fixes GHSA-9986-w7jf-33f6 and fixes GHSA-9986-w7jf-33f6
not include password.tpl from standard pages
2025-10-17 16:24:25 +02:00

116 lines
3.3 KiB
PHP

<?php
defined('PHPWG_ROOT_PATH') or die('Hacking attempt!');
require_once(PHPWG_ROOT_PATH . 'include/base32.class.php');
class PwgTOTP
{
/**
* Generate a Base32 secret for TOTP
*
* @param string $secret Base32-encoded secret
* @param int $timestamp 30s intervasl since 1970
* @return string TOTP Code
*/
private static function generateCodeFromTimestamp($secret, $timestamp)
{
$key = PwgBase32::decode($secret);
$msg = pack('N*', 0) . pack('N*', $timestamp); // hash_hmac need this form
$hash = hash_hmac('sha1', $msg, $key, true);
// RFC 4226, section 5.3
$offset = ord(substr($hash, -1)) & 0x0F;
$part = substr($hash, $offset, 4);
$number = unpack('N', $part)[1] & 0x7FFFFFFF;
$code = $number % 1000000; // code 6 digits $number % 10^6
return str_pad((string)$code, 6, '0', STR_PAD_LEFT); // 123 become 000123
}
/**
* Generate a Base32 secret for TOTP
*
* @param int $length Length in bytes (default: 20)
* @return string Base32-encoded secret
*/
public static function generateSecret($length = 20)
{
$random = random_bytes($length);
return PwgBase32::encode($random, false);
}
/**
* Get Otp auth url
*
* @param string $secret Encoded base32 secret
* @return string otpauth://totp/ url
*/
public static function getOtpAuthUrl($secret)
{
global $user;
$url = substr(get_absolute_root_url(), 0, -1);
return 'otpauth://totp/'.$user['username'].':'.$url.'?secret='.$secret.'&issuer=Piwigo&algorithm=sha1&digits=6&period=30';
}
/**
* Get Qr Code
*
* @param string $secret Encoded base32 secret
* @return string data:image/png;base64..
*/
public static function getQrCode($secret)
{
// require_once(TF_REALPATH . 'include/phpqrcode.php');
// $otp_url = self::getOtpAuthUrl($secret);
// ob_start();
// QRcode::png($otp_url);
// $qrcode_image = ob_get_clean();
// $base64_qrcode = base64_encode($qrcode_image);
// return 'data:image/png;base64,' . $base64_qrcode;
}
/**
* Generate a TOTP Code
*
* @param string $secret Encoded base32 secret
* @param int $timestamp timestamp used in second (default: 30)
* @return string 6 digits TOTP code
*/
public static function generateCode($secret, $timestamp = 30)
{
$timestamp = floor(time() / $timestamp); // e.g 58338889 > 30-second intervals since 1970 at the moment T
return self::generateCodeFromTimestamp($secret, $timestamp);
}
/**
* Verify TOTP Code
*
* @param string $code Digits 6 TOTP Code
* @param string $secret Encoded base32 secret
* @param int $timestamp timestamp used in second (default: 30)
* @param int $check_interval Number of 30s steps to check before/after current (default: 1)
* @return bool
*/
public static function verifyCode($code, $secret, $timestamp = 30, $check_interval = 1)
{
$timestamp = floor(time() / $timestamp);
// generate a totp code for 30s intervals
// following or preceding the current one and check it
for ($i=-$check_interval; $i <= $check_interval; $i++)
{
$interval_timestamp = $timestamp + $i;
$generated_code = self::generateCodeFromTimestamp($secret, $interval_timestamp);
if (hash_equals($generated_code, $code))
{
return true;
}
}
return false;
}
}