[ Index ]

PHP Cross Reference of MyBB 1.8.38

title

Body

[close]

/inc/3rdparty/2fa/ -> GoogleAuthenticator.php (source)

   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  }


2005 - 2021 © MyBB.de | Alle Rechte vorbehalten! | Sponsor: netcup Cross-referenced by PHPXref