인증 앱의 6자리 코드는 어떻게 만들어질까: TOTP 계산 과정
30초마다 바뀌는 인증 앱 코드가 서버와 맞아떨어지는 원리를 RFC 테스트 값으로 따라갑니다. 시간 단계, HMAC, 동적 절단, 서버 검증, 피싱에 약한 이유까지 정리했습니다.
2단계 인증 앱의 6자리 코드는 30초마다 바뀝니다. 앱은 서버에 묻지 않고 코드를 만드는데도 서버는 그 숫자를 받아들입니다. 양쪽이 같은 비밀 키와 같은 시간 단계 규칙으로 각자 계산하고, 두 시계가 충분히 맞아 있기 때문입니다. 이 계산이 RFC 6238의 TOTP(Time-Based One-Time Password)입니다.
계산 과정
먼저 현재 유닉스 시간을 카운터 T = floor((현재 시각 - T0) / X)로 바꿉니다. 기본값이 X=30초, T0=0이므로 59초면 T=1, 60초면 T=2입니다.
그다음은 카운터 기반 알고리즘 HOTP(RFC 4226)와 같습니다. 공유 비밀 키로 8바이트 카운터 T의 HMAC-SHA-1(RFC 2104)을 계산해 20바이트를 얻습니다. HMAC은 키를 섞어 해시 함수를 두 번 적용하는 메시지 인증 코드이고, RFC 6238은 SHA-256이나 SHA-512를 써도 된다(MAY)고 합니다. 이어지는 동적 절단(dynamic truncation)은 마지막 바이트의 하위 4비트를 오프셋으로 삼아 4바이트를 읽고, 부호 처리 혼란을 피하려고 맨 앞 비트를 지웁니다. 이 31비트 정수를 10^자릿수로 나눈 나머지가 코드이며, 자릿수가 모자라면 앞에 0을 채웁니다.
RFC 테스트 키 12345678901234567890과 T=1로 계산한 HMAC-SHA-1은 75a48a19d4cbe100644e8ac1397eea747a2d33ab입니다(RFC 4226 부록 D). 마지막 바이트 ab에서 오프셋 11을 얻고, 그 위치의 c1 39 7e ea에서 맨 앞 비트를 지우면 1,094,287,082입니다. 10^6으로 나눈 나머지는 287082, 10^8로 나눈 나머지는 RFC 6238 표의 59초 값인 94287082입니다.
검증과 재사용 방지
서버는 코드를 받은 시각으로 T를 계산합니다. RFC 6238은 이전 시간 단계와도 비교하되 네트워크 지연에는 최대 한 단계만 허용하라고 권장합니다(RECOMMEND). 범위가 넓을수록 공격할 수 있는 시간도 늘기 때문입니다. 검증에 성공한 OTP는 다시 받아들이면 안 됩니다(MUST NOT). 미국 연방 정부 시스템용 지침인 NIST SP 800-63B-4도 OTP를 유효한 동안 한 번만 수락하게 하고(SHALL), 64비트보다 짧은 OTP에는 시도 횟수 제한을 요구합니다(SHALL). 100만 가지뿐인 6자리 코드가 여기에 해당하며, 별도 규정이 없으면 연속 실패는 100회 이하로 제한합니다. 서버도 같은 키로 코드를 다시 계산하므로 저장된 키는 엄격히 보호해야 합니다.
피싱에는 약하다
TOTP는 비밀번호만 빼낸 공격자는 막지만, 코드 자체를 받아 가는 공격은 막지 못합니다. NIST SP 800-63B-4는 OTP 인증을 피싱 저항성이 없다고 분류합니다. 손으로 입력하는 코드는 인증 중인 세션에 묶이지 않아서, 가짜 사이트가 코드를 유효한 동안 진짜 사이트로 넘기면 로그인이 성립할 수 있습니다. 이 지침에서 피싱 저항성은 채널 바인딩이나 WebAuthn 같은 검증자 이름 바인딩을 갖춘 암호 인증으로만 얻습니다. 미국 연방 시스템의 경우 AAL2에서는 피싱 저항 수단을 하나 이상 제공해야 하고(SHALL), AAL3에서는 피싱 저항성이 필수입니다.
RFC 6238, 4226, 2104는 구현 간 호환을 위한 Informational RFC이며 표준 트랙 문서가 아닙니다. NIST의 SHALL 요구는 미국 정부 시스템 범위에서만 규범적입니다.
참고
- RFC 6238, TOTP: Time-Based One-Time Password Algorithm (2011)
- RFC 4226, HOTP: An HMAC-Based One-Time Password Algorithm (2005)
- RFC 2104, HMAC: Keyed-Hashing for Message Authentication (1997)
- NIST SP 800-63B-4 (2025)