본문 바로가기
분류 전/개념 노트장

Interval이 다를 때 OTP 코드가 다른 이유와 드림핵 SuperSecure OTP

by jwcs 2024. 7. 7.
728x90

드림핵 SuperSecure OTP 문제를 풀다가 OTP 인증이 먹히지 않아 이유와 해결 방법에 대해 구해봤다.

 

OTP 인증 방식

드림핵 문제는 TOTP 방식을 사용한다. TOTP는 시간에 기반한 일회용 비밀번호를 의미하며, 특정 시간 간격마다 새로운 비밀번호를 생성하는 알고리즘이다.

 

TOTP에서는 시크릿 키와 타임 스탬프를 기반으로 OTP를 생성한다. 타임 스탬프 값은 현재 유닉스 시간과 interval을 기준으로 값이 정해진다.

QR코드 사용

QR 코드를 사용하는 이유는 시크릿 키를 공유하기 위해서다. 서버와 클라이언트가 같은 시크릿 키를 가지고 있어야 같은 OTP 코드를 생성할 수 있다. 따라서 QR 코드를 사용함으로써 시크릿 키를 쉽게 공유할 수 있다.

 

QR 코드에는 OTP를 설정하기 위한 비밀 키와 추가 정보가 포함된 URL이 인코딩되어 있다.

otpauth://totp/{accountname}?secret={secret}&issuer={issuer}
  • otpauth://otp/
    • OTP 인증 프로토콜과 TOTP 방식을 나타낸다
  • {accountname}
    • 사용자의 계정 이름이다
  • secret={secret}
    • 비밀 키이다
  • issuser={issuser}
    • OTP를 발행하는 서비스 또는 애플리케이션의 이름이다

 

Interval이 같아야 하는 이유

SuperSecure OTP 문제를 보면 interval이 120이다. 근데 기본적인 interval은 30이다. interval은 OTP 코드 값이 갱신되는 주기를 의미한다. 여기서 OTP 코드가 같아지는 구간이 있을 것으로 생각되지만 실제론 그렇지 않았다. 그 이유가 뭘까?

https://github.com/pyauth/pyotp/blob/develop/src/pyotp/totp.py#L106

 

pyotp/src/pyotp/totp.py at develop · pyauth/pyotp

Python One-Time Password Library. Contribute to pyauth/pyotp development by creating an account on GitHub.

github.com

 

위에서 볼 수 있듯이 `현재 시간 / interval`을 하고 있다.

따라서 서버 interval이 120인 경우

타임 스탬프 = (현재 유닉스 시간 / 120)

 

클라이언트 interval이 30인 경우

타임 스탬프 = (현재 유닉스 시간 / 30)이다. 따라서 같은 값이 잘 나오지 않는 것이다.

 

기본 interval은 30인데 실습을 통해 알 수 있다.

 

from PIL import Image
from pyzbar.pyzbar import decode
import pyscreenshot as ImageGrab
import pyotp

def capture_screen(region=None):
    """
    화면의 특정 영역을 캡처하여 이미지를 반환합니다.
    region은 (left, top, right, bottom) 형식의 튜플입니다.
    """
    if region:
        im = ImageGrab.grab(bbox=region)  # 특정 영역 캡처
    else:
        im = ImageGrab.grab()  # 전체 화면 캡처
    return im

def decode_qr(image):
    """
    이미지에서 QR 코드를 디코딩하여 데이터와 유형을 반환합니다.
    """
    decoded_objects = decode(image)
    for obj in decoded_objects:
        qr_data = obj.data.decode('utf-8')
        qr_type = obj.type
        print(f"Type: {qr_type}, Data: {qr_data}")
        return qr_data
    return None

def main():
    # 화면의 특정 영역을 캡처 (전체 화면을 캡처하려면 region=None으로 설정)
    # region 좌표는 필요에 따라 조정하세요
    region = None
    image = capture_screen(region)

    # 캡처한 이미지에서 QR 코드 디코딩
    qr_data = decode_qr(image)

    if qr_data:
        # QR 코드 데이터에서 OTP 비밀 키 추출
        # QR 데이터 형식: otpauth://totp/...
        import re
        match = re.search(r'secret=([A-Z2-7]+)', qr_data)
        if match:
            otp_secret = match.group(1)
            print("OTP Secret:", otp_secret)

            # TOTP 객체 생성
            current_otp = pyotp.totp.TOTP(otp_secret,interval=120).now()
            basic_otp = pyotp.totp.TOTP(otp_secret).now()
            print("Current OTP:", current_otp)
            print("basic OTP", basic_otp)

        else:
            print("Failed to extract OTP secret from QR code data.")
    else:
        print("No QR code found in the captured image.")

if __name__ == "__main__":
    main()

 

드림핵 문제에서 QR 코드 스캔 후, 위 코드로 비교해보면 인증 오류 발생 이유가 interval임을 알 수 있다. 위에서 current otp를 사용하면 문제 풀이 진행할 수 있다.

728x90
반응형