微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

php集成动态口令认证

大多数系统目前均使用的静态密码进行身份认证登录,但由于静态密码容易被窃取,其安全性无法满足安全要求。

动态口令采用一次一密、用过密码作废的方式防止了密码被窃取带来的安全问题。 动态口令分为HOTP(基于事件计数的动态口令,RFC4226)、TOTP(基于时间计数的动态口令,RFC6238)、OCRA(挑战应答式动态口令,RFC6287)等方式。

本文介绍了集成TOTP方式的动态口令认证的方案,PHP框架采用ThinkPHP3.2.3,动态口令生成器使用的是google authtication。

1、为ThinkPHP框架添加oath算法类

oath算法封装类oath.PHP代码如下:

rush: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 . * * 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,$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内容如下:

rush: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 * @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,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,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,strlen($string)-1); } $binaryArray = self::chunk($string,8); $realString = ''; foreach ($binaryArray as $bin) { // Pad each value to 8 bits $bin = str_pad($bin,8,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,其二维码格式与谷歌一样。生成二维码方法见我的另一篇PHP3.2.3整合PHPqrcode生成logo二维码》 。 生成密钥二维码代码如下:

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解码算法并没有填充的‘='号。

验证用户动态口令代码如下:

verify_key($temp_seed,$otp)){ 验证成功,将数据库更新seed为temp_seed,auth_type为1,last_otp为otp }

2)、动态口令登录

用户动态口令登录验证的代码

数据库读取auth_type,seed,last_otp字段。

verify_key($seed,$otp)) { 动态口令不正确 } else { 登录成功,将数据库更新last_otp为$otp,last_logintime为time() } }

4、测试验证

下载google authtication,使用静态密码登录系统,进入修改密码页面。 打开google authtication,扫描二维码,会显示动态口令

这里写图片描述

这里写图片描述

保存内容,开通动态口令成功! 然后你就可以用高大上的动态口令登录系统了!

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程之家。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐