[ Index ] |
PHP Cross Reference of MyBB 1.8.38 |
[Summary view] [Print] [Text view]
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 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 $width = !empty($params['width']) && (int) $params['width'] > 0 ? (int) $params['width'] : 200; 104 $height = !empty($params['height']) && (int) $params['height'] > 0 ? (int) $params['height'] : 200; 105 $level = !empty($params['level']) && array_search($params['level'], array('L', 'M', 'Q', 'H')) !== false ? $params['level'] : 'M'; 106 107 $urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.''); 108 if (isset($title)) { 109 $urlencoded .= urlencode('&issuer='.urlencode($title)); 110 } 111 112 return 'https://api.qrserver.com/v1/create-qr-code/?data='.$urlencoded.'&size='.$width.'x'.$height.'&ecc='.$level; 113 } 114 115 /** 116 * Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now. 117 * 118 * @param string $secret 119 * @param string $code 120 * @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after) 121 * @param int|null $currentTimeSlice time slice if we want use other that time() 122 * 123 * @return bool 124 */ 125 public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null) 126 { 127 if ($currentTimeSlice === null) { 128 $currentTimeSlice = floor(time() / 30); 129 } 130 131 if (strlen($code) != 6) { 132 return false; 133 } 134 135 for ($i = -$discrepancy; $i <= $discrepancy; ++$i) { 136 $calculatedCode = $this->getCode($secret, $currentTimeSlice + $i); 137 if ($this->timingSafeEquals($calculatedCode, $code)) { 138 return true; 139 } 140 } 141 142 return false; 143 } 144 145 /** 146 * Set the code length, should be >=6. 147 * 148 * @param int $length 149 * 150 * @return PHPGangsta_GoogleAuthenticator 151 */ 152 public function setCodeLength($length) 153 { 154 $this->_codeLength = $length; 155 156 return $this; 157 } 158 159 /** 160 * Helper class to decode base32. 161 * 162 * @param $secret 163 * 164 * @return bool|string 165 */ 166 protected function _base32Decode($secret) 167 { 168 if (empty($secret)) { 169 return ''; 170 } 171 172 $base32chars = $this->_getBase32LookupTable(); 173 $base32charsFlipped = array_flip($base32chars); 174 175 $paddingCharCount = substr_count($secret, $base32chars[32]); 176 $allowedValues = array(6, 4, 3, 1, 0); 177 if (!in_array($paddingCharCount, $allowedValues)) { 178 return false; 179 } 180 for ($i = 0; $i < 4; ++$i) { 181 if ($paddingCharCount == $allowedValues[$i] && 182 substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) { 183 return false; 184 } 185 } 186 $secret = str_replace('=', '', $secret); 187 $secret = str_split($secret); 188 $binaryString = ''; 189 for ($i = 0; $i < count($secret); $i = $i + 8) { 190 $x = ''; 191 if (!in_array($secret[$i], $base32chars)) { 192 return false; 193 } 194 for ($j = 0; $j < 8; ++$j) { 195 $x .= str_pad(base_convert(@$base32charsFlipped[@$secret[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT); 196 } 197 $eightBits = str_split($x, 8); 198 for ($z = 0; $z < count($eightBits); ++$z) { 199 $binaryString .= (($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48) ? $y : ''; 200 } 201 } 202 203 return $binaryString; 204 } 205 206 /** 207 * Get array with all 32 characters for decoding from/encoding to base32. 208 * 209 * @return array 210 */ 211 protected function _getBase32LookupTable() 212 { 213 return array( 214 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7 215 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15 216 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23 217 'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31 218 '=', // padding char 219 ); 220 } 221 222 /** 223 * A timing safe equals comparison 224 * more info here: http://blog.ircmaxell.com/2014/11/its-all-about-time.html. 225 * 226 * @param string $safeString The internal (safe) value to be checked 227 * @param string $userString The user submitted (unsafe) value 228 * 229 * @return bool True if the two strings are identical 230 */ 231 private function timingSafeEquals($safeString, $userString) 232 { 233 if (function_exists('hash_equals')) { 234 return hash_equals($safeString, $userString); 235 } 236 $safeLen = strlen($safeString); 237 $userLen = strlen($userString); 238 239 if ($userLen != $safeLen) { 240 return false; 241 } 242 243 $result = 0; 244 245 for ($i = 0; $i < $userLen; ++$i) { 246 $result |= (ord($safeString[$i]) ^ ord($userString[$i])); 247 } 248 249 // They are only identical strings if $result is exactly 0... 250 return $result === 0; 251 } 252 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
2005 - 2021 © MyBB.de | Alle Rechte vorbehalten! | Sponsor: netcup | Cross-referenced by PHPXref |