返回列表 发布新帖

纯网页版TOTP验证码生成器

1363 0
小K网牛逼 发表于 6 小时前 | 查看全部 阅读模式 <

马上注册,结交更多好友,享用更多功能,让你轻松玩转小K网。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
分享一个自己写的纯网页版TOTP生成工具
纯前端实现,密钥不离本地
实时30秒倒计时可视化展示无需注册,即开即用
以下是完整代码
附运行截图

纯网页版TOTP验证码生成器

纯网页版TOTP验证码生成器

纯网页版TOTP验证码生成器

纯网页版TOTP验证码生成器

纯网页版TOTP验证码生成器

纯网页版TOTP验证码生成器


html代码:
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>TOTP 倒计时</title>
  7.     <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
  8.     <style>
  9.         body {
  10.             font-family: 'Arial', sans-serif;
  11.             max-width: 500px;
  12.             margin: 0 auto;
  13.             padding: 20px;
  14.             text-align: center;
  15.             background: #f5f5f5;
  16.         }
  17.         input {
  18.             padding: 12px;
  19.             width: 300px;
  20.             margin: 15px 0;
  21.             font-size: 16px;
  22.             border: 2px solid #ddd;
  23.             border-radius: 4px;
  24.         }
  25.         button {
  26.             padding: 12px 25px;
  27.             background: #4285f4;
  28.             color: white;
  29.             border: none;
  30.             border-radius: 4px;
  31.             font-size: 16px;
  32.             cursor: pointer;
  33.             transition: background 0.3s;
  34.         }
  35.         button:hover {
  36.             background: #3367d6;
  37.         }
  38.         .totp-display {
  39.             font-family: Arial, sans-serif;
  40.             font-weight: bold;
  41.             font-size: 48px;
  42.             margin: 20px 0;
  43.             letter-spacing: 5px;
  44.             transition: color 0.3s;
  45.         }
  46.         .totp-display.green {
  47.             color: #4CAF50;
  48.         }
  49.         .totp-display.blue {
  50.             color: #2196F3;
  51.         }
  52.         .totp-display.red {
  53.             color: #f44336;
  54.             animation: pulse 0.5s infinite alternate;
  55.         }
  56.         .countdown-container {
  57.             position: relative;
  58.             width: 120px;
  59.             height: 120px;
  60.             margin: 30px auto;
  61.         }
  62.         .countdown-circle {
  63.             width: 100%;
  64.             height: 100%;
  65.         }
  66.         .countdown-circle-bg {
  67.             fill: none;
  68.             stroke: #e0e0e0;
  69.             stroke-width: 10;
  70.         }
  71.         .countdown-circle-fg {
  72.             fill: none;
  73.             stroke: #4CAF50;
  74.             stroke-width: 10;
  75.             stroke-linecap: round;
  76.             transform: rotate(-90deg);
  77.             transform-origin: 50% 50%;
  78.             transition: all 0.1s linear;
  79.         }
  80.         .countdown-circle-fg.blue {
  81.             stroke: #2196F3;
  82.         }
  83.         .countdown-circle-fg.red {
  84.             stroke: #f44336;
  85.         }
  86.         .countdown-text {
  87.             position: absolute;
  88.             top: 50%;
  89.             left: 50%;
  90.             transform: translate(-50%, -50%);
  91.             font-size: 30px;
  92.             font-weight: bold;
  93.             color: #333;
  94.         }
  95.         @keyframes pulse {
  96.             from { opacity: 1; }
  97.             to { opacity: 0.5; }
  98.         }
  99.     </style>
  100. </head>
  101. <body>
  102.     <h1>TOTP 验证码生成器</h1>
  103.     <p>请输入 Base32 密钥:</p>
  104.     <input type="text" id="secret" placeholder="例如:JBSWY3DPEHPK3PXP" />
  105.     <button>生成动态验证码</button>
  106.        <div class="totp-display" id="result">000000</div>
  107.        <div class="countdown-container">
  108.         <svg class="countdown-circle" viewBox="0 0 100 100">
  109.             <circle class="countdown-circle-bg" cx="50" cy="50" r="45"/>
  110.             <circle class="countdown-circle-fg" id="countdown-circle" cx="50" cy="50" r="45"/>
  111.         </svg>
  112.         <div class="countdown-text" id="countdown">30</div>
  113.     </div>
  114.     <script>
  115.         // Base32 解码
  116.         function base32Decode(base32) {
  117.             const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
  118.             base32 = base32.replace(/[^A-Z2-7]/gi, '').toUpperCase();
  119.             let bits = 0, value = 0, output = [];
  120.             for (let i = 0; i < base32.length; i++) {
  121.                 const char = base32.charAt(i);
  122.                 const index = alphabet.indexOf(char);
  123.                 if (index === -1) continue;
  124.                 value = (value << 5) | index;
  125.                 bits += 5;
  126.                 if (bits >= 8) {
  127.                     bits -= 8;
  128.                     output.push((value >>> bits) & 0xFF);
  129.                 }
  130.             }
  131.             return output;
  132.         }
  133.         // 计算 HMAC-SHA1
  134.         function hmacSHA1Bytes(keyBytes, messageBytes) {
  135.             const key = CryptoJS.lib.WordArray.create(keyBytes);
  136.             const message = CryptoJS.lib.WordArray.create(messageBytes);
  137.             const hmac = CryptoJS.HmacSHA1(message, key);
  138.             return hmac.toString(CryptoJS.enc.Hex)
  139.                       .match(/.{1,2}/g)
  140.                       .map(byte => parseInt(byte, 16));
  141.         }
  142.         // 动态截断
  143.         function dynamicTruncation(hmacBytes) {
  144.             const offset = hmacBytes[hmacBytes.length - 1] & 0x0F;
  145.             return (
  146.                 ((hmacBytes[offset]     & 0x7F) << 24) |
  147.                 ((hmacBytes[offset + 1] & 0xFF) << 16) |
  148.                 ((hmacBytes[offset + 2] & 0xFF) <<  8) |
  149.                  (hmacBytes[offset + 3] & 0xFF)
  150.             );
  151.         }
  152.         // 计算 TOTP
  153.         function calculateTOTP(secret) {
  154.             try {
  155.                 const keyBytes = base32Decode(secret);
  156.                 if (keyBytes.length === 0) throw new Error("无效的 Base32 密钥");
  157.                 const timeStep = 30;
  158.                 const timestamp = Math.floor(Date.now() / 1000);
  159.                 const counter = Math.floor(timestamp / timeStep);
  160.                 const counterBytes = new Array(8).fill(0);
  161.                 for (let i = 0; i < 8; i++) {
  162.                     counterBytes[7 - i] = (counter >>> (i * 8)) & 0xFF;
  163.                 }
  164.                 const hmacBytes = hmacSHA1Bytes(keyBytes, counterBytes);
  165.                 const binary = dynamicTruncation(hmacBytes);
  166.                 return (binary % 1000000).toString().padStart(6, '0');
  167.             } catch (e) {
  168.                 return `错误: ${e.message}`;
  169.             }
  170.         }
  171.         // 更新倒计时和 TOTP
  172.         function updateTOTPAndCountdown() {
  173.             const secret = document.getElementById('secret').value.trim();
  174.             if (!secret) return;
  175.             const timestamp = Math.floor(Date.now() / 1000);
  176.             const elapsed = timestamp % 30;
  177.             const remainingSeconds = 30 - elapsed;
  178.             const progress = elapsed / 30;
  179.            // 获取元素
  180.             const circle = document.getElementById('countdown-circle');
  181.             const totpDisplay = document.getElementById('result');
  182.            
  183.             // 先移除所有颜色类
  184.             circle.classList.remove('blue', 'red');
  185.             totpDisplay.classList.remove('green', 'blue', 'red');
  186.             
  187.             // 根据剩余时间设置不同颜色和效果
  188.             if (remainingSeconds > 20) {
  189.                 // 30-21秒:绿色
  190.                 circle.style.stroke = '#4CAF50';
  191.                 totpDisplay.classList.add('green');
  192.             } else if (remainingSeconds > 5) {
  193.                 // 20-6秒:蓝色
  194.                 circle.style.stroke = '#2196F3';
  195.                 circle.classList.add('blue');
  196.                 totpDisplay.classList.add('blue');
  197.             } else {
  198.                 // 5-0秒:红色闪烁
  199.                 circle.style.stroke = '#f44336';
  200.                 circle.classList.add('red');
  201.                 totpDisplay.classList.add('red');
  202.             }
  203.             
  204.             // 更新圆圈进度(逆时针减少)
  205.             const circumference = 2 * Math.PI * 45;
  206.             circle.style.strokeDasharray = circumference;
  207.             circle.style.strokeDashoffset = circumference * progress;
  208.             
  209.             // 更新倒计时数字
  210.             document.getElementById('countdown').textContent = remainingSeconds;
  211.             
  212.             // 更新 TOTP
  213.             document.getElementById('result').textContent = calculateTOTP(secret);

  214.             setTimeout(updateTOTPAndCountdown, 1000);
  215.         }

  216.         // 启动 TOTP 计算
  217.         function startTOTP() {
  218.             const secret = document.getElementById('secret').value.trim();
  219.             if (!secret) {
  220.                 alert("请输入 Base32 密钥!");
  221.                 return;
  222.             }
  223.             
  224.             // 初始化圆圈和TOTP显示
  225.             const circle = document.getElementById('countdown-circle');
  226.             const totpDisplay = document.getElementById('result');
  227.             const circumference = 2 * Math.PI * 45;
  228.             
  229.             circle.style.strokeDasharray = circumference;
  230.             circle.style.strokeDashoffset = 0;
  231.             circle.classList.remove('blue', 'red');
  232.             circle.style.stroke = '#4CAF50';
  233.             
  234.             totpDisplay.classList.remove('blue', 'red');
  235.             totpDisplay.classList.add('green');
  236.             
  237.             updateTOTPAndCountdown();
  238.         }
  239.     </script>
  240. </body>
  241. </html>
复制代码


回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关灯 在本版发帖
扫一扫添加微信客服
QQ客服返回顶部
快速回复 返回顶部 返回列表