我已经在一个星期搞砸了PHP上的Google身份验证,并且偶然发现了多个“用户”身份验证的问题。
基本上,我已经下载了Authentication.php代码,并使用它来生成和验证代码。
<?php
class Authenticator
{
protected $length = 6;
public function generateRandomSecret($secretLength = 16)
{
$secret = '';
$validChars = array(
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', '2', '3', '4', '5', '6', '7',
'=',
);
// Valid secret lengths are 80 to 640 bits
if ($secretLength < 16 || $secretLength > 128) {
throw new Exception('Bad secret length');
}
$random = false;
if (function_exists('random_bytes')) {
$random = random_bytes($secretLength);
} elseif (function_exists('mcrypt_create_iv')) {
$random = mcrypt_create_iv($secretLength, MCRYPT_DEV_URANDOM);
} elseif (function_exists('openssl_random_pseudo_bytes')) {
$random = openssl_random_pseudo_bytes($secretLength, $cryptoStrong);
if (!$cryptoStrong) {
$random = false;
}
}
if ($random !== false) {
for ($i = 0; $i < $secretLength; ++$i) {
$secret .= $validChars[ord($random[$i]) & 31];
}
} else {
throw new Exception('Cannot create secure random secret due to source unavailbility');
}
return $secret;
}
public function getCode($secret, $timeSlice = null)
{
if ($timeSlice === null) {
$timeSlice = floor(time() / 30);
}
$secretkey = $this->debase32($secret);
$time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice);
$hm = hash_hmac('SHA1', $time, $secretkey, true);
$offset = ord(substr($hm, -1)) & 0x0F;
$hashpart = substr($hm, $offset, 4);
$value = unpack('N', $hashpart);
$value = $value[1];
$value = $value & 0x7FFFFFFF;
$modulo = pow(10, $this->length);
print_r(str_pad($value % $modulo, $this->length, '0', STR_PAD_LEFT) . '</br>');
return str_pad($value % $modulo, $this->length, '0', STR_PAD_LEFT);
}
public function getQR($name, $secret, $title = null, $params = array())
{
$width = !empty($params['width']) && (int) $params['width'] > 0 ? (int) $params['width'] : 200;
$height = !empty($params['height']) && (int) $params['height'] > 0 ? (int) $params['height'] : 200;
$level = !empty($params['level']) && array_search($params['level'], array('L', 'M', 'Q', 'H')) !== false ? $params['level'] : 'M';
$urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.'');
if (isset($title)) {
$urlencoded .= urlencode('&issuer='.urlencode($title));
}
return 'https://chart.googleapis.com/chart?chs='.$width.'x'.$height.'&chld='.$level.'|0&cht=qr&chl='.$urlencoded.'';
}
public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null)
{
if ($currentTimeSlice === null) {
$currentTimeSlice = floor(time() / 30);
}
if (strlen($code) != 6) {
return false;
}
for ($i = -$discrepancy; $i <= $discrepancy; ++$i) {
$calculatedCode = $this->getCode($secret, $currentTimeSlice + $i);
if ($this->timingSafeEquals($calculatedCode, $code)) {
print_r('This works!');
return true;
}
}
return false;
}
public function setCodeLength($length)
{
$this->length = $length;
return $this;
}
protected function debase32($secret)
{
if (empty($secret)) {
return '';
}
$base32chars = array(
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', '2', '3', '4', '5', '6', '7',
'=',
);
$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;
}
private function timingSafeEquals($safeString, $userString)
{
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]));
}
return $result === 0;
}
}
因此,只要我的数据库中只有一个用户(它是.txt文件),所有这些身份验证都将起作用,但是如果我添加另一个具有其唯一代码的用户,则先前的用户无法使用身份验证代码登录。
这是check.php文件:
<?php
session_start();
require "Authenticator.php";
if ($_SERVER['REQUEST_METHOD'] != "POST") {
header("location: authentication.php");
die();
}
$Authenticator = new Authenticator();
$checkResult = $Authenticator->verifyCode($_SESSION['auth_secret'], $_POST['code'], 2); // 2 = 2*30sec clock tolerance
if (!$checkResult) {
$_SESSION['failed'] = true;
//header("location: authentication.php");
print_r($_SESSION['auth_secret'] . ' -- ' . $_POST['code']);
//die();
} else {
print_r('VEIKIASD');
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Authentication Successful</title>
</head>
<body>
<hr>
<div style="text-align: center;">
<h1>Authentication Successful</h1>
<p>Thanks for using our sample Time-based Authenticator</p>
</div>
</body>
</html>
如果缺少任何信息,我会很乐意添加。 注意:我正在使用XAMPP托管本地网站,也许这可能是问题所在?
答案 0 :(得分:0)
似乎问题出在我的秘密代码毕竟是读物上。
问题在于user.txt文件格式如下:
foo:fooSecret
bar:barSecret
etc:etcSecret
仔细检查了$_SESSION['auth_secret']
变量后,我发现了空白!