index.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. <?php
  2. // Base32 解码函数
  3. function base32_decode($str) {
  4. $base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
  5. $base32charsFlipped = array_flip(str_split($base32chars));
  6. $paddingCharCount = substr_count($str, '=');
  7. if ($paddingCharCount > 0) {
  8. $str = substr($str, 0, -($paddingCharCount));
  9. }
  10. $str = str_split($str);
  11. $binaryString = "";
  12. for ($i = 0; $i < count($str); $i = $i + 8) {
  13. $x = "";
  14. for ($j = 0; $j < 8; $j++) {
  15. $x .= str_pad(base_convert(@$base32charsFlipped[@$str[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
  16. }
  17. $eightBits = str_split($x, 8);
  18. for ($z = 0; $z < count($eightBits); $z++) {
  19. $binaryString .= ( $eightBits[$z] == '' ) ? '' : chr(base_convert($eightBits[$z], 2, 10));
  20. }
  21. }
  22. return $binaryString;
  23. }
  24. // TOTP 令牌生成函数
  25. function generateTOTPToken($secretKey) {
  26. if(empty($secretKey)) return false; // 检查密钥是否为空
  27. $timestamp = floor(time() / 30); // 时间戳以 30 秒为单位
  28. $validityPeriod = 30 - (time() % 30); // 计算当前时间距离下一个 30 秒的时间差,作为有效期
  29. $secretKey = base32_decode($secretKey);
  30. $timestamp = pack('N*', 0) . pack('N*', $timestamp); // 将时间戳转换为字节流
  31. $hash = hash_hmac('sha1', $timestamp, $secretKey, true); // 使用 HMAC-SHA1 算法生成哈希
  32. $offset = ord(substr($hash, -1)) & 0x0F; // 获取哈希的最后一个字节的低四位作为偏移量
  33. $token = (
  34. (ord($hash[$offset + 0]) & 0x7F) << 24 |
  35. (ord($hash[$offset + 1]) & 0xFF) << 16 |
  36. (ord($hash[$offset + 2]) & 0xFF) << 8 |
  37. (ord($hash[$offset + 3]) & 0xFF)
  38. ) % pow(10, 6); // 取哈希结果的四个字节生成一个 6 位动态验证码
  39. return array($token, $validityPeriod); // 返回 6 位验证码 和 有效期
  40. }
  41. // 示例用户令牌密钥
  42. $userSecretKey = 'JBSWY3DPEHPK3PXP'; // 这是一个示例密钥,实际应用中应该是随机生成的
  43. // 生成动态验证码和有效期
  44. list($token, $validityPeriod) = generateTOTPToken($userSecretKey);
  45. if($token === false) {
  46. echo "Failed to generate TOTP token."; // 在生成动态验证码失败时输出错误消息
  47. exit(); // 出错时停止执行后续代码
  48. }
  49. // 导入 QR Code 生成库
  50. require_once 'vendor/autoload.php';
  51. use chillerlan\QRCode\QRCode;
  52. use chillerlan\QRCode\QROptions;
  53. // 生成二维码
  54. $options = new QROptions;
  55. //$options->version = 7;
  56. $options->outputInterface = QRGdImagePNG::class;
  57. $options->scale = 20;
  58. $options->bgColor = [200, 150, 200];
  59. $options->imageTransparent = true;
  60. $qrCode = new QRCode($options);
  61. // 生成二维码数据
  62. $qrCodeData = $qrCode->render($token);
  63. // 输出HTML页面
  64. echo "<!DOCTYPE html>
  65. <html lang=\"en\">
  66. <head>
  67. <meta charset=\"UTF-8\">
  68. <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
  69. <title>动态验证码</title>
  70. <style>
  71. body {
  72. text-align: center;
  73. margin: 0;
  74. padding: 0;
  75. }
  76. #countdown {
  77. font-size: 20px;
  78. font-weight: bold;
  79. margin-top: 20px;
  80. }
  81. img {
  82. width: 300px;
  83. height: 300px;
  84. display: block;
  85. margin: 0 auto;
  86. }
  87. </style>
  88. </head>
  89. <body>
  90. <h1>Dynamic QR Code</h1>
  91. <div id=\"countdown\"></div>
  92. <img src=\"$qrCodeData\" alt=\"Dynamic QR Code\">
  93. <script>
  94. // 获取倒计时容器和倒计时文本
  95. const countdownContainer = document.getElementById('countdown');
  96. // 设置初始倒计时时间
  97. let timeLeft = $validityPeriod;
  98. // 更新倒计时显示
  99. function updateCountdown() {
  100. countdownContainer.textContent = 'Expires in ' + timeLeft + ' seconds';
  101. // 如果倒计时为0,则重新请求动态口令
  102. if (timeLeft === 0) {
  103. clearInterval(timer);
  104. // 重新加载页面或者请求新的动态口令
  105. location.reload();
  106. }
  107. // 否则,减少剩余时间
  108. else {
  109. timeLeft--;
  110. }
  111. }
  112. // 初始更新倒计时显示
  113. updateCountdown();
  114. // 每秒更新倒计时
  115. const timer = setInterval(updateCountdown, 1000);
  116. </script>
  117. </body>
  118. </html>";
  119. ?>