123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- <?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();
- unset($validChars[32]);
- $secret = '';
- for ($i = 0; $i < $secretLength; $i++) {
- $secret .= $validChars[array_rand($validChars)];
- }
- 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
- * @return string
- */
- public function getQRCodeGoogleUrl($name, $secret) {
- $urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.'');
- return 'https://chart.googleapis.com/chart?chs=200x200&chld=M|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)
- * @return bool
- */
- public function verifyCode($secret, $code, $discrepancy = 1)
- {
- $currentTimeSlice = floor(time() / 30);
- for ($i = -$discrepancy; $i <= $discrepancy; $i++) {
- $calculatedCode = $this->getCode($secret, $currentTimeSlice + $i);
- if ($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;
- }
- /**
- * Helper class to encode base32
- *
- * @param string $secret
- * @param bool $padding
- * @return string
- */
- protected function _base32Encode($secret, $padding = true)
- {
- if (empty($secret)) return '';
- $base32chars = $this->_getBase32LookupTable();
- $secret = str_split($secret);
- $binaryString = "";
- for ($i = 0; $i < count($secret); $i++) {
- $binaryString .= str_pad(base_convert(ord($secret[$i]), 10, 2), 8, '0', STR_PAD_LEFT);
- }
- $fiveBitBinaryArray = str_split($binaryString, 5);
- $base32 = "";
- $i = 0;
- while ($i < count($fiveBitBinaryArray)) {
- $base32 .= $base32chars[base_convert(str_pad($fiveBitBinaryArray[$i], 5, '0'), 2, 10)];
- $i++;
- }
- if ($padding && ($x = strlen($binaryString) % 40) != 0) {
- if ($x == 8) $base32 .= str_repeat($base32chars[32], 6);
- elseif ($x == 16) $base32 .= str_repeat($base32chars[32], 4);
- elseif ($x == 24) $base32 .= str_repeat($base32chars[32], 3);
- elseif ($x == 32) $base32 .= $base32chars[32];
- }
- return $base32;
- }
- /**
- * 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
- );
- }
- }
|