`secrets` — 시크릿 관리를 위한 안전한 난수 생성

secrets — 시크릿 관리를 위한 안전한 난수 생성

버전 3.6에서 추가됨.

secrets 모듈은 비밀번호, 계정 인증, 보안 토큰, 그리고 관련 시크릿 같은 데이터를 관리하기에 알맞은, 암호학적으로 강력한 난수를 생성하는 데 써요.

특히 secretsrandom 모듈의 기본 유사 난수 생성기보다 우선해 써야 해요. 그 기본 생성기는 보안이나 암호학이 아니라 모델링과 시뮬레이션에 맞게 설계됐거든요.

출처: Python 표준 라이브러리

본문

난수 (Random numbers)

secrets 모듈은 운영체제가 제공하는 가장 안전한 난수원에 대한 접근을 제공해요.

class secrets.SystemRandom

운영체제가 제공하는 최고 품질의 난수원으로 난수를 생성하는 클래스예요. 자세한 내용은 random.SystemRandom을 참고하세요.

secrets.choice(*seq*)

비어 있지 않은 시퀀스에서 무작위로 하나의 요소를 골라 반환해요.

secrets.randbelow(*exclusive_upper_bound*)

[0, exclusive_upper_bound) 범위의 무작위 int를 반환해요.

secrets.randbits(*k*)

k개의 무작위 비트를 가진 음이 아닌 int를 반환해요.

토큰 생성 (Generating tokens)

secrets 모듈은 비밀번호 재설정, 추측하기 어려운 URL 같은 애플리케이션에 쓰기 좋은 보안 토큰을 생성하는 함수를 제공해요.

secrets.token_bytes(*nbytes=None*)

nbytes 개수의 바이트를 담은 무작위 바이트 문자열을 반환해요.

nbytes가 지정되지 않았거나 None이면 DEFAULT_ENTROPY를 대신 사용해요.

>>> token_bytes(16)
b'\xebr\x17D*t\xae\xd4\xe3S\xb6\xe2\xebP1\x8b'

secrets.token_hex(*nbytes=None*)

16진수로 된 무작위 텍스트 문자열을 반환해요. 그 문자열은 nbytes개의 무작위 바이트를 갖고, 각 바이트는 두 개의 16진수 자리로 변환돼요.

nbytes가 지정되지 않았거나 None이면 DEFAULT_ENTROPY를 대신 사용해요.

>>> token_hex(16)
'f9bf78b9a18ce6d46a0cd2b0b86df9da'

secrets.token_urlsafe(*nbytes=None*)

nbytes개의 무작위 바이트를 담은 URL에 안전한 무작위 텍스트 문자열을 반환해요. 텍스트는 Base64로 인코딩되므로 평균적으로 각 바이트는 대략 1.3개의 문자로 이어져요.

nbytes가 지정되지 않았거나 None이면 DEFAULT_ENTROPY를 대신 사용해요.

>>> token_urlsafe(16)
'Drmhze6EPcv0fN_81Bj-nA'

토큰은 몇 바이트를 써야 할까?

무차별 대입 공격(brute-force attacks)에 안전하려면 토큰에 충분한 무작위성이 필요해요. 아쉽게도 컴퓨터가 더 강력해지고 더 짧은 시간에 더 많은 추측을 할 수 있게 되면서 "충분한" 기준도 반드시 올라가요. 2015년 기준으로, 32바이트(256비트)의 무작위성이 secrets 모듈에 기대되는 일반적인 용도에 충분하다고 여겨져요.

토큰 길이를 직접 관리하고 싶다면 여러 token_* 함수에 int 인자를 줘서 토큰에 쓰는 무작위성의 양을 명시적으로 지정할 수 있어요. 그 인자는 사용할 무작위성의 바이트 수로 여겨져요.

그렇지 않고 인자가 제공되지 않거나 None이면 token_* 함수는 DEFAULT_ENTROPY를 사용해요.

secrets.DEFAULT_ENTROPY

token_* 함수가 사용하는 기본 무작위성 바이트 수예요. 정확한 값은 유지보수 릴리스 중을 포함해 언제든 바뀔 수 있어요.

기타 함수 (Other functions)

secrets.compare_digest(*a*, *b*)

문자열 또는 bytes-like 객체 ab가 같으면 True, 아니면 False를 반환해요. 이때 "상수 시간 비교"(constant-time compare)를 사용해 타이밍 공격의 위험을 줄여요. 자세한 내용은 hmac.compare_digest()를 참고하세요.

레시피와 모범 사례

이 섹션은 기본 수준의 보안을 관리하기 위해 secrets를 쓰는 레시피와 모범 사례를 보여줘요.

8자리 영숫자 비밀번호 생성:

import string
import secrets
alphabet = string.ascii_letters + string.digits
password = ''.join(secrets.choice(alphabet) for i in range(8))

참고

애플리케이션은 비밀번호를 평문이든 암호화든 복구 가능한 형식으로 저장해서는 안 돼요. 비밀번호는 암호학적으로 강력한 단방향(되돌릴 수 없는) 해시 함수로 소금을 치고 해시해야 해요.

소문자·대문자·숫자 3자리 이상을 각각 하나 이상 포함하는 10자리 영숫자 비밀번호 생성:

import string
import secrets
alphabet = string.ascii_letters + string.digits
while True:
    password = ''.join(secrets.choice(alphabet) for i in range(10))
    if (any(c.islower() for c in password)
            and any(c.isupper() for c in password)
            and sum(c.isdigit() for c in password) >= 3):
        break

XKCD 스타일의 암호 문구(passphrase) 생성:

import secrets
# On standard Linux systems, use a convenient dictionary file.
# Other platforms may need to provide their own word-list.
with open('/usr/share/dict/words') as f:
    words = [word.strip() for word in f]
    password = ' '.join(secrets.choice(words) for i in range(4))

비밀번호 복구 애플리케이션에 쓰기 좋은, 추측하기 어려운 보안 토큰이 담긴 임시 URL 생성:

import secrets
url = 'https://example.com/reset=' + secrets.token_urlsafe()

더 알아보기

  • PEP 506: secrets 모듈 제안서.