golang - math/rand와 crypto/rand

출처

Go 난수 생성에 관한 표준 패키지는 math/rand와 crypto/rand의 2가지가 있다.
각각 math/rand은 약한 난수에 crypto/rand 강한 난수인데 강한 난수 쪽이 반드시 좋다는 것은 아니고, 특성 및 성능의 차이에서 유스 케이스가 나누어진다.

math/rand

https://golang.org/pkg/math/rand/

일반적으로 난수를 사용하는 경우 math/rand를 이용한다.
대부분의 경우 time.Now().UnixNano()를 난수의 시드 값으로 하여, 실행할 때마다 다른 수를 얻을 수 있다.

package main

import (
    "fmt"
    "time"
    "math/rand"
)

//0에서 99의 랜덤한 수치를 표시한다
func main() {
    rand.Seed(time.Now().UnixNano())
    fmt.Println(rand.Intn(100))
}

다양한 형태의 난수를 반환하는 함수가 준비되어 있으며, 또한 고속으로 안정된 난수를 얻을 수 있다.

참고로 최상위 함수는 goroutine에 안전하지만 NewSource()로 생성한 소스는 goroutine으로부터 안전하지 않다. 그러나 최상위 함수가 gotoutine 안전한 것은 내부에 잠금을 가지고 있을 뿐이므로 난수를 얻을 때처럼 잠금을 걸면 groutine 안전하다.

math/rand의 의사 난수 생성기에 몇 가지 수치를 기초로 하여 호출 할 때마다 조합을 바꾸는 것으로 적당히 분산된 난수를 생성한다.(참고: https://golang.org/src/math/rand/rng.go ) 기반이 되는 수치는 일정하므로, 시드 값에 대응하는 값을 반환하는 난수를 재현 할 수 있다. 이러한 무작위 수열이 되지만, 예측 가능한 난수를 약한 난수라고한다.

게임에서의 주사위 등은 약한 난수로 충분하지만, 암호화 및 세션 ID 생성에 사용하는 경우 취약점이 될 수 있다. 예를 들어, 세션 ID를 math/rand 난수를 바탕으로 문자열을 생성 할 때 time.Now().UnixNano()이 초기 값인 경우 세션 ID를 발행한 시간대를 알 수 있다면 무차별 세션 ID를 식별 할 수 있을지도 모른다.

math/rand는 충분히 분산된 수열을 빠르게 얻을 수 있지만, 예측 가능하기 때문에 암호화론적으로는 반드시 안전하지는 않는다.

crypto/rand

https://golang.org/pkg/crypto/rand/

crypto/rand 패키지는 암호학적으로 안전한 난수 생성기를 구현하고 있다.
암호 학적으로 안전한 의사 난수 생성기는 CSPRNG(cryptographically secure pseudo random number generator)라고도 한다.

math/rand와 달리 io.Reader 인터페이스를 이용하여 임의의 바이트 열을 추출 할 수 있다.

package main

import (
    "crypto/rand"
    "fmt"
    "math/big"
)

//0에서 99의 랜덤한 수치를 표시한다
func main() {
    n, err := rand.Int(rand.Reader, big.NewInt(100))
    if err != nil {
        panic(err)
    }
    fmt.Println(n)
}

rand.Reader 내용은 환경에 따라 달라진다. linux의 경우 /dev/urandom를 이용하고, Windows는 CryptoAPI를 사용한다.

crypto/rand에서 이용되는 것은 모두 CSPRNG 이다. linux에서 장치 드라이버 등에서 얻은 환경 소음을 시드로 난수를 생성한다. 따라서 프로그램에서 초기 값을 주지 않는다.

그러나 /dev/urandom은 일반적으로 무거운 처리이므로 math/rand에 비해 환경에 따라 성능이 크게 떨어질 수 있다.


이 글은 2019-11-08에 작성되었습니다.