TOTP生成器 - 基于时间的一次性密码工具 在线
生成和验证2FA测试的TOTP代码。创建密钥、QR码和otpauth:// URI。符合RFC 6238标准 — 100%客户端处理,密钥不会离开您的浏览器。
请输入密钥以生成TOTP代码
常见问题
什么是TOTP生成器?
TOTP(基于时间的一次性密码)生成器是一个在线工具,遵循RFC 6238标准创建有时间限制的身份验证代码。TOTP是Google Authenticator、Authy和Microsoft Authenticator等流行身份验证应用程序背后的算法。它通过使用HMAC(基于哈希的消息认证码)函数将共享密钥与当前Unix时间戳结合,然后截断结果以生成一个短数字代码(通常为6位或8位)。代码默认每30秒更改一次,使其在双因素身份验证(2FA)中作为第二因素非常有用。该工具允许开发者直接在浏览器中生成、验证和测试TOTP代码,无需安装身份验证应用程序。
如何使用此工具?
1. 输入Base32编码的密钥,或点击「生成密钥」创建随机密钥。2. 配置TOTP参数:算法(SHA-1是大多数服务使用的默认值)、位数(6或8)和时间周期(30秒为标准)。3. 工具会立即显示当前TOTP代码,并附带一个倒计时计时器,显示代码何时刷新。4. 可选地,填写颁发者和标签字段以生成完整的otpauth:// URI。5. 使用身份验证应用程序扫描生成的QR码,验证代码是否匹配。6. 使用验证选项卡粘贴代码,检查其对于配置的密钥和时间窗口是否有效。7. 点击任何代码或URI旁边的复制按钮将其复制到剪贴板。
我的密钥安全吗?会被发送到服务器吗?
您的密钥100%安全,绝不会离开您的浏览器。所有TOTP计算——HMAC计算、代码生成、验证和QR码渲染——完全使用JavaScript在客户端执行。该工具使用otpauth库进行RFC 6238计算,使用浏览器原生的Web Crypto API进行加密操作。没有任何数据传输到任何服务器,没有分析工具跟踪您的密钥内容,也没有数据存储在浏览器标签页内存之外的任何地方。这使得该工具在开发和QA期间使用真实TOTP密钥进行测试时是安全的。
otpauth:// URI格式是什么?
otpauth:// URI是一种标准化格式,用于将TOTP(和HOTP)密钥配置到身份验证应用程序中。当编码为QR码时,扫描它会自动使用所有必要参数配置身份验证应用程序。格式为:otpauth://totp/{label}?secret={secret}&issuer={issuer}&algorithm={algorithm}&digits={digits}&period={period}。标签通常格式为issuer:account(例如MyApp:user@example.com),密钥是Base32编码的共享密钥,可选参数包括算法(SHA1、SHA256、SHA512)、位数(6或8)和周期(默认30秒)。此格式受Google Authenticator、Microsoft Authenticator、Authy、FreeOTP以及几乎所有符合标准的身份验证应用程序支持。
TOTP和HOTP有什么区别?
TOTP(基于时间的一次性密码,RFC 6238)根据当前时间生成代码。代码每N秒(默认30秒)自动更改,服务器和客户端都使用当前Unix时间戳作为计数器,因此只要时钟合理准确,它们就保持同步。HOTP(基于HMAC的一次性密码,RFC 4226)根据递增计数器生成代码。每次生成代码时,计数器增加一,服务器和客户端必须独立跟踪计数器——如果生成了代码但未使用,可能会发生不同步。TOTP从技术上讲是HOTP的扩展,其中「计数器」是从当前时间除以周期得出的。该工具专注于TOTP,因为它是现代2FA实现的主流标准。
为什么我的TOTP代码与身份验证应用程序不同?
如果此工具生成的代码与您的身份验证应用程序不匹配,请检查以下常见原因:(1) 密钥错误——确保Base32密钥准确输入,因为即使一个字符错误也会产生完全不同的代码。(2) 算法不匹配——大多数服务使用SHA-1(默认值),如果您的服务使用SHA-256或SHA-512,请确保工具和应用程序配置相同。(3) 位数不匹配——大多数服务使用6位数,但某些企业工具使用8位数。(4) 周期不匹配——标准周期为30秒,但某些服务使用60或90秒。(5) 时钟偏差——TOTP对时间敏感,如果您的设备时钟偏差较大(超过30秒),代码将不匹配。请确保您的系统时钟通过NTP同步。
TOTP作为双因素身份验证有多安全?
正确实现时,TOTP为身份验证提供了强大的第二因素。每个代码仅在一个时间周期(通常30秒)内有效,限制了拦截攻击的窗口。它通过HMAC使用加密哈希函数(SHA-1、SHA-256或SHA-512)从密钥派生代码,使得从观察到的代码反向工程密钥在计算上不可行。共享密钥在注册时建立一次,在正常身份验证过程中不再传输。但是,TOTP并非对所有攻击免疫——网络钓鱼攻击可以实时捕获和重放代码,如果服务器的密钥存储被突破,共享密钥可能会被泄露。为了获得最高安全性,请将TOTP与WebAuthn/FIDO2等防钓鱼方法结合使用。
代码示例
// TOTP Generator using Web Crypto API (RFC 6238)
function base32Decode(base32) {
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
const cleaned = base32.toUpperCase().replace(/[^A-Z2-7]/g, '');
let bits = '';
for (const char of cleaned) {
const val = alphabet.indexOf(char);
if (val === -1) throw new Error(`Invalid Base32 character: ${char}`);
bits += val.toString(2).padStart(5, '0');
}
const bytes = new Uint8Array(Math.floor(bits.length / 8));
for (let i = 0; i < bytes.length; i++) {
bytes[i] = parseInt(bits.substr(i * 8, 8), 2);
}
return bytes;
}
async function generateTOTP(secret, options = {}) {
const {
algorithm = 'SHA-1',
digits = 6,
period = 30,
timestamp = Date.now(),
} = options;
const counter = Math.floor(timestamp / 1000 / period);
const counterBuffer = new ArrayBuffer(8);
const counterView = new DataView(counterBuffer);
counterView.setUint32(4, counter, false);
const keyBytes = base32Decode(secret);
const cryptoKey = await crypto.subtle.importKey(
'raw', keyBytes,
{ name: 'HMAC', hash: algorithm },
false, ['sign']
);
const hmacBuffer = await crypto.subtle.sign('HMAC', cryptoKey, counterBuffer);
const hmac = new Uint8Array(hmacBuffer);
const offset = hmac[hmac.length - 1] & 0x0f;
const code =
((hmac[offset] & 0x7f) << 24) |
((hmac[offset + 1] & 0xff) << 16) |
((hmac[offset + 2] & 0xff) << 8) |
(hmac[offset + 3] & 0xff);
const otp = code % Math.pow(10, digits);
return otp.toString().padStart(digits, '0');
}
function generateSecret(bits = 160) {
const bytes = new Uint8Array(bits / 8);
crypto.getRandomValues(bytes);
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
let result = '', buffer = 0, bufferLength = 0;
for (const byte of bytes) {
buffer = (buffer << 8) | byte;
bufferLength += 8;
while (bufferLength >= 5) {
bufferLength -= 5;
result += alphabet[(buffer >> bufferLength) & 0x1f];
}
}
if (bufferLength > 0) {
result += alphabet[(buffer << (5 - bufferLength)) & 0x1f];
}
return result;
}
// Usage:
// const secret = generateSecret();
// const code = await generateTOTP(secret);
// console.log('TOTP:', code);