“무작위예요”는 사실이 아니라 주장이에요. 선택이 공정하려면 두 가지가 참이어야 해요. 숫자의 원천을 예측할 수 없어야 하고, 모든 항목의 확률이 같아야 해요.
둘 다 구현할 수 있지만 둘 다 흔히 틀리고, 서로 독립적으로 실패해요. 훌륭한 무작위 원천에 편향된 매핑을 가진 도구도 있고, 완벽하게 균등한 매핑에 예측 가능한 숫자를 넣은 도구도 있어요.
무작위성은 어디서 오는가
진짜 무작위성은 이전 상태로 결정되지 않는 물리적 과정에서 나와요:
- 방사성 붕괴 — 원자가 붕괴하는 순간은 예측할 수 없어요
- 열잡음 — 저항기에서 생기는 무작위 전압 변동
- 대기 잡음 — 번개 활동에서 나오는 배경 잡음
- 양자 측정 — 진정으로 결정되지 않는 결과
컴퓨터는 이것들을 직접 관찰할 수 없어요. 대신 하드웨어 생성기, 즉 /dev/urandom과 그에 준하는 것을 표본으로 삼아요. 이것 자체는 물리적 과정에서 시드를 받아요. 이게 진짜 난수 생성기예요.
훨씬 흔한 경우는 의사 난수 생성기예요. 무작위처럼 보이는 수열을 만드는 결정론적 알고리즘이죠.
next = (previous × multiplier + increment) mod modulus
결정적인 성질은 미묘하지 않아요. 알고리즘과 시드를 알면 그 수열이 앞으로 만들 모든 값을 계산할 수 있어요. 근사가 아니라 정확히요. 그래서 대부분의 웹사이트가 쓰는 Math.random()은 중요한 일에 적합하지 않아요. 출력이 내부 상태로 결정되고, 관찰한 출력에서 그 상태를 복원하면 앞으로의 모든 결과를 예측할 수 있으니까요.
문제를 가르는 중간 지대
현대 플랫폼은 호출마다 생 하드웨어 엔트로피를 쓰지 않아요. 암호학적으로 안전한 PRNG를 써요. 결정론적이지만 하드웨어 엔트로피로 시드를 받고, 출력을 관찰해도 상태가 드러나지 않도록 설계됐어요.
| 하드웨어 TRNG | CSPRNG | 일반 PRNG | |
|---|---|---|---|
| 물리적 엔트로피 기반 | 예 | 예 | 아니요 |
| 상태가 주어지면 결정론적 | 아니요 | 예 | 예 |
| 출력이 상태를 드러냄 | 아니요 | 아니요 | 예 |
| 상품 추첨에 적합 | 예 | 예 | 아니요 |
CSPRNG는 여러분이 쓰는 모든 소프트웨어가 결정론적인 것과 같은 의미에서 결정론적이에요. 이것이 받아들여지는 이유는 이미 내보낸 값으로는 상태를 복원할 수 없기 때문이에요.
브라우저는 이를 crypto.getRandomValues()로 제공해요. 여기 추첨이 쓰는 원천이에요.
균등성은 별개의 문제
좋은 원천을 갖는 것만으로는 충분하지 않아요. 난수에서 선택지로 가는 매핑이 여전히 편향될 수 있고, 이게 대부분의 도구가 저지르는 실수예요.

순진한 방식, 즉 0에서 1 사이의 무작위 실수를 범위에 맞춰 늘리는 방식은 낮은 쪽에 편향을 만들어요. 이름이 40개일 때 일부 무작위 값은 마지막 구간을 넘어서고, 이걸 버리지 않고 잘라내면 첫 번째 이름이 마지막 이름보다 조금 더 자주 나와요.
편향은 아무도 눈치채지 못할 만큼 작아요. 그래서 대부분의 구현에서 살아남아요.
올바른 방식은 기각 샘플링이에요. 뽑은 값이 불완전한 마지막 구간에 들어가면 버리고 다시 뽑아요. 그러면 분포가 정확히 균등해져서 모든 항목이 1/N의 확률을 가져요. 이름 40개 추첨에서도 마찬가지예요. 실수를 늘리는 구현은 첫 번째 이름에 아주 약간 더 높은 확률을 줘요.
같은 기법이 이 사이트의 짧은 ID 생성기에도 나와요. 허용률은 252/256이고 그로 인한 편향은 약 천만 분의 이 정도예요. 무시해도 될 만큼 작고, 부주의한 구현이라면 눈치채지 못했을 만큼 작아요. 그래서 측정할 가치가 있어요.
도구의 잘못이 아닌 세 가지 인간의 실수
도박꾼의 오류. 앞면이 다섯 번 연속 나온 뒤에는 뒷면이 나올 때가 됐다고 느껴요. 하지만 각 시행은 독립적이에요. 동전은 불공평했던 기억이 없고, 앞면 다섯 번 뒤에도 각 면의 확률은 여전히 정확히 1/6이에요.
군집 착각. 무작위 데이터의 연속은 패턴처럼 느껴져요. 찾고 있으니까요. 같은 결과가 세 번 연속 나오는 일은 직감보다 훨씬 자주 일어나요. 그리고 진짜 무작위 데이터는 짧은 구간에서 울퉁불퉁해 보여요. 완벽하게 고른 수열은 진짜라기보다 조작됐을 가능성이 커요.
생일 역설. 23명이 모이면 두 사람이 같은 생일일 가능성이 높아요. 이건 심리가 아니라 산수지만, 사람들이 무작위성을 왜 그렇게 못 추정하는지 보여 줘요. 개별 확률은 아주 작고, 그것들이 곱해지니까요.
공통점은 한 번의 관찰로는 어느 방향으로도 아무것도 증명할 수 없다는 거예요. 이상해 보인 추첨은 깔끔해 보인 추첨만큼이나 정보가 없어요.
실제로 가장 중요한 두 가지 실패
메커니즘을 따라가 봤으니, 선택이 문제될 때 실제로 무엇이 잘못되는지 짚어 볼 만해요. 둘 다 원천에 관한 게 아니에요.
운영자가 결과를 바꿨다. 다시 돌리기, 손으로 고치기, 추첨 도중 불참자 건너뛰기. 완벽한 생성기라도 결과를 손대면 손댄 결과예요. 지켜본 사람은 누구나 알 수 있어요.
명단이 공개된 명단이 아니었다. 이름을 더하거나 조용히 빼면 알고리즘과 무관하게 선택이 달라져요.
둘 다 과정의 실패이고, 녹화된 추첨이 둘 다 막아 줘요. 그래서 참가자 명단을 화면에 담아 녹화하는 것이 어떤 기술적 성질보다 가치 있어요. 두 실패를 모두 막아 주니까요.
이 정도 엄격함이 필요한 곳
점심 메뉴를 고르는 데는 이런 게 아무 상관 없어요. 어떤 생성기든 괜찮은 점심을 골라 줘요.
이게 중요해지는 건 걸린 게 있거나, 평판이 걸렸거나, 법적 문제가 있을 때예요:
- 상품 추첨과 대회. 검증 가능한 결과가 핵심이에요
- 직원이나 업무 배정. 사람들이 과정을 들여다봐요
- 과학 및 임상 무작위 배정. 이 분야의 표준은 문서화되고 시드를 밝히며 재현 가능한 무작위성이에요
- 규제와 감사 상황. 문서화된 무작위성은 선호가 아니라 요구 사항인 경우가 많아요
이 모든 곳의 요구 사항은 같은 네 가지이고 짧아요. 명단은 고정되어 공개되고, 가중치가 있으면 공개되고, 원천은 예측할 수 없고, 결과는 명단이 보이는 상태로 기록돼요.
직접 확인하는 방법
브라우저 탭에서 CSPRNG를 감사할 수는 없지만, 메커니즘을 따져 볼 수는 있고 그걸로 충분해요.
원천이 무엇인지 물어보세요. 답이 “무작위 함수”라면 예측 가능한 그 함수인지 물어보세요. crypto.getRandomValues()는 구체적이고 확인 가능한 답이에요.
균등성을 어떻게 얻는지 물어보세요. 답이 기각 샘플링 언급 없이 실수를 범위에 맞춰 늘리는 거라면 그 매핑은 편향됐을 가능성이 커요. 작지만 측정 가능하게요.
운영자가 무엇을 바꿀 수 있었는지 물어보세요. 아무것도 못 바꿨다면 결과는 방어 가능해요. 이름을 건너뛰거나 추첨을 다시 돌릴 수 있었다면 원천과 무관하게 방어할 수 없어요.
공정성 페이지에서 이 사이트의 구현을 더 자세히 다뤄요. 문제 제기가 예상되는 결정에 도구를 고른다면 한 번 읽어 볼 만해요.