로그인 스크립트에 2 단계 인증 추가하기

사이버 보안Beginner
지금 연습하기

소개

다중 인증 (Multi-factor authentication, MFA) 은 두 가지 이상의 증명 방식을 요구함으로써 계정 탈취 위험을 줄여줍니다. 비밀번호는 사용자가 '알고 있는 것'에 해당합니다. 두 번째 요소는 별도의 장치, 앱, 파일 또는 하드웨어 토큰에서 생성된 코드일 수 있습니다.

이 실습에서는 비밀번호만 사용하는 Python 로그인 스크립트로 시작하여 간단한 두 번째 인증 요소를 추가해 봅니다. 실습을 초보자도 이해하기 쉽고 결과가 명확하도록 만들기 위해, 두 번째 요소로는 보호된 비밀 파일에서 생성된 HMAC 기반 코드를 사용합니다.

비밀번호 전용 로그인 테스트

이 단계에서는 시작용 로그인 스크립트를 실행합니다. 이 스크립트는 사용자 이름과 비밀번호만 확인하는데, 이는 사용자가 알고 있는 정보만 증명으로 사용하므로 단일 요소 인증에 해당합니다.

프로젝트 디렉토리로 이동합니다:

cd /home/labex/project

준비된 파일들을 확인합니다:

ls -l

사용자 데이터베이스를 확인합니다:

cat users.json

비밀번호 전용 로그인을 성공적으로 수행합니다:

python3 login.py alice wonderland

출력 결과는 다음과 같아야 합니다:

login ok

이제 잘못된 비밀번호를 입력해 봅니다:

python3 login.py alice wrong

이 명령은 login failed를 출력해야 합니다. 로그인 실패 시 0 이 아닌 상태 코드로 종료되는데, 이는 이 테스트에서 정상적인 동작입니다.

시작 상태에 대한 검사기를 실행합니다:

python3 mfa-check.py password-only

예상 결과는 다음과 같습니다:

password-only: complete

2 단계 인증 비밀값 생성

이 단계에서는 두 번째 인증 요소를 위한 별도의 비밀값을 생성합니다. 실제 MFA 시스템에서는 이 비밀값이 인증 앱이나 하드웨어 토큰에 저장될 수 있습니다. 이 실습에서는 동작을 쉽게 확인하고 검증할 수 있도록 로컬 파일로 저장합니다.

비밀값을 생성합니다:

printf 'labex-mfa-secret-2026\n' > mfa_secret.txt

소유자만 읽고 쓸 수 있도록 파일 권한을 제한합니다:

chmod 600 mfa_secret.txt

권한을 확인합니다:

ls -l mfa_secret.txt

권한 비트가 -rw-------로 시작해야 합니다:

-rw------- ... mfa_secret.txt

비밀값에 대한 검사기를 실행합니다:

python3 mfa-check.py secret

예상 결과는 다음과 같습니다:

secret: complete

로그인 코드 생성

이 단계에서는 간단한 코드 생성기를 만듭니다. HMAC-SHA256 을 사용하여 비밀 파일과 사용자 이름을 결합합니다. HMAC 을 사용하는 이유는 동일한 입력과 비밀값으로 동일한 짧은 코드를 생성할 수 있으면서도, 비밀값을 모르는 사람은 코드를 쉽게 생성할 수 없기 때문입니다.

generate-code.py를 생성합니다:

cat > generate-code.py <<'PY'
#!/usr/bin/env python3
import hmac
import hashlib
import sys
from pathlib import Path

if len(sys.argv) != 2:
    print("usage: generate-code.py USERNAME")
    raise SystemExit(2)

username = sys.argv[1]
secret = Path("mfa_secret.txt").read_text().strip().encode()
digest = hmac.new(secret, username.encode(), hashlib.sha256).hexdigest()
print(digest[:6])
PY

실행 권한을 부여합니다:

chmod +x generate-code.py

alice에 대한 코드를 생성합니다:

python3 generate-code.py alice

다음과 같은 6 자리 코드가 출력되어야 합니다:

21f18f

생성기에 대한 검사기를 실행합니다:

python3 mfa-check.py generator

예상 결과는 다음과 같습니다:

generator: complete

2 단계 인증 적용

이 단계에서는 로그인 스크립트를 교체하여 비밀번호와 생성된 코드를 모두 요구하도록 변경합니다. 이것이 MFA 의 핵심 변경 사항입니다. 이제 비밀번호만으로는 로그인이 불가능합니다.

login.py를 MFA 가 적용된 버전으로 교체합니다:

cat > login.py <<'PY'
#!/usr/bin/env python3
import hmac
import hashlib
import json
import sys
from pathlib import Path

USERS = Path("users.json")
SECRET = Path("mfa_secret.txt")

def expected_code(username):
    secret = SECRET.read_text().strip().encode()
    digest = hmac.new(secret, username.encode(), hashlib.sha256).hexdigest()
    return digest[:6]

def main():
    if len(sys.argv) != 4:
        print("usage: login.py USERNAME PASSWORD MFA_CODE")
        return 2

    username, password, code = sys.argv[1], sys.argv[2], sys.argv[3]
    users = json.loads(USERS.read_text())
    if users.get(username, {}).get("password") != password:
        print("login failed")
        return 1

    if not hmac.compare_digest(code, expected_code(username)):
        print("mfa failed")
        return 1

    print("login ok")
    return 0

if __name__ == "__main__":
    raise SystemExit(main())
PY

스크립트에 실행 권한이 있는지 확인합니다:

chmod +x login.py

새로운 코드를 생성하여 셸 변수에 저장합니다:

CODE="$(python3 generate-code.py alice)"

올바른 비밀번호와 코드를 함께 사용하여 로그인합니다:

python3 login.py alice wonderland "$CODE"

출력 결과는 다음과 같아야 합니다:

login ok

MFA 적용 여부를 확인하는 검사기를 실행합니다:

python3 mfa-check.py enforced

예상 결과는 다음과 같습니다:

enforced: complete

MFA 실패 및 성공 테스트

이 단계에서는 최종 로그인 동작을 테스트합니다. 두 번째 요소 없이 올바른 비밀번호만 입력하면 실패해야 하고, 잘못된 코드를 입력해도 실패해야 하며, 올바른 비밀번호와 코드를 모두 입력해야만 성공해야 합니다.

먼저, 이전의 비밀번호 전용 명령을 시도해 봅니다:

python3 login.py alice wonderland || true

MFA 코드가 누락되었으므로 새로운 사용법 메시지가 출력되어야 합니다:

usage: login.py USERNAME PASSWORD MFA_CODE

잘못된 코드를 입력해 봅니다:

python3 login.py alice wonderland 000000 || true

출력 결과는 다음과 같아야 합니다:

mfa failed

올바른 코드를 생성하여 로그인합니다:

CODE="$(python3 generate-code.py alice)"
python3 login.py alice wonderland "$CODE"

출력 결과는 다음과 같아야 합니다:

login ok

전체 검사기를 실행합니다:

python3 mfa-check.py all

최종 출력은 모든 MFA 구성 요소가 완료되었음을 보여주어야 합니다:

secret: complete
generator: complete
enforced: complete

요약

비밀번호 전용 로그인 스크립트를 간단한 다중 인증 로그인 흐름으로 변경했습니다. 별도의 MFA 비밀값을 생성하고, HMAC 기반 코드를 생성하며, 로그인 스크립트에서 해당 코드를 요구하도록 설정하고, 실패 및 성공 사례를 모두 테스트했습니다.

보안의 핵심은 독립적인 두 번째 증명이 요구될 때, 비밀번호가 탈취되더라도 그것만으로는 충분하지 않게 만드는 것입니다.