PEP 504 – 기본적으로 시스템 RNG 사용
- Author:
- Alyssa Coghlan <ncoghlan at gmail.com>
- Status:
- Withdrawn
- Type:
- Standards Track
- Created:
- 15-Sep-2015
- Python-Version:
- 3.6
- Post-History:
- 15-Sep-2015
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
Python은 현재 random 모듈의 모듈 수준 API에 결정론적 메르센 트위스터 난수 제너레이터를 기본적으로 사용하므로, 사용자는 “보안에 민감한” 작업을 수행할 때 대신 암호학적으로 안전한 os.urandom 또는 random.SystemRandom 인터페이스나 cryptography와 같은 서드 파티 라이브러리를 사용하도록 전환해야 한다는 점을 알아야 합니다.
안타깝게도 이러한 접근 방식으로 인해 자신이 보안 민감한 작업을 수행하고 있다는 사실을 인지하지 못한 개발자가 기본 모듈 수준 API를 사용하고, 그 결과 사용자에게 불필요한 위험을 노출하는 상황이 발생했습니다.
이는 급성 문제는 아니지만 만성적인 문제이며, 보안 결함이 도입된 시점과 악용되는 시점 사이에 긴 지연이 발생하는 경우가 많기 때문에 개발자가 경험을 통해 자연스럽게 배우기 어렵습니다.
이 문제에 대한 궁극적으로 광범위한 해결책을 제공하기 위해, 이 PEP는 Python 3.6에서 Python이 기본적으로 시스템 난수 제너레이터를 사용하도록 전환하고, 개발자가 새로운 random.ensure_repeatable() API를 사용하거나 자신의 random.Random() 인스턴스를 명시적으로 생성하여 프로세스 전체에서 결정론적 난수 제너레이터를 사용하도록 선택할 것을 요구합니다.
기존 코드에 미치는 영향을 최소화하기 위해 결정성이 필요한 모듈 수준 API는 암묵적으로 결정론적 PRNG로 전환합니다.
PEP 철회
이 PEP에 대한 논의 중 Steven D’Aprano는 기본 비밀번호와 기타 토큰 생성과 같은 보안 민감 작업을 처리하는 “명백한 하나의 방법”을 제공하는 표준화된 secrets 모듈을 제공하자는 더 간단한 대안을 제안했습니다.
Steven의 제안은 기존 random 모듈 API에 호환성 위험을 도입하지 않으면서 그러한 토큰을 생성하는 쉬운 방법과 올바른 방법을 일치시키는 원하는 효과를 가지므로, 이 PEP는 Steven의 제안을 PEP 506으로 더욱 다듬는 작업을 위해 철회되었습니다.
제안
현재 보안 민감 애플리케이션에서 random 모듈의 모듈 수준 함수를 사용하는 것은 결코 올바르지 않습니다. 이 PEP는 Python 3.6 이상에서 이러한 경고를 변경하여, 해당 프로세스에서 random.ensure_repeatable()이 직접 또는 간접적으로 호출된 경우 random 모듈의 모듈 수준 함수를 보안 민감 애플리케이션에 사용하는 것이 올바르지 않다고 제안합니다.
이를 달성하기 위해 random의 모듈 수준 호출 가능 객체는 현재처럼 random.Random 인스턴스의 바인딩된 메서드가 아니라, 기존 random._inst 모듈 속성의 해당 메서드에 위임하는 함수로 변경됩니다.
기본적으로 이 속성은 random.SystemRandom 인스턴스에 바인딩됩니다.
그런 다음 새로운 random.ensure_repeatable() API는 random._inst 속성을 system.Random 인스턴스에 다시 바인딩하여, 추가적인 간접 참조 수준을 제외하고 이전 Python 버전에 존재했던 것과 동일한 모듈 수준 API 동작을 복원합니다.:
def ensure_repeatable():
"""Switch to using random.Random() for the module level APIs
This switches the default RNG instance from the cryptographically
secure random.SystemRandom() to the deterministic random.Random(),
enabling the seed(), getstate() and setstate() operations. This means
a particular random scenario can be replayed later by providing the
same seed value or restoring a previously saved state.
NOTE: Libraries implementing security sensitive operations should
always explicitly use random.SystemRandom() or os.urandom in order to
correctly handle applications that call this function.
"""
if not isinstance(_inst, Random):
_inst = random.Random()
기존 코드에 미치는 영향을 최소화하기 위해 다음 모듈 수준 함수 중 하나를 호출하면 random.ensure_repeatable()이 암묵적으로 호출됩니다.
random.seedrandom.getstaterandom.setstate
random.Random 또는 random.SystemRandom 클래스 API에는 변경 사항이 제안되지 않으며, 자신의 난수 생성기를 명시적으로 인스턴스화하는 애플리케이션은 이 제안의 영향을 전혀 받지 않습니다.
암묵적 선택에 대한 경고
Python 3.6에서는 다음 검사를 사용하여 결정론적 PRNG 사용을 암묵적으로 선택하면 사용 중단 경고가 발생합니다.:
if not isinstance(_inst, Random):
warnings.warn(DeprecationWarning,
"Implicitly ensuring repeatability. "
"See help(random.ensure_repeatable) for details")
ensure_repeatable()
경고의 구체적인 문구에는 Stack Overflow에 적절한 답변이 추가되어야 하며, 이는 print 호출에서 괄호가 누락되어 추가된 사용자 지정 오류 메시지에 대해 수행된 방식과 같습니다 [10].
Python 2.7이 보안 수정 전용 모드로 전환된 후 최초의 Python 3 릴리스에서는 사용 중단 경고가 RuntimeWarning으로 승격되어 기본적으로 표시됩니다.
이 PEP는 특정 시드가 주어졌을 때 동일한 출력 시퀀스를 생성하는 결정적 의사 난수 생성기인 기본 RNG를 프로세스 전체에서 사용하도록 보장하는 기능을 결코 제거할 것을 제안하지 않습니다. 이 기능은 모델링 및 시뮬레이션 시나리오에서 널리 사용되며, ensure_repeatable()이 직접 또는 간접적으로 호출되도록 요구하는 것은 결정론적 PRNG 사용의 잠재적인 보안 영향을 충분히 고려하지 않고 웹 애플리케이션에서 모듈 수준 난수 API를 보안 민감 작업에 사용하는 경우를 해결하기에 충분한 개선입니다.
성능 영향
random.Random와 random.SystemRandom 사이의 큰 성능 차이로 인해 Python 3.6으로 포팅된 애플리케이션은 다음과 같은 경우 상당한 성능 저하를 겪게 됩니다.
- 애플리케이션이 모듈 수준 난수 API를 사용하는 경우
- 암호학적 품질의 무작위성이 필요하지 않은 경우
- 애플리케이션이
random.seed,random.getstate또는random.setstate를 호출하여 이미 결정론적 PRNG 사용을 암묵적으로 다시 선택하지 않은 경우 - 애플리케이션이
random.ensure_repeatable을 명시적으로 호출하도록 업데이트되지 않았습니다.
이는 Python 3.6 What’s New 가이드의 이식 섹션에 언급되며, 영향을 받는 애플리케이션의 __main__ 모듈에 다음 코드를 포함하도록 권장합니다.:
if hasattr(random, "ensure_repeatable"):
random.ensure_repeatable()
암호학적 품질의 난수성이 필요한 애플리케이션은 속도와 관계없이 시스템 난수 제너레이터를 사용해야 하므로, 그러한 경우 이 PEP에서 제안하는 변경은 이전에 잠재해 있던 보안 결함을 해결합니다.
문서 변경 사항
random 모듈 문서는 seed, getstate 및 setstate 인터페이스에 대한 설명을 모듈의 뒤쪽으로 옮기고, 새로운 ensure_repeatable 함수와 관련 보안 경고에 대한 설명을 추가하도록 업데이트됩니다.
모듈 문서의 해당 섹션에는 ensure_repeatable로 활성화되는 결정적 PRNG(게임, 모델링 및 시뮬레이션, 소프트웨어 테스트)와 기본적으로 사용되는 시스템 RNG(암호학, 보안 토큰 생성)의 각각의 사용 사례에 대한 논의도 추가됩니다. 이 논의에서는 후자의 작업에 서드 파티 보안 라이브러리를 사용할 것도 권장합니다.
근거
기한과 예산의 압박 속에서 안전한 소프트웨어를 작성하는 일은 어려운 문제입니다. 이는 개인 식별 정보가 포함된 데이터 침해 [1]에 대한 정기적인 통지와, 자동차 [2]와 같은 새로운 시스템이 인터넷에 연결될 때 보안 고려 사항을 반영하지 못한 사례에서 드러납니다. 또한 인터넷 [4]에서 쉽게 접할 수 있는 프로그래밍 조언 중 상당수가 컴퓨터 보안의 수학적 난해성을 전혀 고려하지 않는다는 점도 문제입니다. 이러한 문제를 더욱 악화시키는 것은 방어자가 잠재적인 취약점모두를 다뤄야 한다는 사실입니다. 한 번의 실수로 다른 방어 수단을 무력화할 수 있기 때문입니다 [11].
이 마지막 측면을 특히 어렵게 만드는 요인 중 하나는 부적절하게 사용하면 조용한 보안 실패를 일으키는 API입니다. 즉, 자신이 하는 일이 잘못되었다는 사실을 알아낼 수 있는 유일한 방법은 코드를 검토하는 누군가가 “이는 잠재적인 보안 문제입니다”라고 말하거나, 그러한 간과로 인해 자신이 담당하는 시스템이 침해되는 것입니다. (시스템이 침해된 후에도 해당 시스템에 대한 책임은 여전히 자신에게 있을 뿐 아니라, 침해가 어떻게 발생했는지 사후에 파악할 수 있을 만큼 침입 탐지 및 감사 메커니즘도 충분히 갖춰져 있어야 합니다.)
이러한 상황은 “보안 피로”의 주요 원인입니다. 개발자는 (대개 당연히 [9] 그렇다고 느끼며) 보안 엔지니어가 “쉬운 방법으로 하지 마십시오. 보안 취약점이 발생합니다”라는 말만 하며 모든 시간을 보낸다고 생각하게 됩니다.
세계에서 가장 인기 있는 언어 중 하나 [8]를 설계한 우리는 더 많은 상황에서 쉬운 방법이 올바른 방법(또는 적어도 “잘못되지 않은” 방법)이 되도록 함으로써 이 문제를 줄일 수 있습니다. 그러면 개발자와 보안 엔지니어는 실제로 흥미로운 위협을 완화하는 데 더 많은 시간을 쓰고, 기본 언어 동작과 씨름하는 데는 더 적은 시간을 쓸 수 있습니다.
논의
“ensure_repeatable”을 “ensure_deterministic”보다 사용하는 이유
이는 어떤 단어의 전문 용어로서의 의미가 일반적인 의미와 충돌하는 경우이지만, 기술적으로는 동일한 의미입니다.
기술적인 관점에서 “결정적 RNG”는 알고리즘과 현재 상태를 알고 있으면 임의의 미래 상태를 안정적으로 계산할 수 있다는 의미입니다.
문제는 “결정적”이라는 말만으로는 그러한 조건을 전달하지 못한다는 점입니다. 따라서 일반적인 의미에는 익숙하지만 기술적 의미에 추가된 조건에는 익숙하지 않은 사람들은 이를 “예측 가능하다” 또는 “무작위가 아니다”로 해석할 가능성이 높습니다.
전통적인 RNG를 설명하는 데 “결정적”이라는 표현을 사용할 때의 두 번째 문제는, 시스템 RNG로는 할 수 없고 전통적인 RNG로는 할 수 있는 일이 무엇인지 실제로 알려 주지 않는다는 점입니다.
“ensure_repeatable”은 이 두 가지 문제를 모두 해결하고자 합니다. 이 이름의 일반적인 의미가 시스템 RNG보다 결정적 PRNG를 선호하는 주된 이유를 정확히 설명하기 때문입니다. 즉, 동일한 시드 값을 제공하거나 이전에 저장한 PRNG 상태를 복원하여 동일한 출력 시퀀스를 반복할 수 있도록 보장합니다.
Python 3.6 이상에서만 기본값 변경
ssl 모듈의 기능을 향상하고 HTTPS 인증서를 기본적으로 올바르게 검증하도록 전환하는 것과 같은 최근의 다른 보안 변경 사항은 현재 지원되는 모든 Python 버전에 변경 사항을 백포팅할 만큼 중요하다고 여겨졌습니다.
이 경우의 차이는 정도의 차이입니다. 이 특정 변경을 그렇지 않을 경우보다 몇 년 일찍 도입함으로써 얻는 추가적인 이점은 유지 보수 릴리스에 이처럼 큰 변경을 적용하는 데 따르는 추가 노력이나 안정성 위험을 정당화하기에 충분하지 않습니다.
모듈 수준 함수 유지
일반적인 하위 호환성 고려 사항에 더해 Python은 교육 목적으로 널리 사용되며, 현재 random 모듈 API를 사용할 수 있다고 가정하는 방대한 교육 자료를 무효화하고 싶지 않습니다. 따라서 이 제안은 대부분의 공개 API를 수정 없이 계속 사용할 수 있을 뿐 아니라 새로운 경고도 발생하지 않도록 보장합니다.
결정적 RNG를 암묵적으로 선택할 때 경고
Python은 모델링 및 시뮬레이션 목적으로 널리 사용되며 이러한 경우에는 결정적 PRNG를 암묵적으로 선택하는 것이 올바르므로, 이를 암묵적으로 선택하도록 해야 합니다. 또한 많은 경우 이러한 소프트웨어 모델에는 최신 Python 버전에서도 계속 작동하도록 관리할 전담 유지 보수 팀이 없습니다.
안타깝게도 os.urandom에서 얻은 데이터를 사용하여 random.seed를 명시적으로 호출하는 것도 온라인에서 쉽게 볼 수 있는 결함 있는 “Python에서 보안 토큰을 생성하는 방법” 안내서에 등장하는 실수입니다.
먼저 DeprecationWarning을 사용하고 이후에는 RuntimeWarning을 사용하여 결정적 PRNG로 암묵적으로 전환하지 말라고 알리는 것은, 암호학적으로 안전한 RNG가 필요한 향후 사용자가 random.seed()를 호출하지 않도록 유도하고, 실제로 결정적 제너레이터가 필요한 사용자가 random.ensure_repeatable()을 명시적으로 호출하도록 유도하기 위한 것입니다.
사용자 공간 CSPRNG 도입 피하기
python-ideas에서 이 제안에 관해 이루어진 최초의 논의 [5]에서는 상대적으로 느린 시스템 난수 제너레이터를 기본값으로 사용하는 대신, 암호학적으로 안전한 의사 난수 제너레이터를 도입하여 기본적으로 사용하자고 제안했습니다.
이 접근법의 문제는 무작위 수 생성이 핵심 성능 경로에 포함되지 않을 수도 있는 애플리케이션을 위해 보안에 민감한 상황에서 추가적인 실패 지점을 도입한다는 것입니다 [7].
암호학적 품질의 난수성이 필요한 애플리케이션은 속도와 관계없이 시스템 난수 제너레이터를 사용해야 하므로, 그러한 경우입니다.
결정론적 PRNG는 “충분히 안전하지” 않습니까?
한마디로 “아니요”입니다. 그렇기 때문에 모듈 문서에는 보안에 민감한 용도로 사용하지 말라는 경고가 있습니다. 현재로서는 특히 Python의 난수 제너레이터에 관한 연구를 알지 못하지만, PHP의 난수 제너레이터에 관한 연구 [3]에서는 해당 하위 시스템의 약점을 이용하여 널리 사용되는 PHP 웹 애플리케이션에서 비밀번호 복구 토큰에 대한 실제 공격을 가능하게 할 수 있음이 입증되었습니다.
그러나 보안 소프트웨어 개발의 규칙 중 하나는 “공격은 더 나아지기만 하고 결코 악화되지 않는다”는 것이므로, Python 3.6이 출시될 때쯤에는 Python의 결정론적 PRNG에 대한 실제 공격이 공개적으로 문서화되어 있을 수도 있습니다.
Python 생태계의 보안 피로
지난 몇 년 동안 컴퓨팅 업계 전체는 우리가 모두 의존하는 공유 네트워크 인프라를 “기본적으로 안전한” 상태로 업그레이드하기 위해 공동의 노력을 기울여 왔습니다. 네트워크 서비스 개발( OpenStack Infrastructure-as-a-Service 플랫폼 포함)과 일반적인 Linux 시스템 관리에 가장 널리 사용되는 프로그래밍 언어 중 하나인 Python에 그 부담의 상당 부분이 돌아온 것은 당연히 이러한 문제가 그다지 중요하지 않은 다른 맥락에서 Python을 사용하는 Python 사용자들에게 답답한 일입니다.
이러한 고려 사항은 python-ideas에 게시된 최초 초안 개념 [6]에 비해 이 제안에서 하위 호환성이 크게 향상되도록 이끈 주요 요인 중 하나입니다.
감사의 글
- 암호학적으로 안전한 난수 제너레이터를 기본값으로 사용하는 방안을 진지하게 고려하자고 Guido van Rossum에게 제안해 주신 Theo de Raadt에게 감사드립니다.
- 결정론적 RNG에서만 의미가 있는 함수가 호출될 때
random.Random구현으로 투명하게 전환하는 접근법을 제안해 주신 Serhiy Storchaka, Terry Reedy, Petr Viktorin 및 python-ideas 스레드의 모든 분께 감사드립니다. - 비밀번호 재설정 토큰을 생성하는 데 PHP의 난수 제너레이터를 사용할 때 발생할 수 있는 실제 공격에 관한 참고 자료를 제공해 주신 Nathaniel Smith에게 감사드립니다.
- 사용자 공간 CSPRNG를 도입하면 시스템 RNG를 직접 사용하는 것에 비해 얻는 이점은 부족한데도 복잡성만 증가할 것이라고 지적한 네트워크 보안 전문가들과의 추가 논의를 추진해 주신 Donald Stufft에게 감사드립니다.
- 현재 Python 생태계의 보안 피로에 대해 설득력 있게 주장해 주신 Paul Moore에게 감사드립니다.
참고 자료
Copyright
This document has been placed in the public domain.