首先介绍一下谷歌验证器,就相当于给一个条网站登录的页面加了2把锁,第一把是自己的账号密码,第二把是谷歌验证器生成的数字code(875 265)
接下来如何使用,调用GoogleAuthenticator.php 类,下面看一个简单的demo
<?php session_start(); require_once './GoogleAuthenticator.php'; $ga = new PHPGangsta_GoogleAuthenticator(); if(!empty($_POST['user'])&&!empty($_POST['pass'])&&!empty($_POST['Verification'])){ //"安全密匙SecretKey" 入库,和账户关系绑定,客户端也是绑定这同一个"安全密匙SecretKey" $secret = $_SESSION['login_secret']; //第一个参数是"标识",第二个参数为"安全密匙SecretKey" 生成二维码信息 $oneCode = $_POST['Verification']; $checkResult = $ga->verifyCode($secret, $oneCode, 2); if($checkResult){ $user = $_POST['user']; $pass = $_POST['pass']; if($user=='admin'&&$pass=='123456'){ exit('登录成功'); }else{ exit('密码错误'); } }else{ exit('验证码错误'); } }else{ $secret = $ga->createSecret(); $_SESSION["login_secret"] = $secret; $qrCodeUrl = $ga->getQRCodeGoogleUrl('https://bb.fswkh.cn/', $secret); } ?> <!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> <h2>验证码</h2> <img src="<?php echo $qrCodeUrl; ?> "> </div> </body> </html>
1 <?php 2 3 /** 4 * PHP Class for handling Google Authenticator 2-factor authentication. 5 * 6 * @author Michael Kliewe 7 * @copyright 2012 Michael Kliewe 8 * @license http://www.opensource.org/licenses/bsd-license.php BSD License 9 * 10 * @link http://www.phpgangsta.de/ 11 */ 12 class PHPGangsta_GoogleAuthenticator 13 { 14 protected $_codeLength = 6; 15 16 /** 17 * Create new secret. 18 * 16 characters, randomly chosen from the allowed base32 characters. 19 * 20 * @param int $secretLength 21 * 22 * @return string 23 */ 24 public function createSecret($secretLength = 16) 25 { 26 $validChars = $this->_getBase32LookupTable(); 27 28 // Valid secret lengths are 80 to 640 bits 29 if ($secretLength < 16 || $secretLength > 128) { 30 throw new Exception('Bad secret length'); 31 } 32 $secret = ''; 33 $rnd = false; 34 if (function_exists('random_bytes')) { 35 $rnd = random_bytes($secretLength); 36 } elseif (function_exists('mcrypt_create_iv')) { 37 $rnd = mcrypt_create_iv($secretLength, MCRYPT_DEV_URANDOM); 38 } elseif (function_exists('openssl_random_pseudo_bytes')) { 39 $rnd = openssl_random_pseudo_bytes($secretLength, $cryptoStrong); 40 if (!$cryptoStrong) { 41 $rnd = false; 42 } 43 } 44 if ($rnd !== false) { 45 for ($i = 0; $i < $secretLength; ++$i) { 46 $secret .= $validChars[ord($rnd[$i]) & 31]; 47 } 48 } else { 49 throw new Exception('No source of secure random'); 50 } 51 var_dump($secret); 52 return $secret; 53 } 54 55 /** 56 * Calculate the code, with given secret and point in time. 57 * 58 * @param string $secret 59 * @param int|null $timeSlice 60 * 61 * @return string 62 */ 63 public function getCode($secret, $timeSlice = null) 64 { 65 if ($timeSlice === null) { 66 $timeSlice = floor(time() / 30); 67 } 68 69 $secretkey = $this->_base32Decode($secret); 70 71 // Pack time into binary string 72 $time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice); 73 // Hash it with users secret key 74 $hm = hash_hmac('SHA1', $time, $secretkey, true); 75 // Use last nipple of result as index/offset 76 $offset = ord(substr($hm, -1)) & 0x0F; 77 // grab 4 bytes of the result 78 $hashpart = substr($hm, $offset, 4); 79 80 // Unpak binary value 81 $value = unpack('N', $hashpart); 82 $value = $value[1]; 83 // Only 32 bits 84 $value = $value & 0x7FFFFFFF; 85 86 $modulo = pow(10, $this->_codeLength); 87 88 return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT); 89 } 90 91 /** 92 * Get QR-Code URL for image, from google charts. 93 * 94 * @param string $name 95 * @param string $secret 96 * @param string $title 97 * @param array $params 98 * 99 * @return string 100 */ 101 public function getQRCodeGoogleUrl($name, $secret, $title = null, $params = array()) 102 { 103 var_dump($secret); 104 $width = !empty($params['width']) && (int) $params['width'] > 0 ? (int) $params['width'] : 200; 105 $height = !empty($params['height']) && (int) $params['height'] > 0 ? (int) $params['height'] : 200; 106 $level = !empty($params['level']) && array_search($params['level'], array('L', 'M', 'Q', 'H')) !== false ? $params['level'] : 'M'; 107 108 $urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.''); 109 if (isset($title)) { 110 $urlencoded .= urlencode('&issuer='.urlencode($title)); 111 } 112 113 return 'https://chart.googleapis.com/chart?chs='.$width.'x'.$height.'&chld='.$level.'|0&cht=qr&chl='.$urlencoded.''; 114 } 115 116 /** 117 * Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now. 118 * 119 * @param string $secret 120 * @param string $code 121 * @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after) 122 * @param int|null $currentTimeSlice time slice if we want use other that time() 123 * 124 * @return bool 125 */ 126 public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null) 127 { 128 if ($currentTimeSlice === null) { 129 $currentTimeSlice = floor(time() / 30); 130 } 131 132 if (strlen($code) != 6) { 133 return false; 134 } 135 136 for ($i = -$discrepancy; $i <= $discrepancy; ++$i) { 137 $calculatedCode = $this->getCode($secret, $currentTimeSlice + $i); 138 if ($this->timingSafeEquals($calculatedCode, $code)) { 139 return true; 140 } 141 } 142 143 return false; 144 } 145 146 /** 147 * Set the code length, should be >=6. 148 * 149 * @param int $length 150 * 151 * @return PHPGangsta_GoogleAuthenticator 152 */ 153 public function setCodeLength($length) 154 { 155 $this->_codeLength = $length; 156 157 return $this; 158 } 159 160 /** 161 * Helper class to decode base32. 162 * 163 * @param $secret 164 * 165 * @return bool|string 166 */ 167 protected function _base32Decode($secret) 168 { 169 if (empty($secret)) { 170 return ''; 171 } 172 173 $base32chars = $this->_getBase32LookupTable(); 174 $base32charsFlipped = array_flip($base32chars); 175 176 $paddingCharCount = substr_count($secret, $base32chars[32]); 177 $allowedValues = array(6, 4, 3, 1, 0); 178 if (!in_array($paddingCharCount, $allowedValues)) { 179 return false; 180 } 181 for ($i = 0; $i < 4; ++$i) { 182 if ($paddingCharCount == $allowedValues[$i] && 183 substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) { 184 return false; 185 } 186 } 187 $secret = str_replace('=', '', $secret); 188 $secret = str_split($secret); 189 $binaryString = ''; 190 for ($i = 0; $i < count($secret); $i = $i + 8) { 191 $x = ''; 192 if (!in_array($secret[$i], $base32chars)) { 193 return false; 194 } 195 for ($j = 0; $j < 8; ++$j) { 196 $x .= str_pad(base_convert(@$base32charsFlipped[@$secret[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT); 197 } 198 $eightBits = str_split($x, 8); 199 for ($z = 0; $z < count($eightBits); ++$z) { 200 $binaryString .= (($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48) ? $y : ''; 201 } 202 } 203 204 return $binaryString; 205 } 206 207 /** 208 * Get array with all 32 characters for decoding from/encoding to base32. 209 * 210 * @return array 211 */ 212 protected function _getBase32LookupTable() 213 { 214 return array( 215 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7 216 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15 217 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23 218 'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31 219 '=', // padding char 220 ); 221 } 222 223 /** 224 * A timing safe equals comparison 225 * more info here: http://blog.ircmaxell.com/2014/11/its-all-about-time.html. 226 * 227 * @param string $safeString The internal (safe) value to be checked 228 * @param string $userString The user submitted (unsafe) value 229 * 230 * @return bool True if the two strings are identical 231 */ 232 private function timingSafeEquals($safeString, $userString) 233 { 234 if (function_exists('hash_equals')) { 235 return hash_equals($safeString, $userString); 236 } 237 $safeLen = strlen($safeString); 238 $userLen = strlen($userString); 239 240 if ($userLen != $safeLen) { 241 return false; 242 } 243 244 $result = 0; 245 246 for ($i = 0; $i < $userLen; ++$i) { 247 $result |= (ord($safeString[$i]) ^ ord($userString[$i])); 248 } 249 250 // They are only identical strings if $result is exactly 0... 251 return $result === 0; 252 } 253 }
看看最后的效果截图,最后扫码的这个APP 就是谷歌验证器了,大家可以自己到应用中心下载。