| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- <?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>";
- ?>
|