php集成动态口令认证_php实例
大多数系统目前均使用的静态密码进行身份认证登录,但由于静态密码容易被窃取,其安全性无法满足安全要求。
动态口令采用一次一密、用过密码作废的方式防止了密码被窃取带来的安全问题。
动态口令分为HOTP(基于事件计数的动态口令,RFC4226)、TOTP(基于时间计数的动态口令,RFC6238)、OCRA(挑战应答式动态口令,RFC6287)等方式。
本文介绍了集成TOTP方式的动态口令认证的方案,PHP框架采用Thinkphp3.2.3,动态口令生成器使用的是google authtication。
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\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); */ ?>
由于google的动态口令算法中种子密钥使用了base32编码,因此需要base32算法,base32.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 <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; } } ?>
将这两个文件放到Thinkphp框架的ThinkPHP\Library\Vendor\oath目录下,oath目录是自己创建的。
2、添加数据库字段
用户表添加如下字段:
auth_type(0-静态密码,1-动态口令)
seed(种子密钥)
temp_seed(临时种子密钥)
last_logintime(上次登录成功时间)
last_otp(上次使用密码)
其中auth_type是为了标明用户使用的哪种认证方式,seed为用户的种子密钥,temp_seed为用户未开通前临时保存的一个种子密钥,如果用户开通动态口令认证成功,该字段内容会填到seed字段。last_logintime和last_otp为上次认证成功的时间和动态口令,用于避免用户同一个口令重复使用。
3、代码集成
1)、开通动态口令
在原有系统的修改密码页面,加上认证方式的选择,例如:
如果用户选择动态口令方式,则会生成一张二维码显示在页面,用于用户开通动态口令。为了兼容google authtication,其二维码格式与谷歌一样。生成二维码的方法见我的另一篇《Thinkphp3.2.3整合phpqrcode生成带logo的二维码》 。
生成密钥二维码代码如下:
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)这句代码是因为谷歌手机令牌中base32解码算法并没有填充的‘='号。
验证用户动态口令的代码如下:
从数据库读取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 authtication,使用静态密码登录系统,进入修改密码页面。
打开google authtication,扫描二维码,会显示动态口令。
保存内容,开通动态口令成功!
然后你就可以用高大上的动态口令登录系统了!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

Outils d'IA chauds

Undresser.AI Undress
Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover
Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool
Images de déshabillage gratuites

Clothoff.io
Dissolvant de vêtements AI

AI Hentai Generator
Générez AI Hentai gratuitement.

Article chaud

Outils chauds

Bloc-notes++7.3.1
Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise
Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1
Puissant environnement de développement intégré PHP

Dreamweaver CS6
Outils de développement Web visuel

SublimeText3 version Mac
Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Sujets chauds

Les longues URL, souvent encombrées de mots clés et de paramètres de suivi, peuvent dissuader les visiteurs. Un script de raccourcissement d'URL offre une solution, créant des liens concis idéaux pour les médias sociaux et d'autres plateformes. Ces scripts sont utiles pour les sites Web individuels

Laravel simplifie la gestion des données de session temporaires à l'aide de ses méthodes de flash intuitives. Ceci est parfait pour afficher de brefs messages, alertes ou notifications dans votre application. Les données ne persistent que pour la demande ultérieure par défaut: $ demande-

Il s'agit de la deuxième et dernière partie de la série sur la construction d'une application React avec un back-end Laravel. Dans la première partie de la série, nous avons créé une API RESTful utilisant Laravel pour une application de liste de base sur le produit. Dans ce tutoriel, nous serons Dev

Laravel fournit une syntaxe de simulation de réponse HTTP concise, simplifiant les tests d'interaction HTTP. Cette approche réduit considérablement la redondance du code tout en rendant votre simulation de test plus intuitive. L'implémentation de base fournit une variété de raccourcis de type de réponse: Utiliser illuminate \ support \ faades \ http; Http :: faux ([[ 'google.com' => 'Hello World', 'github.com' => ['foo' => 'bar'], 'forge.laravel.com' =>

L'extension PHP Client URL (CURL) est un outil puissant pour les développeurs, permettant une interaction transparente avec des serveurs distants et des API REST. En tirant parti de Libcurl, une bibliothèque de transfert de fichiers multi-protocol très respectée, PHP Curl facilite Efficient Execu

Voulez-vous fournir des solutions instantanées en temps réel aux problèmes les plus pressants de vos clients? Le chat en direct vous permet d'avoir des conversations en temps réel avec les clients et de résoudre leurs problèmes instantanément. Il vous permet de fournir un service plus rapide à votre personnalité

L'enquête sur le paysage PHP 2025 étudie les tendances actuelles de développement du PHP. Il explore l'utilisation du cadre, les méthodes de déploiement et les défis, visant à fournir des informations aux développeurs et aux entreprises. L'enquête prévoit la croissance de la PHP moderne versio

Dans cet article, nous allons explorer le système de notification dans le framework Web Laravel. Le système de notification de Laravel vous permet d'envoyer des notifications aux utilisateurs sur différents canaux. Aujourd'hui, nous discuterons de la façon dont vous pouvez envoyer des notifications OV
