|
Server : Apache System : Linux s1230 5.15.0-139-generic #149~20.04.1 SMP Tue Jul 14 11:21:49 UTC 2026 x86_64 User : p141464 ( 418825) PHP Version : 7.4.33.12 Disable Function : NONE Directory : /html/KMU-CSR-PLANER/wp-content/plugins/matomo/app/libs/Authenticator/ |
Upload File : |
<?php
namespace {
/**
* PHP Class for handling Google Authenticator 2-factor authentication
*
* @author Michael Kliewe
* @copyright 2012 Michael Kliewe
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @link http://www.phpgangsta.de/
*
* small adjustments by matomo.org
* - renamed class
* - removed method getQRCodeGoogleUrl
* - use better random secret generator
* - apply proper type hints
*/
class TwoFactorAuthenticator
{
protected $_codeLength = 6;
/**
* Create new secret.
* 16 characters, randomly chosen from the allowed base32 characters.
*
* @param int $secretLength
* @return string
*/
public function createSecret(int $secretLength = 16) : string
{
$validChars = $this->_getBase32LookupTable();
unset($validChars[32]);
// modified by matomo.org
return \Piwik\Common::getRandomString($secretLength, \implode('', $validChars));
}
/**
* Calculate the code, with given secret and point in time
*
* @param string $secret
* @param int|null $timeSlice
* @return string
*/
public function getCode(string $secret, ?int $timeSlice = null)
{
if ($timeSlice === null) {
$timeSlice = \floor(\time() / 30);
}
$secretkey = $this->_base32Decode($secret);
// Pack time into binary string
$time = \chr(0) . \chr(0) . \chr(0) . \chr(0) . \pack('N*', $timeSlice);
// Hash it with users secret key
$hm = \hash_hmac('SHA1', $time, $secretkey, \true);
// Use last nipple of result as index/offset
$offset = \ord(\substr($hm, -1)) & 0xf;
// grab 4 bytes of the result
$hashpart = \substr($hm, $offset, 4);
// Unpak binary value
$value = \unpack('N', $hashpart);
$value = $value[1];
// Only 32 bits
$value = $value & 0x7fffffff;
$modulo = \pow(10, $this->_codeLength);
return \str_pad($value % $modulo, $this->_codeLength, '0', \STR_PAD_LEFT);
}
/**
* Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now
*
* @param string $secret
* @param string $code
* @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after)
* @param int|null $currentTimeSlice time slice if we want use other that time()
* @return bool
*/
public function verifyCode(string $secret, string $code, int $discrepancy = 1, ?int $currentTimeSlice = null) : bool
{
if ($currentTimeSlice === null) {
$currentTimeSlice = \floor(\time() / 30);
}
for ($i = -$discrepancy; $i <= $discrepancy; $i++) {
$calculatedCode = $this->getCode($secret, $currentTimeSlice + $i);
if ($this->timingSafeEquals($calculatedCode, $code)) {
return \true;
}
}
return \false;
}
/**
* Set the code length, should be >=6
*
* @param int $length
* @return self
*/
public function setCodeLength(int $length)
{
$this->_codeLength = $length;
return $this;
}
/**
* Helper class to decode base32
*
* @param string $secret
* @return string
*/
protected function _base32Decode(string $secret) : string
{
if (empty($secret)) {
return '';
}
$base32chars = $this->_getBase32LookupTable();
$base32charsFlipped = \array_flip($base32chars);
$paddingCharCount = \substr_count($secret, $base32chars[32]);
$allowedValues = array(6, 4, 3, 1, 0);
if (!\in_array($paddingCharCount, $allowedValues)) {
return \false;
}
for ($i = 0; $i < 4; $i++) {
if ($paddingCharCount == $allowedValues[$i] && \substr($secret, -$allowedValues[$i]) != \str_repeat($base32chars[32], $allowedValues[$i])) {
return \false;
}
}
$secret = \str_replace('=', '', $secret);
$secret = \str_split($secret);
$binaryString = "";
for ($i = 0; $i < \count($secret); $i = $i + 8) {
$x = "";
if (!\in_array($secret[$i], $base32chars)) {
return \false;
}
for ($j = 0; $j < 8; $j++) {
$x .= \str_pad(\base_convert($base32charsFlipped[@$secret[$i + $j]] ?? '', 10, 2), 5, '0', \STR_PAD_LEFT);
}
$eightBits = \str_split($x, 8);
for ($z = 0; $z < \count($eightBits); $z++) {
$binaryString .= ($y = \chr(\base_convert($eightBits[$z], 2, 10))) || \ord($y) == 48 ? $y : "";
}
}
return $binaryString;
}
/**
* Helper class to encode base32
*
* @param string $secret
* @param bool $padding
* @return string
*/
protected function _base32Encode(string $secret, bool $padding = \true) : string
{
if (empty($secret)) {
return '';
}
$base32chars = $this->_getBase32LookupTable();
$secret = \str_split($secret);
$binaryString = "";
for ($i = 0; $i < \count($secret); $i++) {
$binaryString .= \str_pad(\base_convert(\ord($secret[$i]), 10, 2), 8, '0', \STR_PAD_LEFT);
}
$fiveBitBinaryArray = \str_split($binaryString, 5);
$base32 = "";
$i = 0;
while ($i < \count($fiveBitBinaryArray)) {
$base32 .= $base32chars[\base_convert(\str_pad($fiveBitBinaryArray[$i], 5, '0'), 2, 10)];
$i++;
}
if ($padding && ($x = \strlen($binaryString) % 40) != 0) {
if ($x == 8) {
$base32 .= \str_repeat($base32chars[32], 6);
} elseif ($x == 16) {
$base32 .= \str_repeat($base32chars[32], 4);
} elseif ($x == 24) {
$base32 .= \str_repeat($base32chars[32], 3);
} elseif ($x == 32) {
$base32 .= $base32chars[32];
}
}
return $base32;
}
/**
* Get array with all 32 characters for decoding from/encoding to base32
*
* @return array<string>
*/
protected function _getBase32LookupTable() : array
{
return array(
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
// 7
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
// 15
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
// 23
'Y',
'Z',
'2',
'3',
'4',
'5',
'6',
'7',
// 31
'=',
);
}
/**
* A timing safe equals comparison
* more info here: https://blog.ircmaxell.com/2014/11/its-all-about-time.html.
*
* @param string $safeString The internal (safe) value to be checked
* @param string $userString The user submitted (unsafe) value
*
* @return bool True if the two strings are identical
*/
private function timingSafeEquals(string $safeString, string $userString) : bool
{
if (\function_exists('hash_equals')) {
return \hash_equals($safeString, $userString);
}
$safeLen = \strlen($safeString);
$userLen = \strlen($userString);
if ($userLen != $safeLen) {
return \false;
}
$result = 0;
for ($i = 0; $i < $userLen; ++$i) {
$result |= \ord($safeString[$i]) ^ \ord($userString[$i]);
}
// They are only identical strings if $result is exactly 0...
return $result === 0;
}
}
}