PEP 506 – 표준 라이브러리에 Secrets 모듈 추가
- Author:
- Steven D’Aprano <steve at pearwood.info>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 19-Sep-2015
- Python-Version:
- 3.6
- Post-History:
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 PEP는 토큰 생성과 같은 일반적인 보안 관련 기능을 제공하는 모듈을 Python 표준 라이브러리에 추가할 것을 제안합니다.
정의
이 제안에서 사용되는 몇 가지 일반적인 약어는 다음과 같습니다.
- PRNG:
의사 난수 제너레이터입니다. 특정한 바람직한 통계적 특성을 지닌 무작위처럼 보이는 수를 생성하는 데 사용되는 결정적 알고리즘입니다.
- CSPRNG:
암호학적으로 강력한 의사 난수 제너레이터입니다. 예측에 견딜 수 있는 무작위처럼 보이는 수를 생성하는 데 사용되는 알고리즘입니다.
- MT:
메르센 트위스터입니다. 광범위하게 연구되었으며 현재
random모듈에서 기본값으로 사용되는 PRNG입니다.
근거
이 제안은 Python 표준 라이브러리로 인해 개발자가 부주의하게 심각한 보안 오류를 저지르기 쉬워진다는 우려에서 비롯되었습니다. OpenBSD의 창립자인 Theo de Raadt는 Guido van Rossum에게 연락하여 비밀번호, 보안 토큰, 세션 키 및 이와 유사한 민감한 정보를 생성하는 데 MT를 사용하는 것에 대해 우려를 표명했습니다 [1].
random 모듈의 문서에는 기본값이 보안 목적으로 적합하지 않다고 명시적으로 나와 있지만 [2], 많은 Python 개발자가 이 경고를 놓치거나 무시하거나 오해할 수 있다고 강하게 여겨집니다. 특히 다음과 같습니다.
- 개발자가 문서를 읽지 않아 결과적으로 경고를 보지 못했을 수 있습니다.
- 모듈을 특정 방식으로 사용하는 것이 보안에 영향을 미친다는 사실을 깨닫지 못했을 수 있습니다.
- 문제가 있을 수 있다는 점을 깨닫지 못한 채, 모범 사례를 제공하지 않는 웹사이트에서 코드를 복사했거나 기법을 배웠을 수 있습니다.
Google에서 “python how to generate passwords”를 검색했을 때 첫 번째 [3] 결과는 random 모듈의 기본 함수를 사용하는 튜토리얼입니다 [4]. 웹 애플리케이션에서 사용하도록 의도된 것은 아니지만, 유사한 기법이 그러한 상황에서 사용될 가능성이 높습니다. 두 번째 결과는 비밀번호 생성에 관한 StackOverflow 질문입니다 [5]. 채택된 답변을 포함하여 제시된 답변 대부분이 기본 함수를 사용합니다. 한 사용자가 기본값은 쉽게 손상될 수 있다고 경고했을 때, 그 사용자는 “제 생각에는 너무 걱정하시는 것 같습니다.”라는 말을 들었습니다 [6].
이는 기존 random 모듈이 비밀번호나 보안 토큰과 같은 것을 생성할 때 매력적인 골칫거리임을 강하게 시사합니다.
좀 더 철학적인 관점의 추가적인 동기는 이 아이디어를 처음 제안한 게시물에서 찾을 수 있습니다 [7].
제안
대안 제안들은 random 모듈의 기본 PRNG에 초점을 맞추어, 개발자가 보안에 대해 생각하지 않고도 기반으로 사용할 수 있는 “기본적으로 안전한” 암호학적 강력 프리미티브를 제공하려 했습니다. (아래의 대안을 참조하십시오.) 이 제안은 다른 접근 방식을 취합니다.
- 표준 라이브러리는 이미 암호학적으로 강력한 프리미티브를 제공하지만, 많은 사용자는 그러한 프리미티브가 존재한다는 사실이나 언제 사용해야 하는지를 알지 못합니다.
- 암호화에 익숙하지 않은 사용자에게 보안 코드를 작성하도록 요구하는 대신, 표준 라이브러리에는 보안 토큰 생성과 같은 가장 일반적인 요구 사항을 위한 바로 사용할 수 있는 “배터리” 세트를 포함해야 합니다. 이 코드는 필요를 직접 충족할 뿐만 아니라(“비밀번호 재설정 토큰은 어떻게 생성합니까?”), 개발자가 이를 통해 배울 수 있는 허용 가능한 관행의 예로도 기능합니다 [8].
이를 위해 이 PEP에서는 표준 라이브러리에 secrets라는 제안된 이름의 새 모듈을 추가할 것을 제안합니다. 이 모듈에는 보안과 관련된 일반적인 활동을 위한 바로 사용할 수 있는 함수 세트와 일부 저수준 프리미티브가 포함됩니다.
secrets이 비밀로 유지해야 하는 모든 것(비밀번호, 토큰 등)을 다루는 기본 모듈이 되고, random 모듈은 하위 호환성을 유지하도록 하자는 제안입니다.
API 및 구현
이 PEP에서는 secrets 모듈에 다음 함수를 추가할 것을 제안합니다.
- 다음 형식으로 비밀번호 복구, 세션 키 등에 사용하기에 적합한 토큰을 생성하는 함수입니다.
- 바이트로,
secrets.token_bytes; - 16진수 숫자를 사용하는 텍스트로,
secrets.token_hex; - URL 안전 base-64 인코딩을 사용하는 텍스트로,
secrets.token_urlsafe.
- 바이트로,
os.urandom을 직접 사용하거나random.SystemRandom을 사용하는 시스템 CSPRNG에 대한 제한된 인터페이스입니다.random모듈과 달리, 이 인터페이스는 시드 설정, 상태 가져오기 또는 설정, 비균등 분포를 위한 메서드를 제공할 필요가 없습니다. 다음을 제공해야 합니다.- 시퀀스에서 항목을 선택하는 함수인
secrets.choice입니다. - 주어진 수의 무작위 비트 및/또는 바이트를 정수로 생성하는 함수인
secrets.randbits입니다. - 0 이상 주어진 상한 미만의 반열린 범위에서 무작위 정수를 반환하는 함수인
secrets.randbelow입니다 [9].
- 시퀀스에서 항목을 선택하는 함수인
- 타이밍 공격에 내성을 유지하면서 텍스트 또는 바이트 다이제스트의 동등성을 비교하는 함수인
secrets.compare_digest입니다.
이러한 용도를 지원하기 위해 random 모듈에 새 CSPRNG를 추가할 필요가 없으며, SystemRandom이면 충분하다는 의견이 지배적인 것으로 보입니다.
몇 가지 예시 구현은 Alyssa (Nick) Coghlan이 제시했으며 [10], 최소한의 API는 Tim Peters가 제시했습니다 [11]. 이 아이디어는 “cryptography” 모듈의 이슈 추적기에서도 논의되었습니다 [12]. 다음 의사 코드를 실제 구현의 출발점으로 삼아야 합니다.:
from random import SystemRandom
from hmac import compare_digest
_sysrand = SystemRandom()
randbits = _sysrand.getrandbits
choice = _sysrand.choice
def randbelow(exclusive_upper_bound):
return _sysrand._randbelow(exclusive_upper_bound)
DEFAULT_ENTROPY = 32 # bytes
def token_bytes(nbytes=None):
if nbytes is None:
nbytes = DEFAULT_ENTROPY
return os.urandom(nbytes)
def token_hex(nbytes=None):
return binascii.hexlify(token_bytes(nbytes)).decode('ascii')
def token_urlsafe(nbytes=None):
tok = token_bytes(nbytes)
return base64.urlsafe_b64encode(tok).rstrip(b'=').decode('ascii')
secrets 모듈 자체는 순수 Python으로 작성되며, 다른 Python 구현에서도 이를 변경 없이 쉽게 사용하거나 필요에 따라 조정할 수 있습니다. 구현은 BitBucket에서 확인할 수 있습니다 [13].
기본 인자
한 가지 어려운 질문은 “토큰은 몇 바이트여야 합니까?”라는 것입니다. 이 질문에는 “token_*” 함수에 기본 엔트로피 양을 제공하여 도움을 드릴 수 있습니다. nbytes 인자가 None이거나 지정되지 않은 경우 기본 엔트로피가 사용됩니다. 이 기본값은 중간 수준 보안 용도에서 안전하다고 예상할 수 있을 만큼 충분히 커야 하지만, 향후에는 유지보수 릴리스에서도 변경될 가능성이 있습니다 [14].
명명 규칙
모듈에서 사용하는 명명 규칙에 관한 질문이 있습니다 [15]: “randrange”와 같은 C 스타일 명명 규칙을 사용할지, 아니면 “random_range”와 같은 더 Python다운 이름을 사용할지에 대한 질문입니다.
비공개 SystemRandom 인스턴스의 단순한 바운드 메서드(예: randrange)이거나 그러한 메서드를 감싸는 얇은 래퍼인 함수는 익숙한 이름을 유지해야 합니다. 새로운 기능인 함수(예: 다양한 token_* 함수)는 더욱 파이썬다운 이름을 사용합니다.
대안
한 가지 대안은 random 모듈이 제공하는 기본 PRNG를 변경하는 것입니다 [16]. 이에 대해서는 상당한 회의론과 노골적인 반대가 제기되었습니다.
- CSPRNG가 현재 PRNG보다 느릴 수 있다는 우려가 있습니다(MT의 경우에는 현재 PRNG도 이미 상당히 느립니다).
- 일부 애플리케이션(예: 과학적 시뮬레이션과 게임플레이 재현)은 PRNG를 알려진 상태로 시드할 수 있어야 하지만, CSPRNG는 설계상 이를 지원하지 않습니다.
random모듈의 또 다른 주요 용도는 초보자가 작성하는 간단한 “숫자 맞히기” 게임이며, 많은 사람은 이를 더 어렵게 만들 수 있는 변경을random모듈에 가하는 것을 매우 꺼립니다.random모듈에서 MT를 제거하자는 제안은 없지만, 비-CSPRNG를 선택하도록 하거나 하위 호환성이 깨지는 변경을 해야 한다는 생각에 상당한 반감이 있었습니다.- MT에 대한 실제 공격은 대개 PHP 애플리케이션을 대상으로 합니다. PHP의 MT 버전은 잘못된 시딩 기법 때문에 Python 버전보다 공격하기 훨씬 쉬운 대상으로 여겨집니다 [17]. 따라서 Python 애플리케이션에 대한 입증된 공격이 없는 상황에서 많은 사람은 하위 호환성이 깨지는 변경에 반대합니다.
Alyssa Coghlan은 시스템 CSPRNG를 기본값으로 사용하는 전역 구성 가능 PRNG에 대한 이전 제안을 내놓았지만, 이후 이 제안을 지지하기 위해 철회했습니다.
다른 언어와의 비교
- PHP
PHP에는
uniqid[18]라는 함수가 포함되어 있으며, 기본적으로 현재 시간을 마이크로초 단위로 나타낸 값을 기반으로 하는 13자 문자열을 반환합니다. Python 문법으로 변환하면 다음 시그니처를 가집니다.:def uniqid(prefix='', more_entropy=False)->str
PHP 문서에서는 이 함수가 보안 목적으로 적합하지 않다고 경고합니다. 그럼에도 불구하고 여러 성숙하고 잘 알려진 PHP 애플리케이션이 해당 목적으로 이 함수를 사용합니다(인용 필요).
PHP 5.3 이상에는
openssl_random_pseudo_bytes[19]라는 함수도 포함되어 있습니다. Python 문법으로 변환하면 대략 다음 시그니처를 가집니다.:def openssl_random_pseudo_bytes(length:int)->Tuple[str, bool]
이 함수는 지정된 길이의 의사 난수 바이트 문자열과, 해당 문자열이 암호학적으로 강력한 것으로 간주되는지를 나타내는 불리언 플래그를 반환합니다. PHP 매뉴얼에서는 오래되었거나 손상된 플랫폼을 제외하면 True가 아닌 값을 반환하는 경우는 드물어야 한다고 설명합니다.
- JavaScript
다소 피상적인 검색에 따르면 [20], JavaScript에서 강력한 난수 값을 생성하는 잘 알려진 표준 함수는 없는 것으로 보입니다.
Math.random은 심각한 약점 때문에 암호화 용도에 부적합함에도 불구하고 자주 사용됩니다 [21]. 최근 몇 년 동안 대부분의 브라우저가window.crypto.getRandomValues[22]를 지원하게 되었습니다.Node.js는 풍부한 암호화 모듈인
crypto[23]를 제공하며, 이 모듈의 대부분은 이 PEP의 범위를 벗어납니다. 이 모듈에는 난수 바이트를 생성하는 단일 함수인crypto.randomBytes가 포함되어 있습니다. - Ruby
Ruby 표준 라이브러리에는
SecureRandom모듈 [24]이 포함되어 있으며, 다음 메서드를 제공합니다.- base64 - Base64로 인코딩된 난수 문자열을 반환합니다.
- hex - 난수 16진수 문자열을 반환합니다.
- random_bytes - 무작위 바이트 문자열을 반환합니다.
- random_number - 인자에 따라 range(0, n) 범위의 무작위 정수 또는 0.0과 1.0 사이의 무작위 부동 소수점 수를 반환합니다.
- urlsafe_base64 - URL에서 안전하게 사용할 수 있는 Base64 인코딩 문자열을 반환합니다.
- uuid - 버전 4 무작위 범용 고유 식별자를 반환합니다.
모듈 이름은 무엇이어야 합니까?
“random.safe” 서브모듈을 추가하자는 제안이 있었으며, Python의 선(禪)에 나오는 “네임스페이스는 정말 훌륭한 아이디어입니다”라는 코안을 인용했습니다. 그러나 선의 저자인 Tim Peters는 이 아이디어에 반대하는 입장을 밝혔으며 [25], 최상위 모듈을 권장합니다.
지금까지 python-ideas 메일링 리스트에서 논의한 결과, “secrets”라는 이름은 어느 정도 지지를 받았으며 강한 반대는 없었습니다.
이미 같은 이름의 서드 파티 모듈이 존재하지만 [26], 사용되지 않고 버려진 것으로 보입니다.
자주 묻는 질문
- 질문: 이것은 실제 문제입니까? 설마 MT는 아무도 출력을 예측할 수 없을 만큼 충분히 무작위이지 않습니까?
답변: 보안 전문가들의 공통된 의견은 보안 맥락에서 MT가 안전하지 않다는 것입니다. MT의 내부 상태를 재구성하는 것은 어렵지 않으므로 [27] [28], 과거와 미래의 모든 값을 예측할 수 있습니다. 무작위성에 MT를 사용하는 시스템에 대한 알려진 실용적 공격이 여러 가지 있습니다 [29].
- 질문: PHP에 대한 공격은 그렇다 치더라도, Python 소프트웨어에 대해 알려진 공격도 있습니까?
답변: 예. 적어도 Zope와 Plone에는 취약점이 있었습니다. Hanno Schlichting은 다음과 같이 논평했습니다 [30]:
"In the context of Plone and Zope a practical attack was demonstrated, but I can't find any good non-broken links about this anymore. IIRC Plone generated a random number and exposed this on each error page along the lines of 'Sorry, you encountered an error, your problem has been filed as <random number>, please include this when you contact us'. This allowed anyone to do large numbers of requests to this page and get enough random values to reconstruct the MT state. A couple of security related modules used random instead of system random (cookie session ids, password reset links, auth token), so the attacker could break all of those."
Christian Heimes는 2012년에 이 문제를 Zope 보안 팀에 보고했으며 [31], 관련된 CVE 취약점이 최소 두 개 있고 [32], Django에는 이 문제에 대한 우회책도 최소 하나 있습니다 [33].
- 질문: 이것은 SSL과 같은 전문 암호화 소프트웨어의 대안입니까?
답변: 아닙니다. 이것은 모든 기능을 갖춘 “핵 원자로”가 아니라 “배터리가 포함된” 솔루션입니다. 이는 일부 기본적인 보안 오류를 완화하기 위한 것이며, 보안과 관련된 모든 문제에 대한 해결책은 아닙니다. Alyssa Coghlan이 자신의 이전 제안을 언급하며 다음과 같이 말했습니다 [34]:
"...folks really are better off learning to use things like cryptography.io for security sensitive software, so this change is just about harm mitigation given that it's inevitable that a non-trivial proportion of the millions of current and future Python developers won't do that."
- 질문: 비밀번호 제너레이터는 어떻습니까?
답변: 암호 생성기의 요구 사항은 표준 라이브러리에 적합할 만큼 일관되지 않다는 것이 공통된 의견입니다 [35]. 모듈의 최초 릴리스에는 어떤 비밀번호 제너레이터도 포함되지 않으며, 대신
itertools모듈의 레시피와 같은 레시피로 문서에 제공됩니다 [36]. - 질문: Linux에서
secrets는 차단되는 /dev/random을 사용합니까, 아니면 차단되지 않는 /dev/urandom을 사용합니까? 다른 플랫폼은 어떻습니까?답변:
secrets는os.urandom및random.SystemRandom을 기반으로 하며, 이는 운영 체제의 최상의 암호학적 무작위성 소스에 대한 인터페이스입니다. Linux에서는/dev/urandom일 수 있고 [37], Windows에서는CryptGenRandom()일 수 있지만, 자세한 구현 세부 사항은 문서 및/또는 소스 코드를 확인하십시오.
참고 문헌
Copyright
This document has been placed in the public domain.