"랜덤" 버튼을 누르면 프로그램이 순식간에 숫자를 제공합니다. 단순해 보이는 이 과정 뒤에는 컴퓨터 과학의 근본적인 역설이 숨어 있습니다: 컴퓨터는 결정론적 기계입니다——같은 입력은 항상 같은 출력을 만들어냅니다. 그렇다면 어떻게 "진정한 무작위"가 될 수 있을까요? 행운의 룰렛에서 비밀번호 생성기까지, 도구마다 "난수"에 대한 요구 사항이 완전히 다르며, 이 차이가 경우에 따라 여러분의 계정 보안을 결정할 수 있습니다.
1. 컴퓨터는 왜 "진정한 무작위"가 될 수 없는가?
전통적인 컴퓨터는 결정론적 튜링 기계의 구현입니다: 모든 작업은 명확한 규칙에 따라 실행되며, 같은 초기 상태는 반드시 같은 후속 상태를 만들어냅니다. 즉, 컴퓨터 내부에서만 실행되는 알고리즘의 출력은 초기값("시드", seed)에 의해 완전히 결정됩니다.
시드를 알면 이후의 모든 "난수"를 예측할 수 있습니다. 이런 알고리즘이 생성하는 숫자 수열은 통계적으로는 무작위처럼 보이지만(각종 무작위성 테스트를 통과), 본질적으로는 예측 가능한, 주기적인 의사난수 수열입니다. 이것이 컴퓨터 과학에서 이들을 "의사난수 생성기"(PRNG: Pseudo-Random Number Generator)라고 부르는 이유입니다.
의사난수는 나쁜 것이 아닙니다——시뮬레이션, 게임, 통계 샘플링 등 대부분의 응용 시나리오에서 PRNG는 충분합니다. 문제는 "예측 불가능성"이 필요한 시나리오(암호학)에서만 발생합니다——이때 의사난수는 심각한 보안 취약점이 됩니다.
2. 의사난수 생성기(PRNG)의 원리
2.1 선형 합동 생성기(LCG)
가장 오래되고 단순한 PRNG 중 하나입니다. 공식은 다음과 같습니다:
X(n+1) = (a × X(n) + c) mod m
a(승수), c(증분), m(계수)는 미리 설정된 상수이고, X(0)이 시드입니다. 호출할 때마다 이전 값으로부터 다음 값을 계산합니다.
LCG는 매우 빠르고 메모리 사용량이 적지만 품질이 제한적입니다: 주기가 유한하고 다차원 공간에서의 분포가 규칙적인 초평면 구조를 보여 통계 테스트로 쉽게 감지됩니다. 초기 ANSI C의 rand() 함수는 LCG였지만, 지금은 단독으로 거의 사용되지 않습니다.
2.2 메르센 트위스터(Mersenne Twister)
1998년 마츠모토 마코토와 니시무라 다쿠지가 제안한 MT19937은 현재 가장 널리 사용되는 PRNG 중 하나입니다. 주기는 2^19937 − 1에 달하며, 거의 모든 통계적 무작위성 테스트를 통과합니다. Python의 random 모듈, Ruby, PHP의 rand() 등 많은 언어의 기본 난수 함수가 메르센 트위스터를 사용합니다.
그러나 메르센 트위스터에는 심각한 약점이 있습니다: 내부 상태는 624개의 32비트 정수입니다. 공격자가 연속된 624개의 출력값을 관찰하면 내부 상태를 완전히 재구성하여 이후의 모든 출력을 예측할 수 있습니다. 이로 인해 보안이 필요한 모든 응용에는 완전히 적합하지 않습니다.
2.3 현대적 고품질 PRNG
최근 xorshift128+, PCG(Permuted Congruential Generator), xoshiro256** 등 더 현대적인 PRNG가 등장했습니다. 속도, 통계 품질, 주기 모두에서 우수한 성능을 발휘하지만, 암호학 응용에는 여전히 부적합합니다.
| 알고리즘 | 주기 | 속도 | 통계 품질 | 암호학적으로 안전? |
|---|---|---|---|---|
| LCG(선형 합동법) | 중간 | 매우 빠름 | 보통 | 아니오 |
| 메르센 트위스터 | 2^19937−1 | 빠름 | 우수 | 아니오 |
| PCG / xoshiro | 2^128 이상 | 매우 빠름 | 우수 | 아니오 |
| ChaCha20(CSPRNG) | 해당 없음 | 빠름 | 우수 | 예 |
| 하드웨어 난수(TRNG) | 해당 없음 | 느림 | 진난수 | 예 |
3. 진난수: 물리 세계에서 예측 불가능성 얻기
결정론적 컴퓨터의 한계를 극복하려면 외부 세계의 엔트로피(Entropy)——진정으로 예측 불가능한 물리적 사건——를 도입해야 합니다. 운영 체제와 하드웨어는 다양한 방법으로 이 엔트로피를 수집합니다:
- 사용자 행동: 마우스 이동 궤적, 키보드 타이밍——인간의 동작은 매우 예측하기 어려움
- 하드웨어 인터럽트 타이밍: 디스크 읽기/쓰기, 네트워크 패킷 도착의 정확한 시점——전자기 잡음 등 물리적 요인의 영향을 받음
- CPU 타이밍 지터: 온도, 전압 변동 등 물리적 요인으로 인한 프로세서의 미세한 시간 차이
- 전용 하드웨어 난수 생성기(HRNG/TRNG): 열 잡음, 방사성 붕괴, 광자 거동 등의 양자 효과를 이용해 진정한 무작위 비트를 직접 생성하는 하드웨어
Linux의 /dev/urandom과 /dev/random은 운영 체제가 관리하는 엔트로피 풀 인터페이스로, 위의 다양한 하드웨어 이벤트를 수집하여 암호학 응용에 고품질 난수 시드를 제공합니다. Intel의 RDRAND 명령은 CPU 수준에서 직접 하드웨어 난수를 제공합니다.
/dev/random vs /dev/urandom: 무슨 차이인가?/dev/random은 엔트로피 풀이 부족할 때 더 많은 엔트로피가 쌓일 때까지 "블록"됩니다. /dev/urandom은 엔트로피가 부족해도 CSPRNG로 출력을 계속 확장하며 블록하지 않습니다. 현대 시스템에서 초기화된 /dev/urandom은 거의 모든 응용에 충분히 안전합니다.
4. 암호학적 안전 난수 생성기(CSPRNG)
CSPRNG는 PRNG의 속도와 엔트로피 소스의 예측 불가능성을 결합한 것입니다. 두 가지 핵심 요건을 충족해야 합니다:
- 다음 비트 예측 불가능성: 이미 출력된 모든 비트를 알더라도 다음 비트를 50% 이상의 확률로 예측할 수 없습니다.
- 상태 침해 후 역방향 보안성: 공격자가 어느 시점에 CSPRNG의 내부 상태를 획득하더라도, 과거에 생성된 출력을 역추산할 수 없습니다.
대표적인 CSPRNG: ChaCha20 기반(Linux kernel 4.8+의 /dev/urandom 구현 기반), Fortuna(Windows BCryptGenRandom의 알고리즘 기반 중 하나), NIST 표준화 Hash_DRBG / HMAC_DRBG.
5. 왜 Math.random()을 절대 비밀번호에 사용하면 안 되는가?
이는 가장 흔하고 가장 위험한 오해 중 하나입니다. 거의 모든 언어에 내장된 "기본 난수 함수"——JavaScript의 Math.random(), Python의 random.random(), PHP의 rand()——는 CSPRNG가 아닌 PRNG를 사용합니다.
JavaScript의 Math.random()을 예로 들면, 연구자들이 보여준 것은: 연속된 3개의 Math.random() 부동소수점 출력만 관찰하면 내부 상태를 완전히 재구성하여 이후의 모든 "난수"를 예측할 수 있다는 것입니다.
올바른 방법은 암호학용으로 설계된 API를 사용하는 것입니다:
- JavaScript/Web:
window.crypto.getRandomValues() - Node.js:
crypto.randomBytes() - Python:
secrets모듈 (Python 3.6+) - PHP:
random_bytes(),random_int() - Java:
java.security.SecureRandom
많은 사람들이 학습할 때
random.seed(42)를 써서 결과를 재현 가능하게 만듭니다——이는 교육과 테스트에서 좋은 실천입니다. 하지만 이것은 PRNG의 본질을 직접 보여주기도 합니다: 시드가 고정되면 모든 출력이 결정적입니다. 시드가 추측 가능하다면(예: 현재 타임스탬프), 공격자는 유한한 시드 값을 시도하는 것만으로 여러분의 모든 "난수" 출력을 복원할 수 있습니다.
6. 용도별 난수 요구 사항
6.1 게임과 엔터테인먼트: PRNG로 충분
주사위 시뮬레이션, 행운의 룰렛, 제비뽑기 도구, 카드 게임——이런 응용의 핵심 요구 사항은 균일한 분포, 빠른 속도, 사용자가 "공정하다"고 느끼는 것입니다. 메르센 트위스터나 현대적인 PRNG로 완전히 충족됩니다.
6.2 통계 시뮬레이션: PRNG + 시드 제어
몬테카를로 시뮬레이션, 통계 샘플링, 머신러닝 데이터 분할——고품질 통계 분포가 필요하면서 검증을 위한 재현성도 필요합니다. 고정 시드 PRNG가 표준 접근법입니다.
6.3 암호학과 보안: 반드시 CSPRNG 사용
비밀번호 생성, 암호화 키, Token, Session ID, CSRF Token, 일회용 비밀번호(OTP)——이 시나리오들의 보안성은 전적으로 난수의 예측 불가능성에 의존합니다. 어떤 PRNG도 허용되지 않으며, 운영 체제가 제공하는 CSPRNG 인터페이스를 사용해야 합니다.
| 응용 시나리오 | 권장 유형 | 예시 API | 핵심 요구 사항 |
|---|---|---|---|
| 게임/룰렛/주사위 | PRNG | Math.random() | 균일 분포, 속도 |
| 통계 시뮬레이션 | PRNG(고정 시드) | random.seed(n) | 재현성, 통계 품질 |
| 비밀번호/키/Token | CSPRNG | crypto.getRandomValues() | 예측 불가능성 |
| 과학 실험 | TRNG | RDRAND / random.org | 진정한 엔트로피 소스 |
7. 결론
컴퓨터가 난수를 생성하는 메커니즘은, 표면적인 "버튼 누르면 숫자 나온다" 뒤에 풍부한 기술적 층이 숨어 있습니다:
- PRNG(의사난수): 결정론적 알고리즘, 빠르고 통계 품질 좋지만 본질적으로 예측 가능——게임, 시뮬레이션에 적합, 암호학에는 부적합
- TRNG(진난수): 물리 세계에서 엔트로피 수집, 진정으로 예측 불가능——속도 느림, 시드 제공이나 특수 용도에 사용
- CSPRNG(암호학적 안전 난수): PRNG의 속도와 엔트로피 소스의 예측 불가능성 결합——비밀번호, 키, Token에 유일하게 올바른 선택
다음에 비밀번호 생성기를 사용하거나 "랜덤" 기능을 볼 때, 한 가지 질문을 해보세요: 이 시나리오에서 필요한 것은 "무작위처럼 보이는 것"인가, 아니면 "진정으로 예측 불가능한 것"인가? 그 차이가 여러분의 데이터 보안의 경계선일 수 있습니다.