블로그보안

TOTP 인증 앱 코드는 실제로 어떻게 작동하는가

TOTP(RFC 6238)가 공유 비밀과 현재 시각을 어떻게 여섯 자리 코드로 바꾸는지, 왜 양쪽이 일치하는지, 그리고 무엇을 막고 무엇을 막지 못하는지 정리합니다.

인증 앱이 여섯 자리 코드를 보여주고 서버가 그것을 받아들일 때, 둘 사이에 네트워크 호출은 일어나지 않았습니다. 폰과 서버는 둘 다 이미 쥐고 있는 두 입력에서 같은 숫자를 독립적으로 계산했습니다. 등록 시 고정된 공유 비밀, 그리고 현재 시각입니다. 그것이 TOTP 뒤의 발상 전부입니다. 네트워크가 필요 없는 두 번째 요소이고, 비행기 안에서도, 서버가 닿지 않아도 작동합니다. 양쪽이 같은 시계에 대해 같은 결정론적 함수를 돌리기 때문입니다.

TOTP는 RFC 6238입니다. HOTP(RFC 4226) 위의 얇은 계층이고, HOTP 자체가 HMAC 위의 얇은 계층입니다. 그것을 이해한다는 것은 서로 위에 쌓인 정확히 세 가지를 이해하는 것입니다.

HOTP: 카운터의 키 있는 해시

HOTP는 공유 비밀 K와 카운터 C에서 코드를 만듭니다. 카운터는 8바이트 정수입니다. 구성은 다음과 같습니다.

  1. HMAC-SHA1(K, C)를 계산합니다. 20바이트 MAC입니다.
  2. 그 20바이트를 숫자로 무너뜨리려 동적 절단을 적용합니다.
  3. 그 숫자를 10^digits로 나눈 나머지를 취해 표시 코드를 얻습니다.

동적 절단이 유일하게 자명하지 않은 단계입니다. HMAC 마지막 바이트의 하위 니블(0–15 값)을 오프셋으로 읽습니다. 그 오프셋에서 시작해 HMAC에서 4바이트를 취하고, 결과를 양수로 유지하려 최상위 비트를 마스킹하고, 결과 31비트 정수를 10^digits로 나눈 나머지를 취합니다. 기본 6자리면 mod 1000000이고, 000000부터 999999까지의 숫자가 남습니다.

mac      = HMAC-SHA1(secret, counter)   # 20바이트
offset   = mac[19] & 0x0f                # 마지막 니블, 0..15
chunk    = mac[offset .. offset+3]       # 그 오프셋에서 4바이트
binary   = chunk & 0x7fffffff            # 부호 비트 마스킹 -> 31비트
code     = binary % 10^digits            # 기본 digits = 6

오프셋이 해시 자체에서 유도되므로 동적이라 부릅니다. 항상 같은 윈도우를 읽는 대신 표본 4바이트를 MAC 전체에 흩뿌려, 출력이 고정된 조각이 아니라 다이제스트 전체를 반영하게 합니다.

TOTP: 카운터를 시각으로 교체

HOTP는 카운터를 수동으로 증가시킵니다. 하드웨어 토큰을 누를 때마다 C가 하나 오르고, 서버가 기대 값을 추적합니다. 그 동기화는 깨지기 쉽습니다. 누름을 놓치면 양쪽이 어긋나기 때문입니다.

TOTP는 수동 카운터를 없앱니다. 사용자가 올리는 숫자 대신, 카운터가 시계입니다.

counter = floor(currentUnixTime / period)    # period 기본값 30
code    = HOTP(secret, counter)

30초 주기로 카운터는 분당 두 번 바뀝니다. 12:00:00부터 12:00:29까지는 한 값을 쥐고, 12:00:30에 다음으로 넘어갑니다. 폰과 서버 둘 다 자기 시계를 읽고, 30으로 나누고, 내림하고, 그 정수를 위의 정확한 HOTP 구성에 넣습니다. 그들의 시계가 윈도우 안에서 일치하면, 서로 말 한마디 없이 같은 여섯 자리를 계산합니다.

그것이 메커니즘 전부입니다. TOTP는 문자 그대로 현재 시각의 키 있는 해시를 여섯 자리로 절단한 것입니다.

등록: QR 코드

공유 비밀은 설정 시 정확히 한 번 양쪽에 닿아야 합니다. 표준 운반체는 otpauth:// URI이고, 거의 항상 QR 코드로 전달됩니다.

otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA1&digits=6&period=30

필드는 다음과 같습니다.

  • secret — 공유 키, Base32 인코딩. 손으로 타이핑되고 글꼴로 표시돼도 견디는 알파벳이기 때문입니다. 디코딩된 바이트가 HMAC에 먹이는 K입니다.
  • issueraccount — 앱이 보여주는 라벨("Example", "[email protected]"). 장식적이고 계산에 들어가지 않습니다.
  • algorithmSHA1, SHA256, 또는 SHA512. 기본 SHA1.
  • digits6 또는 8. 기본 6.
  • period — 스텝당 초. 기본 30.

기본값은 SHA1, 6자리, 30초이고, 실무에서 사실상 의무입니다. 명세는 SHA-256이나 SHA-512와 8자리를 허용하지만, 많은 인기 인증 앱이 algorithmdigits 파라미터를 조용히 무시하고 기본값을 가정합니다. 그래서 8자리 SHA-256 비밀을 발급하는 서버는 사용자 절반의 앱이 결코 검증되지 않는 6자리 SHA-1 코드를 만드는 것을 보게 될 수 있습니다. 양 끝을 통제하면 더 강한 옵션을 쓸 수 있고, 사용자가 자기 인증 앱을 가져오면 기본값을 고수하세요.

(여기 HMAC-SHA1은 SHA-1 충돌 문제가 아닙니다. TOTP는 HMAC에 의존하고, HMAC은 SHA-1을 서명용으로 깨뜨린 충돌과는 다르고 훨씬 강한 공격에 대한 SHA-1의 저항성에 의존합니다. HMAC-SHA1은 여전히 안전하다고 여겨집니다. 그것은 웹훅 서명을 인증하는 데 쓰이는 같은 키 있는 해시 프리미티브이고, TOTP는 메시지가 현재 시각인 그 구성입니다.)

시계 차이와 검증 윈도우

시계는 표류합니다. 시각이 40초 빠른 폰은 서버가 아직 현재 스텝에 있는 동안 다음 스텝의 코드를 계산합니다. 서버가 자기 현재 스텝의 코드만 받아들이면, 그 사용자들은 매번 로그인에 실패할 것입니다.

그래서 서버는 윈도우를 받아들입니다. 흔한 선택은 ±1 스텝입니다. 서버는 현재 카운터, 이전 것, 다음 것을 검사하고, 그 셋 중 무엇에든 매칭을 받아들입니다. 30초 주기로 이는 시계 차이 약 ±30초를 허용하면서 어느 순간이든 유효 코드 수를 셋으로 유지합니다.

윈도우를 넓히는 것은 직접적 트레이드오프입니다.

  • 넓게 — 표류로 인한 정당한 실패가 줄지만, 더 많은 코드가 동시에 유효해, 맹목적으로 추측하는 공격자가 더 큰 표적을 갖고 중계된 코드가 더 오래 쓸 수 있습니다.
  • 좁게 — 더 빡빡한 보안이지만, 시계가 엉성한 사용자가 잠깁니다.

±1이 보통의 균형입니다. 만성적 표류를 보는 서버는 모두를 위해 윈도우를 넓히기 보다 각 사용자의 측정된 오프셋을 기록해 윈도우를 옮기기도 합니다.

TOTP가 막는 것, 그리고 막지 않는 것

TOTP는 한 가지 특정한 것을 방어합니다. 비밀번호를 훔친 공격자가 코드 없이는 여전히 로그인할 수 없고, 코드는 30초마다 바뀝니다. 자격 증명 스터핑과 데이터베이스 유출에 대해 그것은 실질적이고 큰 개선입니다. 강한 첫 번째 요소 비밀번호 뒤의 자연스러운 두 번째 요소입니다.

한계도 똑같이 특정합니다.

  • 피싱 가능합니다. 코드는 사용자가 읽고 타이핑하는 짧은 문자열입니다. 실시간 피싱 사이트는 그것을 요청해 같은 윈도우 안에 진짜 서버로 중계할 수 있습니다. TOTP의 무엇도 코드를 요청하는 사이트에 묶지 않습니다. 자격 증명이 출처에 암호학적으로 한정되어 다른 곳에서 재생될 수 없는 WebAuthn/패스키와 다릅니다. 피싱 저항성이 필요하면 TOTP는 그것이 아닙니다.
  • 윈도우 안 재전송. 코드는 스텝 전체(그리고 받아들여진 이웃)에 유효합니다. 서버는 성공하는 즉시 코드를 사용됨으로 표시하고 그 윈도우 나머지 동안 같은 코드를 거부해야 합니다. 그러지 않으면 위의 중계가 공격자에게 수십 초를 사 줍니다.
  • 저장 비밀 위험. 서버는 폰이 가진 것과 같은 비밀을 저장합니다. TOTP 비밀을 노출하는 데이터베이스 침해는 공격자가 모든 피해자의 코드를 생성하게 합니다. 이 비밀들은 저장 시 암호화와 비밀번호 해시와 같은 취급 규율이 필요합니다. 작동하는 코드로 되돌릴 수 있으니 어쩌면 더 그렇습니다.

그 주의점들에도 TOTP는 SMS 일회용 코드를 결정적으로 이깁니다. SMS는 SIM 스왑과 SS7 공격으로 가로채일 수 있고, 통신사 전달에 의존하며, 매 로그인마다 비밀을 네트워크로 보냅니다. TOTP는 비밀을 한 번 교환하고 다시는 전송하지 않습니다. 패스키가 선택지가 아닐 때, 앱 기반 TOTP가 올바른 기본값입니다.

한 줄 요약

TOTP는 시계의 HMAC입니다. 비밀을 한 번 공유하고, 시각에 합의하고, 키 있는 해시를 여섯 자리로 절단하고, 표류를 위해 작은 윈도우를 받아들이세요. 나머지 전부, 즉 QR 코드, Base32, 검증 윈도우는 그 핵심을 둘러싼 배관입니다. 메커니즘을 직접 살펴보려면, 우리 TOTP 생성기에 Base32 비밀을 붙여 넣고 코드가 매 30초 경계에서 넘어가는 것을 지켜보세요. 위의 정확한 구성을 브라우저에서 돌리며 서버로 아무것도 보내지 않습니다.