Maison  >  Article  >  développement back-end  >  Authentification dynamique par mot de passe intégrée PHP (recommandée)

Authentification dynamique par mot de passe intégrée PHP (recommandée)

墨辰丷
墨辰丷original
2018-06-01 16:43:243027parcourir

Cet article présente principalement en détail l'authentification par mot de passe dynamique intégrée à PHP. Les mots de passe dynamiques utilisent des mots de passe à usage unique et invalident les mots de passe utilisés pour améliorer les performances de sécurité.

La plupart des systèmes utilisent actuellement des mots de passe statiques pour. connexion d'authentification d'identité, mais comme les mots de passe statiques sont faciles à voler, leur sécurité ne peut pas répondre aux exigences de sécurité.

Les mots de passe dynamiques utilisent un mot de passe à usage unique et invalident les mots de passe utilisés pour éviter les problèmes de sécurité causés par le vol de mot de passe.
Les mots de passe dynamiques sont divisés en HOTP (mots de passe dynamiques basés sur le comptage d'événements, RFC4226), TOTP (mots de passe dynamiques basés sur le comptage du temps, RFC6238), OCRA (mots de passe dynamiques défi-réponse, RFC6287) et d'autres méthodes.

Cet article présente la solution d'authentification dynamique par mot de passe intégrant la méthode TOTP. Le framework PHP utilise Thinkphp3.2.3, et le générateur de mot de passe dynamique utilise l'authentification Google.

1. Ajoutez la classe d'algorithme de serment au framework Thinkphp

Le code oath.php de la classe d'encapsulation de l'algorithme de serment est le suivant :

<?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(&#39;/^[ABCDEFGHIJKLMNOPQRSTUVWXYZ234567]+$/&#39;, $b32, $match))
   throw new Exception(&#39;Invalid characters in the base32 string.&#39;);

  $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(&#39;Secret key is too short. Must be at least 16 base 32 characters&#39;);

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

  return str_pad(self::oath_truncate($hash), self::otpLength, &#39;0&#39;, 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);
*/
?>

En raison de la dynamique de Google, la clé de départ dans l'algorithme de mot de passe utilise le codage base32, donc l'algorithme base32 est requis. Le contenu de base32.php est le suivant :

<?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 = &#39;ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=&#39;;
 /**
  * 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, &#39; &#39;);
  if (substr($binaryString, (strlen($binaryString)) - 1) == &#39; &#39;) {
   $binaryString = substr($binaryString, 0, strlen($binaryString)-1);
  }
  return explode(&#39; &#39;, $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 &#39;&#39;;
  }
  // Convert string to binary
  $binaryString = &#39;&#39;;
  foreach (str_split($string) as $s) {
   // Return each character as an 8-bit binary string
   $binaryString .= sprintf(&#39;%08b&#39;, ord($s));
  }
  // Break into 5-bit chunks, then break that into an array
  $binaryArray = self::chunk($binaryString, 5);
  // Pad array to be pisible by 8
  while (count($binaryArray) % 8 !== 0) {
   $binaryArray[] = null;
  }
  $base32String = &#39;&#39;;
  // 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 = &#39;/[^A-Z2-7]/&#39;;
  $base32String = preg_replace($pattern, &#39;&#39;, $base32String);
  if (strlen($base32String) == 0) {
   // Gives an empty string
   return &#39;&#39;;
  }
  $base32Array = str_split($base32String);
  $string = &#39;&#39;;
  foreach ($base32Array as $str) {
   $char = strpos(self::$alphabet, $str);
   // Ignore the padding character
   if ($char !== 32) {
    $string .= sprintf(&#39;%05b&#39;, $char);
   }
  }
  while (strlen($string) %8 !== 0) {
   $string = substr($string, 0, strlen($string)-1);
  }
  $binaryArray = self::chunk($string, 8);
  $realString = &#39;&#39;;
  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;
 }
}

?>

Mettez ces deux fichiers dans le fichier. Répertoire ThinkPHPLibraryVendoroath du framework Thinkphp Le répertoire oath est créé par vous-même.

2. Ajouter des champs de base de données

Ajoutez les champs suivants à la table utilisateur :
auth_type (0-mot de passe statique, 1-mot de passe dynamique)
seed (seed) Key)
temp_seed (clé de départ temporaire)
last_logintime (dernière heure de connexion réussie)
last_otp (dernier mot de passe utilisé)
où auth_type doit indiquer la méthode d'authentification que l'utilisateur utilise, seed est la clé de départ de l'utilisateur, et temp_seed est une clé de départ temporairement enregistrée avant que l'utilisateur ne l'active. Si l'utilisateur active avec succès l'authentification par mot de passe dynamique, le contenu de ce champ sera rempli dans le champ de départ. last_logintime et last_otp sont l'heure et le mot de passe dynamique de la dernière authentification réussie, qui sont utilisés pour empêcher les utilisateurs de réutiliser le même mot de passe.

3. Intégration du code

1), activer le mot de passe dynamique

dans le système d'origine Modifiez la page du mot de passe et ajoutez la sélection de la méthode d'authentification, par exemple :

Authentification dynamique par mot de passe intégrée PHP (recommandée)

Si l'utilisateur choisit la méthode du mot de passe dynamique, un code QR sera généré et affiché sur la page pour que l'utilisateur active le mot de passe dynamique Mot de passe. Afin d'être compatible avec l'authentification Google, son format de QR code est le même que celui de Google. Pour la méthode de génération de codes QR, voir mon autre article "Thinkphp3.2.3 intègre phpqrcode pour générer des codes QR avec logo".
Le code pour générer le code QR de la clé est le suivant :

public function qrcode()
 { 
  Vendor(&#39;oath.base32&#39;);
  $base32 = new \Base32();
  $rand = random(16);//生成随机种子
  $rand = $base32->encode($rand);
  $rand=str_replace(&#39;=&#39;,&#39;&#39;,$rand);//去除填充的‘=&#39;

  $errorCorrectionLevel =intval(3) ;//容错级别 
  $matrixPointSize = intval(8);//生成图片大小

  //生成二维码图片 
  Vendor(&#39;phpqrcode.phpqrcode&#39;);
  $object = new \QRcode();
  $text = sprintf("otpauth://totp/%s?secret=%s", $user, $rand);
  $object->png($text, false, $errorCorrectionLevel, $matrixPointSize, 2);

  生成的种子$rand保存到数据库的temp_seed字段
 }

random est une fonction qui génère une chaîne aléatoire. Le code $rand=str_replace('=','',$rand) est dû au fait que l'algorithme de décodage base32 de Google Mobile Token ne remplit pas le signe '='.

Le code pour vérifier le mot de passe dynamique de l'utilisateur est le suivant :

从数据库读取temp_seed
Vendor(&#39;oath.oath&#39;);
$object = new \Google2FA();
if($object->verify_key($temp_seed, $otp)){
 验证成功,将数据库更新seed为temp_seed,auth_type为1,last_otp为otp
}

2), connexion par mot de passe dynamique

Mot de passe dynamique de l'utilisateur Code de vérification de connexion :

Lisez les champs auth_type, seed, last_otp de la base de données.

if($auth_type==1){//动态口令
 //防止重复认证    
 if($lat_otp == $otp) {
  动态口令重复使用返回    
 }
 Vendor(&#39;oath.oath&#39;);
 $object = new \Google2FA();
 if(!$object->verify_key($seed, $otp))
 {
  动态口令不正确
 }
 else
 {
  登录成功,将数据库更新last_otp为$otp,last_logintime为time()
 }    
}

4. Testez la vérification

Téléchargez l'authentification Google, utilisez un mot de passe statique pour vous connecter au système et accédez à la page de changement de mot de passe.
Ouvrez l'authentification Google, scannez le code QR et le mot de passe dynamique s'affichera.

Authentification dynamique par mot de passe intégrée PHP (recommandée)

Authentification dynamique par mot de passe intégrée PHP (recommandée)

Enregistrez le contenu et activez le mot de passe dynamique avec succès !
Ensuite, vous pouvez vous connecter au système avec un mot de passe dynamique puissant

Résumé : Ce qui précède est l'intégralité du contenu de cet article, j'espère qu'il sera utile à l'apprentissage de chacun.

Recommandations associées :

Accès PHP à la fonction de paiement instantané Alipay

Explication graphique et textuelle détaillée de l'algorithme de déduplication de tableau bidimensionnel PHP

phpExplication détaillée de trois méthodes pour obtenir des données POST

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn