Commit version 1.0

This commit is contained in:
Frank Bischof 2023-01-20 14:21:17 +01:00
parent cc3f1edaef
commit 847e509a1f
4 changed files with 262 additions and 1 deletions

208
GoogleAuthenticator.php Normal file
View File

@ -0,0 +1,208 @@
<?php
/**
* 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
*
*/
class GoogleAuthenticator
{
protected $_codeLength = 6;
/**
* Create new secret.
* 16 characters, randomly chosen from the allowed base32 characters.
*
* @param int $secretLength
* @return string
*/
public function createSecret($secretLength = 16)
{
$validChars = $this->_getBase32LookupTable();
unset($validChars[32]);
$secret = '';
for ($i = 0; $i < $secretLength; $i++) {
$secret .= $validChars[array_rand($validChars)];
}
return $secret;
}
/**
* Calculate the code, with given secret and point in time
*
* @param string $secret
* @param int|null $timeSlice
* @return string
*/
public function getCode($secret, $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)) & 0x0F;
// 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);
}
/**
* Get QR-Code URL for image, from google charts
*
* @param string $name
* @param string $secret
* @param string $title
* @return string
*/
public function getQRCodeGoogleUrl($name, $secret, $title = null) {
$urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.'');
if(isset($title)) {
$urlencoded .= urlencode('&issuer='.urlencode($title));
}
return 'https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl='.$urlencoded.'';
}
/**
* 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($secret, $code, $discrepancy = 1, $currentTimeSlice = null)
{
if ($currentTimeSlice === null) {
$currentTimeSlice = floor(time() / 30);
}
for ($i = -$discrepancy; $i <= $discrepancy; $i++) {
$calculatedCode = $this->getCode($secret, $currentTimeSlice + $i);
if ($calculatedCode == $code ) {
return true;
}
}
return false;
}
/**
* Set the code length, should be >=6
*
* @param int $length
* @return GoogleAuthenticator
*/
public function setCodeLength($length)
{
$this->_codeLength = $length;
return $this;
}
/**
* Helper class to decode base32
*
* @param $secret
* @return bool|string
*/
protected function _base32Decode($secret)
{
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($secret, $padding = true)
{
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
*/
protected function _getBase32LookupTable()
{
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
'=' // padding char
);
}
}

View File

@ -1 +1,14 @@
# phpmfa
# MFA / 2FA for PHP (Using GoogleAuthenticator)
Add the GoogleAuthenticator.php file in your application.
Generate a secret and store this in a database with the user. This secret should be unknown to anybody and is needed for the MFA confirmation.
Scan the QR code generated
Confirm the MFA code using your MFA application on your phone.
## Note
For this test you have to put the code you generated into the check_mfa.php file and use a $_GET['code'] to check if the code is working or not.
You should build this using a proper POST!

23
check_mfa.php Normal file
View File

@ -0,0 +1,23 @@
<?php
// Set the secret generated in generate.php, this should be fetched from a DB.
$secret = '';
// Fetching the generated MFA code from your MFA app.
// This as a $_GET value to test it out easily, but should be a POST in production!
echo "<p>Confirm code:</p>";
$code = $_GET['code'];
echo "<p>Code: $code</p>";
$ga = new GoogleAuthenticator();
// Code verify with your secret and the supplied code.
// Discrepancy is set to '2' which is 60 seconds. Default is 1 (30 seconds) but you might be too late to input the code.
$login = $ga->verifyCode($secret, $code, $discrepancy = 2);
// If login returns TRUE
if ($login) {
echo "OK";
} else {
echo "FAILED";
}
?>

17
generate.php Normal file
View File

@ -0,0 +1,17 @@
<?php
require_once('GoogleAuthenticator.php');
echo "<p>Create token:</p>";
$user = 'MyLoginUsername';
// Generate a new secret for the user, this secret should be stored with the user in a database or make a static setting
$ga = new GoogleAuthenticator();
$secret = $ga->createSecret();
echo "<p>This personal secret should be stored in a database/user table and should never be known to anybody: $secret</p>";
echo "<hr>"
// Generate QR by using Google Charts.
$qrCodeUrl = $ga->getQRCodeGoogleUrl($user, $secret, 'My Environment');
echo "<p>Scan in Google Authenticator or Microsoft Authenticator"
echo "<img src='$qrCodeUrl'>";
?>