|
|
@@ -0,0 +1,133 @@
|
|
|
+<?php
|
|
|
+// Base32 解码函数
|
|
|
+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 令牌生成函数
|
|
|
+function generateTOTPToken($secretKey) {
|
|
|
+ if(empty($secretKey)) return false; // 检查密钥是否为空
|
|
|
+
|
|
|
+ $timestamp = floor(time() / 30); // 时间戳以 30 秒为单位
|
|
|
+ $validityPeriod = 30 - (time() % 30); // 计算当前时间距离下一个 30 秒的时间差,作为有效期
|
|
|
+ $secretKey = base32_decode($secretKey);
|
|
|
+ $timestamp = pack('N*', 0) . pack('N*', $timestamp); // 将时间戳转换为字节流
|
|
|
+ $hash = hash_hmac('sha1', $timestamp, $secretKey, true); // 使用 HMAC-SHA1 算法生成哈希
|
|
|
+ $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); // 取哈希结果的四个字节生成一个 6 位动态验证码
|
|
|
+ return array($token, $validityPeriod); // 返回 6 位验证码 和 有效期
|
|
|
+}
|
|
|
+
|
|
|
+// 示例用户令牌密钥
|
|
|
+$userSecretKey = 'JBSWY3DPEHPK3PXP'; // 这是一个示例密钥,实际应用中应该是随机生成的
|
|
|
+
|
|
|
+// 生成动态验证码和有效期
|
|
|
+list($token, $validityPeriod) = generateTOTPToken($userSecretKey);
|
|
|
+if($token === false) {
|
|
|
+ echo "Failed to generate TOTP token."; // 在生成动态验证码失败时输出错误消息
|
|
|
+ exit(); // 出错时停止执行后续代码
|
|
|
+}
|
|
|
+
|
|
|
+// 导入 QR Code 生成库
|
|
|
+require_once 'vendor/autoload.php';
|
|
|
+
|
|
|
+use chillerlan\QRCode\QRCode;
|
|
|
+use chillerlan\QRCode\QROptions;
|
|
|
+
|
|
|
+// 生成二维码
|
|
|
+$options = new QROptions;
|
|
|
+
|
|
|
+//$options->version = 7;
|
|
|
+$options->outputInterface = QRGdImagePNG::class;
|
|
|
+$options->scale = 20;
|
|
|
+$options->bgColor = [200, 150, 200];
|
|
|
+$options->imageTransparent = true;
|
|
|
+$qrCode = new QRCode($options);
|
|
|
+
|
|
|
+// 生成二维码数据
|
|
|
+$qrCodeData = $qrCode->render($token);
|
|
|
+
|
|
|
+// 输出HTML页面
|
|
|
+echo "<!DOCTYPE html>
|
|
|
+<html lang=\"en\">
|
|
|
+<head>
|
|
|
+ <meta charset=\"UTF-8\">
|
|
|
+ <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
|
|
|
+ <title>动态验证码</title>
|
|
|
+ <style>
|
|
|
+ body {
|
|
|
+ text-align: center;
|
|
|
+ margin: 0;
|
|
|
+ padding: 0;
|
|
|
+ }
|
|
|
+ #countdown {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: bold;
|
|
|
+ margin-top: 20px;
|
|
|
+ }
|
|
|
+ img {
|
|
|
+ width: 300px;
|
|
|
+ height: 300px;
|
|
|
+ display: block;
|
|
|
+ margin: 0 auto;
|
|
|
+ }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+ <h1>Dynamic QR Code</h1>
|
|
|
+ <div id=\"countdown\"></div>
|
|
|
+ <img src=\"$qrCodeData\" alt=\"Dynamic QR Code\">
|
|
|
+ <script>
|
|
|
+ // 获取倒计时容器和倒计时文本
|
|
|
+ const countdownContainer = document.getElementById('countdown');
|
|
|
+ // 设置初始倒计时时间
|
|
|
+ let timeLeft = $validityPeriod;
|
|
|
+ // 更新倒计时显示
|
|
|
+ function updateCountdown() {
|
|
|
+ countdownContainer.textContent = 'Expires in ' + timeLeft + ' seconds';
|
|
|
+ // 如果倒计时为0,则重新请求动态口令
|
|
|
+ if (timeLeft === 0) {
|
|
|
+ clearInterval(timer);
|
|
|
+ // 重新加载页面或者请求新的动态口令
|
|
|
+ location.reload();
|
|
|
+ }
|
|
|
+ // 否则,减少剩余时间
|
|
|
+ else {
|
|
|
+ timeLeft--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 初始更新倒计时显示
|
|
|
+ updateCountdown();
|
|
|
+ // 每秒更新倒计时
|
|
|
+ const timer = setInterval(updateCountdown, 1000);
|
|
|
+ </script>
|
|
|
+</body>
|
|
|
+</html>";
|
|
|
+?>
|
|
|
+
|