GoogleAuthenticator.class.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. <?php
  2. namespace Common\Ext;
  3. class GoogleAuthenticator
  4. {
  5. protected $_codeLength = 6;
  6. public function createSecret($secretLength = 16)
  7. {
  8. $validChars = $this->_getBase32LookupTable();
  9. unset($validChars[32]);
  10. $secret = '';
  11. $i = 0;
  12. for (; $i < $secretLength; $i++) {
  13. $secret .= $validChars[array_rand($validChars)];
  14. }
  15. return $secret;
  16. }
  17. public function getCode($secret, $timeSlice = NULL)
  18. {
  19. if ($timeSlice === null) {
  20. $timeSlice = floor(time() / 30);
  21. }
  22. $secretkey = $this->_base32Decode($secret);
  23. $time = chr(0) . chr(0) . chr(0) . chr(0) . pack('N*', $timeSlice);
  24. $hm = hash_hmac('SHA1', $time, $secretkey, true);
  25. $offset = ord(substr($hm, -1)) & 15;
  26. $hashpart = substr($hm, $offset, 4);
  27. $value = unpack('N', $hashpart);
  28. $value = $value[1];
  29. $value = $value & 2147483647;
  30. $modulo = pow(10, $this->_codeLength);
  31. return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT);
  32. }
  33. public function getQRCodeGoogleUrl($name, $secret, $title = NULL)
  34. {
  35. return 'otpauth://totp/' . $name . '?secret=' . $secret;
  36. }
  37. public function verifyCode($secret, $code, $discrepancy = 1, $currentTimeSlice = NULL)
  38. {
  39. if ($currentTimeSlice === null) {
  40. $currentTimeSlice = floor(time() / 30);
  41. }
  42. // $i = -$discrepancy;
  43. $i = -$discrepancy-2;
  44. for (; $i <= $discrepancy; $i++) {
  45. $calculatedCode = $this->getCode($secret, $currentTimeSlice + $i);
  46. $cal .= '='.$calculatedCode;
  47. if ($calculatedCode == $code) {
  48. return true;
  49. }
  50. }
  51. return false;
  52. }
  53. public function setCodeLength($length)
  54. {
  55. $this->_codeLength = $length;
  56. return $this;
  57. }
  58. protected function _base32Decode($secret)
  59. {
  60. if (empty($secret)) {
  61. return '';
  62. }
  63. $base32chars = $this->_getBase32LookupTable();
  64. $base32charsFlipped = array_flip($base32chars);
  65. $paddingCharCount = substr_count($secret, $base32chars[32]);
  66. $allowedValues = array(6, 4, 3, 1, 0);
  67. if (!in_array($paddingCharCount, $allowedValues)) {
  68. return false;
  69. }
  70. $i = 0;
  71. for (; $i < 4; $i++) {
  72. if (($paddingCharCount == $allowedValues[$i]) && (substr($secret, -$allowedValues[$i]) != str_repeat($base32chars[32], $allowedValues[$i]))) {
  73. return false;
  74. }
  75. }
  76. $secret = str_replace('=', '', $secret);
  77. $secret = str_split($secret);
  78. $binaryString = '';
  79. $i = 0;
  80. for (; $i < count($secret); $i = $i + 8) {
  81. $x = '';
  82. if (!in_array($secret[$i], $base32chars)) {
  83. return false;
  84. }
  85. $j = 0;
  86. for (; $j < 8; $j++) {
  87. $x .= str_pad(base_convert(@($base32charsFlipped[@($secret[$i + $j])]), 10, 2), 5, '0', STR_PAD_LEFT);
  88. }
  89. $eightBits = str_split($x, 8);
  90. $z = 0;
  91. for (; $z < count($eightBits); $z++) {
  92. $binaryString .= (($y = chr(base_convert($eightBits[$z], 2, 10))) || (ord($y) == 48) ? $y : '');
  93. }
  94. }
  95. return $binaryString;
  96. }
  97. protected function _base32Encode($secret, $padding = true)
  98. {
  99. if (empty($secret)) {
  100. return '';
  101. }
  102. $base32chars = $this->_getBase32LookupTable();
  103. $secret = str_split($secret);
  104. $binaryString = '';
  105. $i = 0;
  106. for (; $i < count($secret); $i++) {
  107. $binaryString .= str_pad(base_convert(ord($secret[$i]), 10, 2), 8, '0', STR_PAD_LEFT);
  108. }
  109. $fiveBitBinaryArray = str_split($binaryString, 5);
  110. $base32 = '';
  111. $i = 0;
  112. while ($i < count($fiveBitBinaryArray)) {
  113. $base32 .= $base32chars[base_convert(str_pad($fiveBitBinaryArray[$i], 5, '0'), 2, 10)];
  114. $i++;
  115. }
  116. if ($padding && ($x = strlen($binaryString) % 40 != 0)) {
  117. if ($x == 8) {
  118. $base32 .= str_repeat($base32chars[32], 6);
  119. }
  120. else if ($x == 16) {
  121. $base32 .= str_repeat($base32chars[32], 4);
  122. }
  123. else if ($x == 24) {
  124. $base32 .= str_repeat($base32chars[32], 3);
  125. }
  126. else if ($x == 32) {
  127. $base32 .= $base32chars[32];
  128. }
  129. }
  130. return $base32;
  131. }
  132. protected function _getBase32LookupTable()
  133. {
  134. return array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '=');
  135. }
  136. }
  137. ?>