왜 당신의 Random은 예측가능한가: Go의 Random 번호
Min-jun Kim
Dev Intern · Leapcell

소개
난수는 암호화부터 시뮬레이션 및 게임에 이르기까지 컴퓨터 사용에 광범위하게 사용됩니다. 난수는 진정한 난수와 유사 난수의 두 가지 유형으로 분류할 수 있습니다.
진정한 난수
진정한 난수는 동전 던지기, 주사위 굴리기, 팽이 돌리기, 전자 노이즈, 핵분열 등과 같은 물리적 현상을 사용하여 생성됩니다. 이러한 방법을 기반으로 하는 난수 생성기를 물리적 난수 생성기라고 합니다.
유사 난수
유사 난수란 무작위로 보이지만 그렇지 않은 프로세스를 말합니다. 예를 들어 유사 난수는 결정론적 알고리즘을 사용하여 무작위로 보이는 시퀀스를 생성하여 계산됩니다.
유사 난수를 계산하는 데 사용되는 함수를 랜덤 함수라고 하고, 랜덤 함수를 사용하여 난수를 생성하는 알고리즘을 난수 생성기라고 합니다. 일부 랜덤 함수는 주기적입니다. 비주기적 함수가 일반적으로 더 좋지만 주기적 랜덤 함수가 더 빠른 경우가 많습니다. 일부 주기적 함수에는 조정 가능한 계수가 있어 매우 큰 주기를 달성할 수 있으므로 비주기적 함수만큼 효과적입니다.
Golang에서 유사 난수 구현을 살펴보겠습니다.
초기 Go 버전의 난수
// Go 1.18 import ( "fmt" "math/rand" ) func main() { fmt.Println(rand.Intn(100)) }
이 코드를 실행하면 실행 횟수에 관계없이 결과는 동일합니다. 왜 그럴까요? 당황하지 마시고 구현을 살펴보겠습니다. 소스 코드를 검사하면 math/rand
라이브러리가 시드 값이 1인 기본 소스를 사용하여 난수를 생성하는 것을 알 수 있습니다. 앞에서 언급했듯이 유사 난수는 결정론적 알고리즘에 의해 생성되므로 실제로 무작위가 아닙니다. NewSource
의 시드가 변경되지 않으면 프로그램을 다시 시작할 때마다 난수 시퀀스가 항상 동일합니다.
소스 코드: rand.go (Go 1.18)
// ... /* * Top-level convenience functions */ var globalRand = New(&lockedSource{src: NewSource(1).(*rngSource)}) // ...
각 실행 시 시드가 다른지 확인하려면 타임스탬프를 시드로 사용할 수 있습니다. 이렇게 하면 프로그램을 실행할 때마다 랜덤 시퀀스가 변경됩니다.
// Go 1.18 package main import ( "fmt" "math/rand" "time" ) func main() { rand.Seed(time.Now().UnixNano()) fmt.Println(rand.Intn(100)) // 프로그램을 실행할 때마다 결과가 달라집니다. }
다행히 Google은 Go 1.20에서 이 문제를 해결했습니다. Go 1.20부터 전역 난수 생성기의 랜덤 시드는 1로 고정되지 않고 자동으로 초기화됩니다. 환경 변수 GODEBUG=randautoseed=0
이 설정되지 않는 한 프로그램을 실행할 때마다 랜덤 시퀀스가 변경됩니다.
암호화 방식으로 안전한 난수
crypto/rand
라이브러리는 보다 안전한 난수 생성기를 구현합니다. 코드 주석에 따르면 Linux 기반 플랫폼에서는 getrandom(2)
시스템 호출의 우선 순위를 지정합니다. 사용할 수 없는 경우 /dev/urandom
으로 대체됩니다.
getrandom
은 /dev/urandom
문자 장치 파일을 읽어 고품질 난수를 얻기 위해 캡슐화하는 시스템 호출입니다. /dev/urandom
은 /dev/random
을 시드 참조로 사용하고, /dev/random
은 하드웨어 생성 노이즈에서 값을 파생하므로 매우 높은 무작위성 품질을 갖습니다.
package rand import "io" // Reader는 암호화 방식으로 안전한 난수 생성기의 전역 공유 인스턴스입니다. // // Linux, FreeBSD, Dragonfly 및 Solaris에서 Reader는 사용 가능한 경우 getrandom(2)를 사용하고, 그렇지 않으면 /dev/urandom을 사용합니다. // OpenBSD 및 macOS에서 Reader는 getentropy(2)를 사용합니다. // 다른 Unix 계열 시스템에서 Reader는 /dev/urandom에서 읽습니다. // Windows 시스템에서 Reader는 RtlGenRandom API를 사용합니다. // Wasm에서 Reader는 Web Crypto API를 사용합니다. var Reader io.Reader // Read는 io.ReadFull을 사용하여 Reader.Read를 호출하는 도우미 함수입니다. // 반환 시 n == len(b)는 err == nil인 경우에만 해당합니다. func Read(b []byte) (n int, err error) { return io.ReadFull(Reader, b) }
요약
- 프로그램에서 생성된 난수는 유사 난수입니다.
- 1.20 이전의 Go 버전에서
math/rand
패키지는 기본 공유 소스에 대해 고정 시드(seed=1
)를 사용하여 모든 프로그램 실행에 대해 동일한 난수 시퀀스를 생성했습니다. Go 1.20부터 시드는 자동으로 초기화되어 명시적으로 재정의되지 않는 한 시퀀스가 변경됩니다. crypto/rand
패키지는 암호화 방식으로 안전한 난수 생성기를 제공합니다.crypto/rand
는math/rand
보다 안전하지만 성능 비용이 따릅니다. 사용 사례에 따라 적절한 라이브러리를 선택하십시오. 암호화 목적으로는 항상crypto/rand
패키지를 사용하십시오.- JavaScript에서
crypto.getRandomValues
는 암호화 방식으로 안전한 난수 생성기이므로Math.random
보다 안전합니다.
Leapcell은 Go 프로젝트를 클라우드에 배포하기 위한 최고의 선택입니다.
Leapcell은 웹 호스팅, 비동기 작업 및 Redis를 위한 차세대 서버리스 플랫폼입니다.
다국어 지원
- Node.js, Python, Go 또는 Rust로 개발하십시오.
무제한 프로젝트를 무료로 배포
- 사용량에 대해서만 지불하고 요청이나 요금이 없습니다.
탁월한 비용 효율성
- 유휴 요금 없이 사용량에 따라 지불합니다.
- 예: 25달러로 평균 응답 시간 60ms에서 694만 건의 요청을 지원합니다.
간소화된 개발자 경험
- 간편한 설정을 위한 직관적인 UI.
- 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합.
- 실행 가능한 통찰력을 위한 실시간 지표 및 로깅.
손쉬운 확장성 및 고성능
- 고도의 동시성을 쉽게 처리할 수 있도록 자동 확장됩니다.
- 운영 오버헤드가 전혀 없으므로 구축에만 집중하면 됩니다.
설명서에서 자세히 알아보세요!
X(으)로 팔로우하세요: @LeapcellHQ