PHP Integrated Dynamic password Authentication _php instance

Source: Internet
Author: User
Tags ord php framework rfc sha1 sha1 hash sprintf
Most systems are currently using a static password for authentication login, but because the static password is easy to be stolen, its security can not meet the security requirements.

Dynamic password is used once a secret, use the password to invalidate the way to prevent password theft security issues.
The dynamic password is divided into HOTP (dynamic password based on event count, RFC4226), TOTP (dynamic password based on time count, RFC6238), OCRA (Challenge Response dynamic password, RFC6287) and so on.

This paper introduces the scheme of dynamic password Authentication with integrated TOTP mode, the PHP framework uses Thinkphp3.2.3, and the dynamic password generator uses Google authtication.

1. Add oath algorithm class for thinkphp framework

The oath algorithm encapsulates the class oath.php code as follows:

<? php/** * This program was free software:you can redistribute it and/or modify * it under the terms of the GNU General publ IC License as published by * The free software Foundation, either version 3 of the License, or * (at your option) any late R version. * * This program was distributed in the hope that it'll 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 has received a copy of the GNU general public License * along. If not, see
 
  
 . * * 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 = +;//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" and 7, "I" and 8, "J" = 9, "K" = Ten, "L" and "All", "M" = +, "N" = +, "O" = +, "P" = +, "Q" = +, "R" = +, "S" = +, "T "X", "U" = +, "V" = +, "W" = +, "×" = +, "Y" =, "Z" = +, "2" = +, "3" =&gt ; "4", "5", "6" and "7" = 31); /** * Generates a digit secret key in base32 format * @return string **/public static function Generate_secret_key ( $length = +) {$b = "234567QWERTYUIOPASDFGHJKLZXCVBNM ";  $s = "";  for ($i = 0; $i < $length; $i + +) $s. = $b 32[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 ($b) {$b = Strtoupper ($b 32); if (!preg_match ('/^[abcdefghijklmnopqrstuvwxyz234567]+$/', $b, $match)) throw new Exception (' Invalid characters in th  e base32 string. ');  $l = strlen ($b 32);  $n = 0;  $j = 0;  $binary = "";     for ($i = 0; $i < $l; $i + +) {$n = $n << 5;  Move buffer left by 5 to make $n = $n + self:: $lut [$b 32[$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 &lt     ; <= 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 (' S Ecret key is too short. Must bE at least base 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 $b 32seed * @param string $key-user specified key * @param integer $window * @param boolean $useTim Estamp * @return Boolean **/public static function Verify_key ($b 32seed, $key, $window = 5, $useTimeStamp = True) {$ti  Mestamp = Self::get_timestamp ();  if ($useTimeStamp!== true) $timeStamp = (int) $useTimeStamp;  $binarySeed = Self::base32_decode ($b 32seed);    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 [+]) & 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 (ten, 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 Tokenecho ("Init key: $InitalizationKey \ n"), Echo ("Timestamp: $TimeStamp \ n"), Echo ("One time password: $OTP \ n "),//Use the Verify a key as it allows for some time drift. $result = Google2fa::verify_key ($InitalizationKey," 1234 Var_dump ($result);*/?>

Since the BASE32 encoding is used for the seed key in Google's dynamic password algorithm, the BASE32 algorithm is required and the base32.php content is as follows:

<?php//namespace base32;/** * BASE32 encoder and Decoder * * Last update:2012-06-20 * * RFC 4648 compliant * @link HTT P://www.ietf.org/rfc/rfc4648.txt * * Some groundwork based on this class * HTTPS://GITHUB.COM/NTICOMPASS/PHP-BASE32 * @a Uthor Christian Riesen
 
  
 * @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 ($binary  String)-1); } Return Explode (", $binaryString); }/** * encodes into BASE32 * * @param string $string Clear text String * @return string BASE32 encoded String */Pub  Lic 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 a 8-bit binary string $binaryString. = sprintf ('%08b ', ord ($s));  }//Break to 5-bit chunks, then break that to an array $binaryArray = Self::chunk ($binaryString, 5);  Pad array to is divisible by 8 while (count ($binaryArray)% 8!== 0) {$binaryArray [] = null;  } $base 32String = ';   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 $base 32String. = self:: $alphabet [$char]; } return $base 32String; }/** * Decodes base32 * * @param string $base 32String Base32 encoded String * @return string Clear text string * * PU  Blic static function decode ($base 32String) {//only work in upper cases $base 32String = Strtoupper ($base 32String);  Remove anything. is not base32 alphabet $pattern = '/[^a-z2-7]/'; $base 32String = preg_replace ($pattern, ", $base 32String);  if (strlen ($base 32String) = = 0) {//Gives an empty string return ';  } $base 32Array = Str_split ($base 32String);  $string = ";   foreach ($base 32Array as $str) {$char = Strpos (self:: $alphabet, $STR);   Ignore the padding character if ($char!==) {$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; }}?>
 

Put these two files into the Thinkphp\library\vendor\oath directory of the thinkphp framework, and the oath directory is created by itself.

2. Add Database Fields

The user table adds the following fields:
Auth_type (0-static password, 1-dynamic password)
Seed (seeds key)
Temp_seed (temporary seed key)
Last_logintime (Last Login success time)
LAST_OTP (last password used)
Where Auth_type is to indicate which authentication method the user uses, seed is the user's seed key, temp_seed a seed key that is temporarily saved before the user is opened, and if the user opens the dynamic password authentication succeeds, the field content will be filled into the seed field. Last_logintime and LAST_OTP are the time and dynamic passwords for the last successful authentication and are used to prevent users from reusing the same password.

3. Code Integration

1), open the dynamic password

In the original system Change Password page, plus the choice of authentication method, for example:

If the user chooses the dynamic password method, a QR code will be generated for the user to open the dynamic password. In order to be compatible with Google Authtication, the QR code format is the same as Google. The method of generating two-dimensional code see my other article, "Thinkphp3.2.3 integration Phpqrcode Create a QR code with logo."
Generate the key QR code as follows:

Public Function QRCode () {   Vendor (' oath.base32 ');  $base = new \base32 ();  $rand = random (16);//Generate stochastic seed  $rand = $base 32->encode ($rand);  $rand =str_replace (' = ', ' ', $rand);//Remove filled ' = '  $errorCorrectionLevel =intval (3);//fault-tolerant level   $matrixPointSize = Intval (8);//Generate picture size  //Generate two-dimensional code picture   Vendor (' Phpqrcode.phpqrcode ');  $object = new \qrcode ();  $text = sprintf ("otpauth://totp/%s?secret=%s", $user, $rand);  $object->png ($text, False, $errorCorrectionLevel, $matrixPointSize, 2);  The generated seed $rand is saved to the Temp_seed field in the database}

The random string function is generated. $rand =str_replace (' = ', ', $rand) This code is because the BASE32 decoding algorithm in the Google phone token is not populated with the ' = ' number.

The code to verify the user's dynamic password is as follows:

Read Temp_seedvendor (' Oath.oath ') from the database, $object = new \google2fa (), if ($object->verify_key ($temp _seed, $OTP)) {validation succeeded, Update the database seed to Temp_seed,auth_type to 1,LAST_OTP for OTP}

2), dynamic password login

Code for user Dynamic password logon verification:

Reads the AUTH_TYPE,SEED,LAST_OTP field from the database.

if ($auth _type==1) {//Dynamic password//Prevent duplicate Authentication     if ($lat _OTP = = $OTP) {  Dynamic password re-use return     } Vendor (' Oath.oath '); $object = new \ GOOGLE2FA (); if (! $object->verify_key ($seed, $OTP)) {  dynamic password is incorrect} else {  login succeeded, LAST_OTP database updated to $otp,last_logintime to time ()}    }

4. Test verification

Download Google authtication, log in to the system with a static password and go to the Change Password page.
Open Google authtication, scan QR code, will show the dynamic password.

Save content, activate dynamic password successfully!
Then you can log into the system with the dynamic password on the tall!

The above is the whole content of this article, I hope that everyone's learning has helped, but also hope that we support the script home.

  • Related Article

    Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.