PHP integrierte dynamische Passwortauthentifizierung (empfohlen)

墨辰丷
Freigeben: 2023-03-29 09:46:01
Original
3035 Leute haben es durchsucht

Dieser Artikel stellt hauptsächlich die in PHP integrierte dynamische Passwortauthentifizierung im Detail vor. Dynamische Passwörter verwenden Einmalpasswörter und machen gebrauchte Passwörter ungültig, um die Sicherheitsleistung zu verbessern.

Die meisten Systeme verwenden derzeit statische Passwörter Identitätsauthentifizierungsanmeldung, aber da statische Passwörter leicht gestohlen werden können, kann ihre Sicherheit die Sicherheitsanforderungen nicht erfüllen.

Dynamische Passwörter verwenden ein Einmalpasswort und machen verwendete Passwörter ungültig, um Sicherheitsprobleme durch Passwortdiebstahl zu verhindern.
Dynamische Passwörter werden in HOTP (dynamische Passwörter basierend auf Ereigniszählung, RFC4226), TOTP (dynamische Passwörter basierend auf Zeitzählung, RFC6238), OCRA (dynamische Challenge-Response-Passwörter, RFC6287) und andere Methoden unterteilt.

In diesem Artikel wird die Lösung der dynamischen Passwortauthentifizierung mit der Integration der TOTP-Methode vorgestellt. Das PHP-Framework verwendet Thinkphp3.2.3 und der dynamische Passwortgenerator verwendet die Google-Authentifizierung.

1. Oath-Algorithmusklasse zum Thinkphp-Framework hinzufügen

Der Code der Oath-Algorithmus-Kapselungsklasse oath.php lautet wie folgt:

<?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);
*/
?>
Nach dem Login kopieren

Aufgrund der Dynamik von Google Passwort-Algorithmus Der mittlere Seed-Schlüssel verwendet die Base32-Kodierung, daher ist der Inhalt von base32.php wie folgt:

<?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;
 }
}

?>
Nach dem Login kopieren

Platzieren Sie diese beiden Dateien im ThinkPHPLibraryVendoroath-Verzeichnis des Thinkphp-Frameworks wird von Ihnen selbst erstellt.

2. Datenbankfelder hinzufügen

Fügen Sie die folgenden Felder zur Benutzertabelle hinzu:
auth_type (0-statisches Passwort, 1-dynamisches Passwort)
Seed (Seed-Schlüssel)
temp_seed (temporärer Seed-Schlüssel)
last_logintime (letzte erfolgreiche Anmeldezeit)
last_otp (letztes verwendetes Passwort)
wobei auth_type angibt, welche Authentifizierungsmethode der Benutzer verwendet, Seed ist der Seed-Schlüssel des Benutzers und temp_seed ist ein Seed-Schlüssel, der vorübergehend gespeichert wird, bevor der Benutzer ihn aktiviert. Wenn der Benutzer die dynamische Passwortauthentifizierung erfolgreich aktiviert, wird der Inhalt dieses Felds in das Seed-Feld eingefügt. last_logintime und last_otp sind die Zeit und das dynamische Passwort der letzten erfolgreichen Authentifizierung, die verwendet werden, um zu verhindern, dass Benutzer dasselbe Passwort wiederverwenden.

3. Code-Integration

1), dynamisches Passwort aktivieren

im Originalsystem Ändern Sie die Passwortseite und fügen Sie die Auswahl der Authentifizierungsmethode hinzu, zum Beispiel:

PHP integrierte dynamische Passwortauthentifizierung (empfohlen)

Wenn der Benutzer die dynamische Passwortmethode wählt, wird ein QR-Code generiert und auf der Seite angezeigt Damit der Benutzer das dynamische Passwort Passwort aktivieren kann. Um mit der Google-Authentifizierung kompatibel zu sein, ist das QR-Code-Format mit dem von Google identisch. Informationen zur Methode zum Generieren von QR-Codes finden Sie in meinem anderen Artikel „Thinkphp3.2.3 integriert phpqrcode, um QR-Codes mit Logo zu generieren“.
Der Code zum Generieren des Schlüssel-QR-Codes lautet wie folgt:

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字段
 }
Nach dem Login kopieren

random ist eine Funktion, die eine zufällige Zeichenfolge generiert. Der Code $rand=str_replace('=','',$rand) liegt daran, dass der Base32-Dekodierungsalgorithmus in Google Mobile Token das „=“-Zeichen nicht ausfüllt.

Der Code zur Überprüfung des dynamischen Benutzerkennworts lautet wie folgt:

从数据库读取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
}
Nach dem Login kopieren

2), dynamische Kennwortanmeldung

Benutzer dynamisch Passwort-Anmeldebestätigungscode:

Lesen Sie die Felder auth_type, Seed und last_otp aus der Datenbank.

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()
 }    
}
Nach dem Login kopieren

4. Testverifizierung

Laden Sie die Google-Authentifizierung herunter, verwenden Sie ein statisches Passwort, um sich beim System anzumelden, und rufen Sie die Seite zur Passwortänderung auf.
Öffnen Sie die Google-Authentifizierung, scannen Sie den QR-Code und das dynamische Passwort wird angezeigt.

PHP integrierte dynamische Passwortauthentifizierung (empfohlen)

PHP integrierte dynamische Passwortauthentifizierung (empfohlen)

Speichern Sie den Inhalt und aktivieren Sie das dynamische Passwort erfolgreich!
Dann können Sie sich mit einem leistungsstarken dynamischen Passwort beim System anmelden

Zusammenfassung: Das Obige ist der gesamte Inhalt dieses Artikels, ich hoffe, dass er zum Lernen aller beiträgt!

Verwandte Empfehlungen:

PHP-Zugriff auf die Alipay-Sofortzahlungsfunktion

Detaillierte Grafik- und Texterklärung des zweidimensionalen PHP-Array-Deduplizierungsalgorithmus

phpDetaillierte Erläuterung von drei Methoden zum Erhalten von POST-Daten

Das obige ist der detaillierte Inhalt vonPHP integrierte dynamische Passwortauthentifizierung (empfohlen). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage