马上注册,结交更多好友,享用更多功能,让你轻松玩转小K网。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
分享一个自己写的纯网页版TOTP生成工具
纯前端实现,密钥不离本地
实时30秒倒计时可视化展示无需注册,即开即用
以下是完整代码
附运行截图
纯网页版TOTP验证码生成器
纯网页版TOTP验证码生成器
纯网页版TOTP验证码生成器
html代码:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>TOTP 倒计时</title>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
- <style>
- body {
- font-family: 'Arial', sans-serif;
- max-width: 500px;
- margin: 0 auto;
- padding: 20px;
- text-align: center;
- background: #f5f5f5;
- }
- input {
- padding: 12px;
- width: 300px;
- margin: 15px 0;
- font-size: 16px;
- border: 2px solid #ddd;
- border-radius: 4px;
- }
- button {
- padding: 12px 25px;
- background: #4285f4;
- color: white;
- border: none;
- border-radius: 4px;
- font-size: 16px;
- cursor: pointer;
- transition: background 0.3s;
- }
- button:hover {
- background: #3367d6;
- }
- .totp-display {
- font-family: Arial, sans-serif;
- font-weight: bold;
- font-size: 48px;
- margin: 20px 0;
- letter-spacing: 5px;
- transition: color 0.3s;
- }
- .totp-display.green {
- color: #4CAF50;
- }
- .totp-display.blue {
- color: #2196F3;
- }
- .totp-display.red {
- color: #f44336;
- animation: pulse 0.5s infinite alternate;
- }
- .countdown-container {
- position: relative;
- width: 120px;
- height: 120px;
- margin: 30px auto;
- }
- .countdown-circle {
- width: 100%;
- height: 100%;
- }
- .countdown-circle-bg {
- fill: none;
- stroke: #e0e0e0;
- stroke-width: 10;
- }
- .countdown-circle-fg {
- fill: none;
- stroke: #4CAF50;
- stroke-width: 10;
- stroke-linecap: round;
- transform: rotate(-90deg);
- transform-origin: 50% 50%;
- transition: all 0.1s linear;
- }
- .countdown-circle-fg.blue {
- stroke: #2196F3;
- }
- .countdown-circle-fg.red {
- stroke: #f44336;
- }
- .countdown-text {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- font-size: 30px;
- font-weight: bold;
- color: #333;
- }
- @keyframes pulse {
- from { opacity: 1; }
- to { opacity: 0.5; }
- }
- </style>
- </head>
- <body>
- <h1>TOTP 验证码生成器</h1>
- <p>请输入 Base32 密钥:</p>
- <input type="text" id="secret" placeholder="例如:JBSWY3DPEHPK3PXP" />
- <button>生成动态验证码</button>
- <div class="totp-display" id="result">000000</div>
- <div class="countdown-container">
- <svg class="countdown-circle" viewBox="0 0 100 100">
- <circle class="countdown-circle-bg" cx="50" cy="50" r="45"/>
- <circle class="countdown-circle-fg" id="countdown-circle" cx="50" cy="50" r="45"/>
- </svg>
- <div class="countdown-text" id="countdown">30</div>
- </div>
- <script>
- // Base32 解码
- function base32Decode(base32) {
- const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
- base32 = base32.replace(/[^A-Z2-7]/gi, '').toUpperCase();
- let bits = 0, value = 0, output = [];
- for (let i = 0; i < base32.length; i++) {
- const char = base32.charAt(i);
- const index = alphabet.indexOf(char);
- if (index === -1) continue;
- value = (value << 5) | index;
- bits += 5;
- if (bits >= 8) {
- bits -= 8;
- output.push((value >>> bits) & 0xFF);
- }
- }
- return output;
- }
- // 计算 HMAC-SHA1
- function hmacSHA1Bytes(keyBytes, messageBytes) {
- const key = CryptoJS.lib.WordArray.create(keyBytes);
- const message = CryptoJS.lib.WordArray.create(messageBytes);
- const hmac = CryptoJS.HmacSHA1(message, key);
- return hmac.toString(CryptoJS.enc.Hex)
- .match(/.{1,2}/g)
- .map(byte => parseInt(byte, 16));
- }
- // 动态截断
- function dynamicTruncation(hmacBytes) {
- const offset = hmacBytes[hmacBytes.length - 1] & 0x0F;
- return (
- ((hmacBytes[offset] & 0x7F) << 24) |
- ((hmacBytes[offset + 1] & 0xFF) << 16) |
- ((hmacBytes[offset + 2] & 0xFF) << 8) |
- (hmacBytes[offset + 3] & 0xFF)
- );
- }
- // 计算 TOTP
- function calculateTOTP(secret) {
- try {
- const keyBytes = base32Decode(secret);
- if (keyBytes.length === 0) throw new Error("无效的 Base32 密钥");
- const timeStep = 30;
- const timestamp = Math.floor(Date.now() / 1000);
- const counter = Math.floor(timestamp / timeStep);
- const counterBytes = new Array(8).fill(0);
- for (let i = 0; i < 8; i++) {
- counterBytes[7 - i] = (counter >>> (i * 8)) & 0xFF;
- }
- const hmacBytes = hmacSHA1Bytes(keyBytes, counterBytes);
- const binary = dynamicTruncation(hmacBytes);
- return (binary % 1000000).toString().padStart(6, '0');
- } catch (e) {
- return `错误: ${e.message}`;
- }
- }
- // 更新倒计时和 TOTP
- function updateTOTPAndCountdown() {
- const secret = document.getElementById('secret').value.trim();
- if (!secret) return;
- const timestamp = Math.floor(Date.now() / 1000);
- const elapsed = timestamp % 30;
- const remainingSeconds = 30 - elapsed;
- const progress = elapsed / 30;
- // 获取元素
- const circle = document.getElementById('countdown-circle');
- const totpDisplay = document.getElementById('result');
-
- // 先移除所有颜色类
- circle.classList.remove('blue', 'red');
- totpDisplay.classList.remove('green', 'blue', 'red');
-
- // 根据剩余时间设置不同颜色和效果
- if (remainingSeconds > 20) {
- // 30-21秒:绿色
- circle.style.stroke = '#4CAF50';
- totpDisplay.classList.add('green');
- } else if (remainingSeconds > 5) {
- // 20-6秒:蓝色
- circle.style.stroke = '#2196F3';
- circle.classList.add('blue');
- totpDisplay.classList.add('blue');
- } else {
- // 5-0秒:红色闪烁
- circle.style.stroke = '#f44336';
- circle.classList.add('red');
- totpDisplay.classList.add('red');
- }
-
- // 更新圆圈进度(逆时针减少)
- const circumference = 2 * Math.PI * 45;
- circle.style.strokeDasharray = circumference;
- circle.style.strokeDashoffset = circumference * progress;
-
- // 更新倒计时数字
- document.getElementById('countdown').textContent = remainingSeconds;
-
- // 更新 TOTP
- document.getElementById('result').textContent = calculateTOTP(secret);
-
- setTimeout(updateTOTPAndCountdown, 1000);
- }
-
- // 启动 TOTP 计算
- function startTOTP() {
- const secret = document.getElementById('secret').value.trim();
- if (!secret) {
- alert("请输入 Base32 密钥!");
- return;
- }
-
- // 初始化圆圈和TOTP显示
- const circle = document.getElementById('countdown-circle');
- const totpDisplay = document.getElementById('result');
- const circumference = 2 * Math.PI * 45;
-
- circle.style.strokeDasharray = circumference;
- circle.style.strokeDashoffset = 0;
- circle.classList.remove('blue', 'red');
- circle.style.stroke = '#4CAF50';
-
- totpDisplay.classList.remove('blue', 'red');
- totpDisplay.classList.add('green');
-
- updateTOTPAndCountdown();
- }
- </script>
- </body>
- </html>
复制代码
|
|