2
0

googleAuthenticator.class.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. <?php
  2. class googleAuthenticator
  3. {
  4. static protected $_codeLength = 6;
  5. static public function createSecret($secretLength = 16)
  6. {
  7. $validChars = self::_getBase32LookupTable();
  8. // Valid secret lengths are 80 to 640 bits
  9. if ($secretLength < 16 || $secretLength > 128) {
  10. throw new Exception('Bad secret length');
  11. }
  12. $secret = '';
  13. $rnd = false;
  14. if (function_exists('random_bytes')) {
  15. $rnd = random_bytes($secretLength);
  16. } elseif (function_exists('openssl_random_pseudo_bytes')) {
  17. $rnd = openssl_random_pseudo_bytes($secretLength, $cryptoStrong);
  18. if (!$cryptoStrong) {
  19. $rnd = false;
  20. }
  21. }
  22. if ($rnd !== false) {
  23. for ($i = 0; $i < $secretLength; ++$i) {
  24. $secret .= $validChars[ord($rnd[$i]) & 31];
  25. }
  26. } else {
  27. throw new Exception('No source of secure random');
  28. }
  29. return $secret;
  30. }
  31. static public function getCode($secret, $timeSlice = null)
  32. {
  33. if ($timeSlice === null) {
  34. $timeSlice = floor(time() / 30);
  35. }
  36. $secretkey = self::_base32Decode($secret);
  37. // Pack time into binary string
  38. $time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice);
  39. // Hash it with users secret key
  40. $hm = hash_hmac('SHA1', $time, $secretkey, true);
  41. // Use last nipple of result as index/offset
  42. $offset = ord(substr($hm, -1)) & 0x0F;
  43. // grab 4 bytes of the result
  44. $hashpart = substr($hm, $offset, 4);
  45. // Unpak binary value
  46. $value = unpack('N', $hashpart);
  47. $value = $value[1];
  48. // Only 32 bits
  49. $value = $value & 0x7FFFFFFF;
  50. $modulo = pow(10, self::$_codeLength);
  51. return str_pad($value % $modulo, self::$_codeLength, '0', STR_PAD_LEFT);
  52. }
  53. static public function getGoogleUrl($name, $secret)
  54. {
  55. $urlencoded = 'otpauth://totp/'.$name.'?secret='.$secret;
  56. return $urlencoded;
  57. }
  58. static public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = null)
  59. {
  60. if ($currentTimeSlice === null) {
  61. $currentTimeSlice = floor(time() / 30);
  62. }
  63. if (strlen($code) != 6) {
  64. return false;
  65. }
  66. for ($i = -$discrepancy; $i <= $discrepancy; ++$i) {
  67. $calculatedCode = self::getCode($secret, $currentTimeSlice + $i);
  68. if (self::timingSafeEquals($calculatedCode, $code)) {
  69. return true;
  70. }
  71. }
  72. return false;
  73. }
  74. static public function setCodeLength($length)
  75. {
  76. return self::$_codeLength = $length;
  77. }
  78. static protected function _base32Decode($secret)
  79. {
  80. if (empty($secret)) {
  81. return '';
  82. }
  83. $base32chars = self::_getBase32LookupTable();
  84. $base32charsFlipped = array_flip($base32chars);
  85. $paddingCharCount = substr_count($secret, $base32chars[32]);
  86. $allowedValues = array(6, 4, 3, 1, 0);
  87. if (!in_array($paddingCharCount, $allowedValues)) {
  88. return false;
  89. }
  90. for ($i = 0; $i < 4; ++$i) {
  91. if ($paddingCharCount == $allowedValues[$i] &&
  92. substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) {
  93. return false;
  94. }
  95. }
  96. $secret = str_replace('=', '', $secret);
  97. $secret = str_split($secret);
  98. $binaryString = '';
  99. for ($i = 0; $i < count($secret); $i = $i + 8) {
  100. $x = '';
  101. if (!in_array($secret[$i], $base32chars)) {
  102. return false;
  103. }
  104. for ($j = 0; $j < 8; ++$j) {
  105. $x .= str_pad(base_convert(@$base32charsFlipped[@$secret[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
  106. }
  107. $eightBits = str_split($x, 8);
  108. for ($z = 0; $z < count($eightBits); ++$z) {
  109. $binaryString .= (($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48) ? $y : '';
  110. }
  111. }
  112. return $binaryString;
  113. }
  114. static protected function _getBase32LookupTable()
  115. {
  116. return array(
  117. 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7
  118. 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15
  119. 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23
  120. 'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31
  121. '=', // padding char
  122. );
  123. }
  124. static private function timingSafeEquals($safeString, $userString)
  125. {
  126. if (function_exists('hash_equals')) {
  127. return hash_equals($safeString, $userString);
  128. }
  129. $safeLen = strlen($safeString);
  130. $userLen = strlen($userString);
  131. if ($userLen != $safeLen) {
  132. return false;
  133. }
  134. $result = 0;
  135. for ($i = 0; $i < $userLen; ++$i) {
  136. $result |= (ord($safeString[$i]) ^ ord($userString[$i]));
  137. }
  138. // They are only identical strings if $result is exactly 0...
  139. return $result === 0;
  140. }
  141. }