<?phpnamespaceapp\api\common\exception;classGoogleAuthenticator{protected$_codeLength=6;/**
* Create new secret.
* 16 characters, randomly chosen from the allowed base32 characters.
*
* @param int $secretLength
*
* @return string
*/publicfunctioncreateSecret($secretLength=16){$validChars=$this->_getBase32LookupTable();// Valid secret lengths are 80 to 640 bitsif($secretLength<16||$secretLength>128){thrownewException('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{thrownewException('No source of secure random');}return$secret;}//W57P4SQWCKK7JSN6 /**
* Calculate the code, with given secret and point in time.
*
* @param string $secret
* @param int|null $timeSlice
*
* @return string
*/publicfunctiongetCode($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);returnstr_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
*/publicfunctiongetQRCodeGoogleUrl($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$urlencoded=urlencode('otpauth://totp/'.$name.'?secret='.$secret.'');// 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
*/publicfunctionverifyCode($secret,$code,$discrepancy=1,$currentTimeSlice=null){if($currentTimeSlice===null){$currentTimeSlice=floor(time()/30);}if(strlen($code)!=6){returnfalse;}for($i=-$discrepancy;$i<=$discrepancy;++$i){$calculatedCode=$this->getCode($secret,$currentTimeSlice+$i);if($this->timingSafeEquals($calculatedCode,$code)){returntrue;}}returnfalse;}/** //
* Set the code length, should be >=6.
*
* @param int $length
*
* @return PHPGangsta_GoogleAuthenticator
*/publicfunctionsetCodeLength($length){$this->_codeLength=$length;return$this;}/**
* 内部调用
* Helper class to decode base32.
*
* @param $secret
*
* @return bool|string
*/protectedfunction_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)){returnfalse;}for($i=0;$i<4;++$i){if($paddingCharCount==$allowedValues[$i]&&substr($secret,-($allowedValues[$i]))!=str_repeat($base32chars[32],$allowedValues[$i])){returnfalse;}}$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)){returnfalse;}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
*/protectedfunction_getBase32LookupTable(){returnarray('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
*/privatefunctiontimingSafeEquals($safeString,$userString){if(function_exists('hash_equals')){returnhash_equals($safeString,$userString);}$safeLen=strlen($safeString);$userLen=strlen($userString);if($userLen!=$safeLen){returnfalse;}$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;}}// $nea= new PHPGangsta_GoogleAuthenticator();// // print_r( $nea->createSecret() );// echo "<br>";// // print_r( $nea->getCode('W57P4SQWCKK7JSN6') );// print_r( $nea->getQRCodeGoogleUrl('22222','W57P4SQWCKK7JSN6') );// // print_r( $nea->verifyCode('W57P4SQWCKK7JSN6','512056') );