动态验证码部署
快速使用
docker load -i dynamic-qrcode.tar.gz && docker run -d --name dq -p 8027:80 dynamic-qrcode:v1.0
编译
docker build -t dynamic-qrcode:v1.0 .
安装
docker load -i dynamic-qrcode.tar.gz
运行
docker run -d --name dq -p PORT:80 dynamic-qrcode:v1.0 // PORT 按需修改
服务端PHP验证码校验
工具类
<?php
namespace App\Utils;
class TOTPAuthenticator
{
// Base32 解码函数
public static function base32_decode($str)
{
$base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
$base32charsFlipped = array_flip(str_split($base32chars));
$paddingCharCount = substr_count($str, '=');
if ($paddingCharCount > 0) {
$str = substr($str, 0, -($paddingCharCount));
}
$str = str_split($str);
$binaryString = "";
for ($i = 0; $i < count($str); $i = $i + 8) {
$x = "";
for ($j = 0; $j < 8; $j++) {
$x .= str_pad(base_convert(@$base32charsFlipped[@$str[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
}
$eightBits = str_split($x, 8);
for ($z = 0; $z < count($eightBits); $z++) {
$binaryString .= ($eightBits[$z] == '') ? '' : chr(base_convert($eightBits[$z], 2, 10));
}
}
return $binaryString;
}
// TOTP 令牌生成函数
public static function generateTOTPToken($secretKey)
{
if (empty($secretKey)) return false;
$timestamp = floor(time() / 30);
$validityPeriod = 30 - (time() % 30);
$secretKey = self::base32_decode($secretKey);
$timestamp = pack('N*', 0) . pack('N*', $timestamp);
$hash = hash_hmac('sha1', $timestamp, $secretKey, true);
$offset = ord(substr($hash, -1)) & 0x0F;
$token = (
(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, 6);
return array($token, $validityPeriod);
}
}
测试用例
<?php
namespace Tests\Unit;
use PHPUnit\Framework\TestCase;
use App\Utils\TOTPAuthenticator;
class TOTPAuthenticatorTest extends TestCase
{
// 测试 TOTP 令牌生成函数
public function testGenerateTOTPToken()
{
$secretKey = 'JBSWY3DPEHPK3PXP';
$tokenInfo = TOTPAuthenticator::generateTOTPToken($secretKey);
$this->assertIsArray($tokenInfo);
$this->assertCount(2, $tokenInfo);
$this->assertIsInt($tokenInfo[0]);
$this->assertIsInt($tokenInfo[1]);
$this->assertGreaterThan(0, $tokenInfo[1]);
}
}