如何在PHP中使用Google Authenticator生成唯一的QR码?

时间:2019-05-27 07:28:50

标签: php mysql qr-code google-authenticator

希望我们的PHP系统上的每个用户都拥有唯一的QR码。我已经实现了Google Authenticator系统,但是它只能为每个用户生成相同的QR码,并且一个用户可以使用他们的代码登录到另一个用户的帐户。

如何让Google Authenticator为每个用户生成唯一的代码?我需要传递什么值/变量给Google Authenticator才能为我们系统上的每个用户帐户提供唯一的代码?

我们尝试传递用户的电子邮件地址或用户名,但是尽管电子邮件地址是唯一的,但身份验证器仍会发送相同的QR码。

[1]使用username,用户名变量名是$ name

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)) {
                return true;
            }
        }

        return false;
    }

[2]使用电子邮件,电子邮件变量名称为$ email

public function getQR($email, $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/'.$email.'?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)) {
                return true;
            }
        }

        return false;
    }

我希望该代码为每个用户生成一个唯一的QR码,以便每个用户在手机上的Google身份验证器应用中都有自己的身份验证代码。

2 个答案:

答案 0 :(得分:0)

秘密无非就是一个随机的base32字符串。 因此可以像

一样简单
MyCompletableFuture.supplyAsync(() -> "test")
    .thenApplyAsync(String::toUpperCase)
    .thenAcceptAsync(System.out::println);

其中“ ABCDEFGHIJKLMNOPQRSTUVWXYZ234567”是base32字母

答案 1 :(得分:0)

您的代码看起来与 https://github.com/PHPGangsta/GoogleAuthenticator 非常相似 导致不同用户使用相似 QR 码的问题是您为他们使用相同的秘密。

每个用户都必须拥有唯一的秘密!

PHPGangsta 有一个生成伪随机秘密的函数:

public function createSecret($secretLength = 16)
{
    $validChars = $this->_getBase32LookupTable();

    // Valid secret lengths are 80 to 640 bits
    if ($secretLength < 16 || $secretLength > 128) {
        throw new Exception('Bad secret length');
    }
    $secret = '';
    $rnd = false;
    if (function_exists('random_bytes')) {
        $rnd = random_bytes($secretLength);
    } elseif (function_exists('mcrypt_create_iv')) {
        $rnd = mcrypt_create_iv($secretLength, MCRYPT_DEV_URANDOM);
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
        $rnd = openssl_random_pseudo_bytes($secretLength, $cryptoStrong);
        if (!$cryptoStrong) {
            $rnd = false;
        }
    }
    if ($rnd !== false) {
        for ($i = 0; $i < $secretLength; ++$i) {
            $secret .= $validChars[ord($rnd[$i]) & 31];
        }
    } else {
        throw new Exception('No source of secure random');
    }

    return $secret;
}