TOTP 생성기 - 시간 기반 일회용 비밀번호 도구 온라인
2FA 테스트를 위한 TOTP 코드 생성 및 검증. 시크릿 키, QR 코드, otpauth:// URI 생성. RFC 6238 준수 — 100% 클라이언트 사이드, 시크릿 키가 브라우저를 떠나지 않습니다.
TOTP 코드를 생성하려면 시크릿 키를 입력하세요
자주 묻는 질문
TOTP 생성기란 무엇인가요?
TOTP(시간 기반 일회용 비밀번호) 생성기는 RFC 6238 표준을 따르는 시간 제한 인증 코드를 생성하는 온라인 도구입니다. TOTP는 Google Authenticator, Authy, Microsoft Authenticator 같은 인기 인증 앱의 핵심 알고리즘입니다. 공유 비밀 키와 현재 Unix 타임스탬프를 HMAC(해시 기반 메시지 인증 코드) 함수로 결합한 후, 결과를 잘라내어 짧은 숫자 코드(보통 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% 안전하며 브라우저를 벗어나지 않습니다. HMAC 계산, 코드 생성, 검증, QR 코드 렌더링 등 모든 TOTP 연산은 JavaScript를 사용하여 완전히 클라이언트 측에서 수행됩니다. 이 도구는 RFC 6238 연산에 otpauth 라이브러리를 사용하고 암호화 작업에 브라우저의 네이티브 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)는 증가하는 카운터를 기반으로 코드를 생성합니다. 코드가 생성될 때마다 카운터가 1씩 증가하며, 서버와 클라이언트가 독립적으로 카운터를 추적해야 합니다. 코드를 생성했지만 사용하지 않으면 비동기화가 발생할 수 있습니다. TOTP는 기술적으로 "카운터"가 현재 시간을 주기로 나눈 값인 HOTP의 확장입니다. 이 도구는 현대 2FA 구현의 주류 표준인 TOTP에 초점을 맞추고 있습니다.
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);