前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >超简单实现php谷歌验证

超简单实现php谷歌验证

作者头像
C4rpeDime
发布2018-12-06 16:20:11
3.8K0
发布2018-12-06 16:20:11
举报
文章被收录于专栏:黑白安全黑白安全

前几天才得知有google身份验证器这种好玩的东西,这是一个类似QQ令牌的离线验证码,不需要联网。只需要时区同步和SecretKey一致即可计算出离线验证码,简单方便安全便捷,感兴趣的朋友可以试试。

1、首先保存以下源码为GoogleAuthenticator.php文件

代码语言:javascript
复制
<?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 * * @link http://www.phpgangsta.de/ */class PHPGangsta_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();

        // 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;
    }

    /**     * 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     * @param array  $params     *     * @return string     */
    public function getQRCodeGoogleUrl($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.'';
    }

    /**     * 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);
        }

        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;
    }

    /**     * Set the code length, should be >=6.     *     * @param int $length     *     * @return PHPGangsta_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;
    }

    /**     * 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
        );
    }

    /**     * A timing safe equals comparison     * more info here: http://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($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]));
        }

        // They are only identical strings if $result is exactly 0...
        return $result === 0;
    }}

2、保存以下代码为logo.php

代码语言:javascript
复制
<?php if(!empty($_POST['user'])&&!empty($_POST['pass'])&&!empty($_POST['Verification'])){
	require_once './GoogleAuthenticator.php';

	$ga = new PHPGangsta_GoogleAuthenticator();

	//"安全密匙SecretKey" 入库,和账户关系绑定,客户端也是绑定这同一个"安全密匙SecretKey"
	$secret = 'QLE4O7GHUJLT63ARS';

	$oneCode = $_POST['Verification'];
	$checkResult = $ga->verifyCode($secret, $oneCode, 2);
	if($checkResult){
		$user = $_POST['user'];
		$pass = $_POST['pass'];
		if($user=='admin'&&$pass=='admin000'){
			exit('登录成功');
		}else{
			exit('密码错误');
		}
	}else{
		exit('验证码错误');
	}}?><!DOCTYPE html><html><head>
	<title>登录</title></head><body>
	<div>
		<form action="login.php" method="post">
			<p>账号:<input type="text" name="user" placeholder="账号"></p>
			<p>密码:<input type="pass" name="pass"></p>
			<p>验证码:<input type="number" name="Verification"></p>
			<input type="submit" value="提交">
		</form>
	</div></body></html>

此时安装Google身份验证器到手机上(安卓版可以去网上下载,苹果可以在商城应用商城搜到),然后绑定SecretKey即可使用。

当然多用户也可以使用该功能,只需要给每个用户绑定一个SecretKey即可。

如果需要生成二维码可以使用以下代码生成

代码语言:javascript
复制
$qrCodeUrl = $ga->getQRCodeGoogleUrl('localhost', $secret); //第一个参数是"标识",第二个参数为"安全密匙SecretKey" 生成二维码信息echo '<img src="'.$qrCodeUrl.'">';

教程到此结束。

图片预览

超简单实现php谷歌验证
超简单实现php谷歌验证
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、首先保存以下源码为GoogleAuthenticator.php文件
  • 2、保存以下代码为logo.php
  • 图片预览
相关产品与服务
验证码
腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档