파이썬에서 암호화적으로 안전한 비밀번호 생성: random 대 secrets
엔트로피는 비밀번호의 품질을 문자열 자체의 속성이 아닌 생성 과정의 특성으로 정의합니다. 공식 H = L × log₂(N)은 길이 L 동안 크기 N의 알파벳에서 각 문자가 독립적이고 동일한 확률로 선택된다고 가정합니다. 소문자 8자(N=26)의 경우: 약 37.6비트. 전체 ASCII(N=94)의 경우: 약 79비트.
흔한 오해: 비밀번호의 표면적 무작위성이 강도를 보장한다는 것입니다. f9A$kL2pQzX1과 같은 두 비밀번호는 형식적 엔트로피가 동일할 수 있지만 사용된 생성기에 따라 실제 보안성은 크게 다를 수 있습니다.
random 모듈: 결정론적 PRNG
random 모듈은 내부 상태가 약 19937비트인 메르센 트위스터(MT19937)를 구현합니다. 각 값은 이전 값에 기반해 결정론적입니다. 기본 시드는 os.urandom()에서 가져오지만, PRNG의 고유 속성은 그대로 유지됩니다:
- 결정론성: 하나의 시드는 하나의 예측 가능한 시퀀스를 생성합니다.
- 복구 가능성: 상태는 약 624개의 원시 32비트 출력에서 재구성될 수 있습니다.
- 암호화적 보안 부재: 상태 복구 공격에 취약합니다.
일반적인 코드 예시:
import random
import string
alphabet = string.ascii_letters + string.digits
def generate_password(length=12):
return ''.join(random.choice(alphabet) for _ in range(length))
비밀번호는 PRNG 시퀀스의 일부입니다. 상태가 알려지면 시퀀스는 예측 가능합니다.
secrets 모듈: 시스템 엔트로피에 대한 직접 접근
Secrets는 각 호출에 대해 os.urandom()을 직접 사용합니다:
- secrets.randbelow(n) → os.urandom().
- random.SystemRandom() → os.urandom().
고정된 상태나 결정론성이 없습니다. 각 바이트는 하드웨어 소스(인터럽트, 네트워크 노이즈, 마우스 이동)에서 추출됩니다.
차이는 결정적입니다: random은 보안의 환상을 만들지만, secrets는 실제 보안을 제공합니다.
실제 공격: 시드 및 상태 복구
GigaChat에서 생성된 5개의 연속 비밀번호 실험(길이 12, Python 3.9.18, 시간 2026년 3월 24일 11:11):
가설: 시드 = 서버 타임스탬프 ±1시간.
from datetime import datetime, timedelta
base_time = datetime(2026, 3, 24, 11, 11, 0)
window_seconds = 3600
for offset in range(-window_seconds, window_seconds):
t = base_time + timedelta(seconds=offset)
seed = int(t.timestamp())
# random.seed(seed) 초기화 및 검증
결과: 일치 없음. 시드에 os.urandom()을 사용하면 시간 기반 무차별 대입 공격을 무력화합니다.
MT19937에 대한 실제 공격은 624개의 원시 값이 필요합니다. 비밀번호는 왜곡된 출력(선택 → 문자)으로, 5개 비밀번호에 대해 총 60개의 선택만 있습니다. 복구는 불가능합니다.
공격 경계:
- 원시 PRNG 출력 필요.
- 예측 가능한 시드(시간, PID).
- 동일 생성기 반복 사용.
- 알려진 알고리즘.
핵심 요점
- 엔트로피는 결과가 아닌 과정의 속성: 항상 생성기를 확인하세요.
- random(메르센 트위스터)은 충분한 데이터가 주어지면 상태 복구에 취약합니다.
- secrets/os.urandom은 비밀번호, 키, 토큰에 대한 유일한 선택입니다.
- random의 무작위성 환상은 프로덕션에서 위험합니다.
- PRNG 공격은 시드 오류나 상태 유출 시에만 현실적입니다.
중급/시니어 개발자를 위한 권장사항
코드에서 구현:
import secrets
import string
alphabet = string.ascii_letters + string.digits + string.punctuation
def secure_password(length=16):
return ''.join(secrets.choice(alphabet) for _ in range(length))
피하세요:
- 인증에 random 사용.
- 고정된 시드.
- 생성기 재사용.
테스트: 10⁶개의 비밀번호 생성, 균일 분포 확인(카이제곱 검정).
— Editorial Team
아직 댓글이 없습니다.