PHP integrated dynamic password authentication_php example
Most systems currently use static passwords for identity authentication login, but because static passwords are easy to be stolen, their security cannot meet security requirements.
Dynamic passwords use a one-time password and invalidate used passwords to prevent security issues caused by password theft.
Dynamic passwords are divided into HOTP (dynamic passwords based on event counting, RFC4226), TOTP (dynamic passwords based on time counting, RFC6238), OCRA (challenge-response dynamic passwords, RFC6287) and other methods.
This article introduces the solution of dynamic password authentication integrating TOTP method. The PHP framework uses Thinkphp3.2.3, and the dynamic password generator uses google authtication.
1. Add oath algorithm class to Thinkphp framework
The oath algorithm encapsulation class oath.php code is as follows:
<?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\n"); echo("Timestamp: $TimeStamp\n"); echo("One time password: $otp\n"); // Use this to verify a key as it allows for some time drift. $result = Google2FA::verify_key($InitalizationKey, "123456"); var_dump($result); */ ?>
Since the seed key in Google’s dynamic password algorithm uses base32 encoding, the base32 algorithm is required. The content of base32.php is as follows:
<?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 <chris.riesen@gmail.com> * @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; } } ?>
Place these two files in the ThinkPHPLibraryVendoroath directory of the Thinkphp framework. The oath directory was created by yourself.
2. Add database fields
Add the following fields to the user table:
auth_type (0-static password, 1-dynamic password)
seed (seed key)
temp_seed (temporary seed key)
last_logintime (last successful login time)
last_otp (last password used)
Among them, auth_type is to indicate which authentication method the user uses, seed is the user's seed key, and temp_seed is a seed key temporarily saved before the user activates it. If the user successfully activates dynamic password authentication, the content of this field will be filled in to seed. field. last_logintime and last_otp are the time and dynamic password of the last successful authentication, which are used to prevent users from reusing the same password.
3. Code integration
1), activate dynamic password
Add the selection of authentication method on the password change page of the original system, for example:
If the user chooses the dynamic password method, a QR code will be generated and displayed on the page for the user to activate the dynamic password. In order to be compatible with Google authentication, its QR code format is the same as Google's. For the method of generating QR codes, see my other article "Thinkphp3.2.3 integrates phpqrcode to generate QR codes with logo".
The code to generate the key QR code is as follows:
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 is a function that generates random strings. The code $rand=str_replace('=','',$rand) is because the base32 decoding algorithm in Google Mobile Token does not fill the '=' sign.
The code to verify the user’s dynamic password is as follows:
从数据库读取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), dynamic password login
Code for user dynamic password login verification:
Read auth_type, seed, last_otp fields from the database.
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. Testing and verification
Download google authtication, log in to the system using a static password, and enter the password change page.
Open Google Authtication, scan the QR code, and the dynamic password will be displayed.
Save the content and activate the dynamic password successfully!
Then you can log in to the system with your awesome dynamic password!
The above is the entire content of this article. I hope it will be helpful to everyone’s study. I also hope that everyone will support Script Home.

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

Long URLs, often cluttered with keywords and tracking parameters, can deter visitors. A URL shortening script offers a solution, creating concise links ideal for social media and other platforms. These scripts are valuable for individual websites a

Following its high-profile acquisition by Facebook in 2012, Instagram adopted two sets of APIs for third-party use. These are the Instagram Graph API and the Instagram Basic Display API.As a developer building an app that requires information from a

Laravel simplifies handling temporary session data using its intuitive flash methods. This is perfect for displaying brief messages, alerts, or notifications within your application. Data persists only for the subsequent request by default: $request-

This is the second and final part of the series on building a React application with a Laravel back-end. In the first part of the series, we created a RESTful API using Laravel for a basic product-listing application. In this tutorial, we will be dev

Laravel provides concise HTTP response simulation syntax, simplifying HTTP interaction testing. This approach significantly reduces code redundancy while making your test simulation more intuitive. The basic implementation provides a variety of response type shortcuts: use Illuminate\Support\Facades\Http; Http::fake([ 'google.com' => 'Hello World', 'github.com' => ['foo' => 'bar'], 'forge.laravel.com' =>

The PHP Client URL (cURL) extension is a powerful tool for developers, enabling seamless interaction with remote servers and REST APIs. By leveraging libcurl, a well-respected multi-protocol file transfer library, PHP cURL facilitates efficient execution of various network protocols, including HTTP, HTTPS, and FTP. This extension offers granular control over HTTP requests, supports multiple concurrent operations, and provides built-in security features.

Do you want to provide real-time, instant solutions to your customers' most pressing problems? Live chat lets you have real-time conversations with customers and resolve their problems instantly. It allows you to provide faster service to your custom

The 2025 PHP Landscape Survey investigates current PHP development trends. It explores framework usage, deployment methods, and challenges, aiming to provide insights for developers and businesses. The survey anticipates growth in modern PHP versio
