php 통합 동적 구령 인증

대부분의 시스템 이 현재 사용 하고 있 는 정적 암 호 는 신분 인증 로그 인 을 하지만 정적 암 호 는 쉽게 도 둑 맞 기 때문에 안전성 이 안전 요 구 를 만족 시 킬 수 없다.
동적 암 호 는 비밀 번 호 를 사용 하여 폐기 하 는 방식 으로 비밀 번 호 를 훔 쳐 서 가 져 오 는 안전 문 제 를 방지 했다.
동적 구령 은 HOTP(이벤트 계수 기반 동적 구령,RFC 4226),TOTP(시간 계수 기반 동적 구령,RFC 6238),OCRA(도전 응답 식 동적 구령,RFC 6287)등 으로 나 뉜 다.
본 고 는 TOP 방식 의 동적 구령 인증 을 통합 하 는 방안 을 소개 했다.PHP 프레임 워 크 는 Thinkphp 3.2.3 을 사용 하고 동적 구령 생 성 기 는 google authttication 을 사용한다.
1.Thinkphp 프레임 워 크 에 oath 알고리즘 클래스 추가
oath 알고리즘 패키지 클래스 oath.php 코드 는 다음 과 같 습 니 다.

<?PHP
/**
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * PHP Google two-factor authentication module.
 *
 * See http://www.idontplaydarts.com/2011/07/google-totp-two-factor-authentication-for-php/
 * for more details
 *
 * @author Phil
 **/

class Google2FA {

 const keyRegeneration  = 30; // Interval between key regeneration
 const otpLength  = 6; // Length of the Token generated

 private static $lut = array( // Lookup needed for Base32 encoding
  "A" => 0, "B" => 1,
  "C" => 2, "D" => 3,
  "E" => 4, "F" => 5,
  "G" => 6, "H" => 7,
  "I" => 8, "J" => 9,
  "K" => 10, "L" => 11,
  "M" => 12, "N" => 13,
  "O" => 14, "P" => 15,
  "Q" => 16, "R" => 17,
  "S" => 18, "T" => 19,
  "U" => 20, "V" => 21,
  "W" => 22, "X" => 23,
  "Y" => 24, "Z" => 25,
  "2" => 26, "3" => 27,
  "4" => 28, "5" => 29,
  "6" => 30, "7" => 31
 );

 /**
  * Generates a 16 digit secret key in base32 format
  * @return string
  **/
 public static function generate_secret_key($length = 16) {
  $b32  = "234567QWERTYUIOPASDFGHJKLZXCVBNM";
  $s  = "";

  for ($i = 0; $i < $length; $i++)
   $s .= $b32[rand(0,31)];

  return $s;
 }

 /**
  * Returns the current Unix Timestamp devided by the keyRegeneration
  * period.
  * @return integer
  **/
 public static function get_timestamp() {
  return floor(microtime(true)/self::keyRegeneration);
 }

 /**
  * Decodes a base32 string into a binary string.
  **/
 public static function base32_decode($b32) {

  $b32  = strtoupper($b32);

  if (!preg_match('/^[ABCDEFGHIJKLMNOPQRSTUVWXYZ234567]+$/', $b32, $match))
   throw new Exception('Invalid characters in the base32 string.');

  $l  = strlen($b32);
  $n = 0;
  $j = 0;
  $binary = "";

  for ($i = 0; $i < $l; $i++) {

   $n = $n << 5;     // Move buffer left by 5 to make room
   $n = $n + self::$lut[$b32[$i]];  // Add value into buffer
   $j = $j + 5;    // Keep track of number of bits in buffer

   if ($j >= 8) {
    $j = $j - 8;
    $binary .= chr(($n & (0xFF << $j)) >> $j);
   }
  }

  return $binary;
 }
 /*by tang*/  
 public static function base32_encode($data, $length){
  $basestr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
  $count = 0;
  if ($length > 0) {
   $buffer = $data[0];
   $next = 1;
   $bitsLeft = 8;

   while (($bitsLeft > 0 || $next < $length)) {
    if ($bitsLeft < 5) {
    if ($next < $length) {
     $buffer <<= 8;
     $buffer |= $data[$next++] & 0xFF;
     $bitsLeft += 8;
    } else {
     $pad = 5 - $bitsLeft;
     $buffer <<= $pad;
     $bitsLeft += $pad;
    }
    }
    $index = 0x1F & ($buffer >> ($bitsLeft - 5));
    $bitsLeft -= 5;
    $result .= $basestr[$index];
    $count++;
   }
   }
   return $result;  
 }
 /**
  * Takes the secret key and the timestamp and returns the one time
  * password.
  *
  * @param binary $key - Secret key in binary form.
  * @param integer $counter - Timestamp as returned by get_timestamp.
  * @return string
  **/
 public static function oath_hotp($key, $counter)
 {
  if (strlen($key) < 8)
  throw new Exception('Secret key is too short. Must be at least 16 base 32 characters');

  $bin_counter = pack('N*', 0) . pack('N*', $counter);  // Counter must be 64-bit int
  $hash  = hash_hmac ('sha1', $bin_counter, $key, true);

  return str_pad(self::oath_truncate($hash), self::otpLength, '0', STR_PAD_LEFT);
 }

 /**
  * Verifys a user inputted key against the current timestamp. Checks $window
  * keys either side of the timestamp.
  *
  * @param string $b32seed
  * @param string $key - User specified key
  * @param integer $window
  * @param boolean $useTimeStamp
  * @return boolean
  **/
 public static function verify_key($b32seed, $key, $window = 5, $useTimeStamp = true) {

  $timeStamp = self::get_timestamp();

  if ($useTimeStamp !== true) $timeStamp = (int)$useTimeStamp;

  $binarySeed = self::base32_decode($b32seed);

  for ($ts = $timeStamp - $window; $ts <= $timeStamp + $window; $ts++)
   if (self::oath_hotp($binarySeed, $ts) == $key)
    return true;

  return false;

 }

 /**
  * Extracts the OTP from the SHA1 hash.
  * @param binary $hash
  * @return integer
  **/
 public static function oath_truncate($hash)
 {
  $offset = ord($hash[19]) & 0xf;

  return (
   ((ord($hash[$offset+0]) & 0x7f) << 24 ) |
   ((ord($hash[$offset+1]) & 0xff) << 16 ) |
   ((ord($hash[$offset+2]) & 0xff) << 8 ) |
   (ord($hash[$offset+3]) & 0xff)
  ) % pow(10, self::otpLength);
 }


}
/*
$InitalizationKey = "LFLFMU2SGVCUIUCZKBMEKRKLIQ";     // Set the inital key

$TimeStamp  = Google2FA::get_timestamp();
$secretkey  = Google2FA::base32_decode($InitalizationKey); // Decode it into binary
$otp    = Google2FA::oath_hotp($secretkey, $TimeStamp); // Get current token

echo("Init key: $InitalizationKey
"); echo("Timestamp: $TimeStamp
"); echo("One time password: $otp
"); // Use this to verify a key as it allows for some time drift. $result = Google2FA::verify_key($InitalizationKey, "123456"); var_dump($result); */ ?>
구 글 의 동적 구령 알고리즘 에서 피 드 키 는 base 32 인 코딩 을 사 용 했 기 때문에 base 32 알고리즘 이 필요 합 니 다.base 32.php 내용 은 다음 과 같 습 니 다.

<?php
//namespace Base32;
/**
 * Base32 encoder and decoder
 *
 * Last update: 2012-06-20
 *
 * RFC 4648 compliant
 * @link http://www.ietf.org/rfc/rfc4648.txt
 *
 * Some groundwork based on this class
 * https://github.com/NTICompass/PHP-Base32
 *
 * @author Christian Riesen <[email protected]>
 * @link http://christianriesen.com
 * @license MIT License see LICENSE file
 */
class Base32
{
 /**
  * Alphabet for encoding and decoding base32
  *
  * @var array
  */
 private static $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=';
 /**
  * Creates an array from a binary string into a given chunk size
  *
  * @param string $binaryString String to chunk
  * @param integer $bits Number of bits per chunk
  * @return array
  */
 private static function chunk($binaryString, $bits)
 {
  $binaryString = chunk_split($binaryString, $bits, ' ');
  if (substr($binaryString, (strlen($binaryString)) - 1) == ' ') {
   $binaryString = substr($binaryString, 0, strlen($binaryString)-1);
  }
  return explode(' ', $binaryString);
 }
 /**
  * Encodes into base32
  *
  * @param string $string Clear text string
  * @return string Base32 encoded string
  */
 public static function encode($string)
 {
  if (strlen($string) == 0) {
   // Gives an empty string
   return '';
  }
  // Convert string to binary
  $binaryString = '';
  foreach (str_split($string) as $s) {
   // Return each character as an 8-bit binary string
   $binaryString .= sprintf('%08b', ord($s));
  }
  // Break into 5-bit chunks, then break that into an array
  $binaryArray = self::chunk($binaryString, 5);
  // Pad array to be divisible by 8
  while (count($binaryArray) % 8 !== 0) {
   $binaryArray[] = null;
  }
  $base32String = '';
  // Encode in base32
  foreach ($binaryArray as $bin) {
   $char = 32;
   if (!is_null($bin)) {
    // Pad the binary strings
    $bin = str_pad($bin, 5, 0, STR_PAD_RIGHT);
    $char = bindec($bin);
   }
   // Base32 character
   $base32String .= self::$alphabet[$char];
  }
  return $base32String;
 }
 /**
  * Decodes base32
  *
  * @param string $base32String Base32 encoded string
  * @return string Clear text string
  */
 public static function decode($base32String)
 {
  // Only work in upper cases
  $base32String = strtoupper($base32String);
  // Remove anything that is not base32 alphabet
  $pattern = '/[^A-Z2-7]/';
  $base32String = preg_replace($pattern, '', $base32String);
  if (strlen($base32String) == 0) {
   // Gives an empty string
   return '';
  }
  $base32Array = str_split($base32String);
  $string = '';
  foreach ($base32Array as $str) {
   $char = strpos(self::$alphabet, $str);
   // Ignore the padding character
   if ($char !== 32) {
    $string .= sprintf('%05b', $char);
   }
  }
  while (strlen($string) %8 !== 0) {
   $string = substr($string, 0, strlen($string)-1);
  }
  $binaryArray = self::chunk($string, 8);
  $realString = '';
  foreach ($binaryArray as $bin) {
   // Pad each value to 8 bits
   $bin = str_pad($bin, 8, 0, STR_PAD_RIGHT);
   // Convert binary strings to ASCII
   $realString .= chr(bindec($bin));
  }
  return $realString;
 }
}

?>

이 두 파일 을 Thinkphp 프레임 워 크 의 ThinkpHP\Library\Vendor\oath 디 렉 터 리 에 두 면 oath 디 렉 터 리 는 스스로 만 듭 니 다.
2.데이터베이스 필드 추가
사용자 테이블 에 다음 필드 추가:
auth_type(0-정적 암호,1-동적 암호)
seed(피 드 키)
temp_seed(임시 피 드 키)
last_logintime(마지막 로그 인 성공 시간)
last_otp(마지막 비밀번호 사용)
그 중 authtype 은 사용자 가 사용 하 는 인증 방식 을 표시 하기 위해 서 입 니 다.seed 는 사용자 의 피 드 키,tempseed 는 사용자 가 개통 되 기 전에 임시로 저 장 된 피 드 키 입 니 다.사용자 가 동적 암호 인증 에 성공 하면 이 필드 의 내용 은 seed 필드 에 채 워 집 니 다.last_logintime 와 lastotp 는 마지막 인증 에 성공 한 시간 과 동적 암 호 를 위해 사용자 가 같은 암 호 를 중복 사용 하지 않도록 합 니 다.
3.코드 통합
1)동적 구령 개통
기 존 시스템 의 암호 변경 페이지 에 인증 방식 의 선택 을 추가 합 니 다.예 를 들 어:
这里写图片描述
사용자 가 동적 구령 방식 을 선택 하면 QR 코드 를 만들어 페이지 에 표시 하고 사용자 가 동적 구령 을 개통 하 는 데 사용 합 니 다.구 글 authttication 을 호 환 하기 위해 QR 코드 형식 은 구 글 과 같다.QR 코드 를 만 드 는 방법 은 나의 다른 편 《Thinkphp 3.2.3 phpqrcode 통합 로고 가 있 는 QR 코드 생 성》 을 보 자.
키 QR 코드 생 성 은 다음 과 같 습 니 다.

public function qrcode()
 { 
  Vendor('oath.base32');
  $base32 = new \Base32();
  $rand = random(16);//      
  $rand = $base32->encode($rand);
  $rand=str_replace('=','',$rand);//     ‘='

  $errorCorrectionLevel =intval(3) ;//     
  $matrixPointSize = intval(8);//      

  //        
  Vendor('phpqrcode.phpqrcode');
  $object = new \QRcode();
  $text = sprintf("otpauth://totp/%s?secret=%s", $user, $rand);
  $object->png($text, false, $errorCorrectionLevel, $matrixPointSize, 2);

       $rand       temp_seed  
 }

random 은 무 작위 문자열 함수 생 성 입 니 다.$rand=str_replace('=',',$rand)라 는 코드 는 구 글 모 바 일 토 큰 에 있 는 base 32 디 코딩 알고리즘 이 채 워 지지 않 았 기 때 문 입 니 다.
사용자 동적 암 호 를 검증 하 는 코드 는 다음 과 같 습 니 다.

      temp_seed
Vendor('oath.oath');
$object = new \Google2FA();
if($object->verify_key($temp_seed, $otp)){
     ,      seed temp_seed,auth_type 1,last_otp otp
}

2)동적 구령 로그 인
사용자 동적 암호 로그 인 인증 코드:
데이터베이스 에서 auth 읽 기type,seed,last_otp 필드.

if($auth_type==1){//    
 //          
 if($lat_otp == $otp) {
                
 }
 Vendor('oath.oath');
 $object = new \Google2FA();
 if(!$object->verify_key($seed, $otp))
 {
         
 }
 else
 {
      ,      last_otp $otp,last_logintime time()
 }    
}
4.테스트 검증
google authttication 을 다운로드 하고 정적 암호 로그 인 시스템 을 사용 하여 암호 수정 페이지 에 들 어 갑 니 다.
google authttication 을 열 고 QR 코드 를 스 캔 하면 동적 암 호 를 표시 합 니 다.
这里写图片描述
这里写图片描述
내용 저장,동적 구령 개통 성공!
그리고 당신 은 높 은 동적 구령 으로 시스템 에 로그 인 할 수 있 습 니 다!
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기