2P by neo 1달전 | favorite | 댓글 1개
  • Go 1.22에서는 기존의 math/rand 패키지와 새로 도입된 math/rand/v2 패키지에서 암호학적으로 안전한 난수 생성기를 사용하도록 변경됨. 이를 통해 더 나은 무작위성을 제공하고, 개발자가 실수로 math/rand를 crypto/rand 대신 사용할 때 발생할 수 있는 피해를 크게 줄일 수 있게 됨.

통계적 무작위성과 암호학적 무작위성의 차이

  • 통계적 무작위성은 시뮬레이션, 샘플링, 수치 분석, 비암호화 무작위 알고리즘, 무작위 테스트, 입력 셔플링, 무작위 지수 백오프 등에 적합함.
  • 매우 기본적이고 계산하기 쉬운 수학 공식으로도 이런 용도에는 충분히 잘 동작함. 하지만 사용된 알고리즘을 아는 관찰자는 일정 수의 값을 보고 나면 향후 시퀀스를 예측할 수 있음.
  • 암호학적 무작위성은 어떤 수의 이전에 생성된 값을 관찰했더라도 실제로는 완전히 예측 불가능해야 함.
  • 안전한 암호화 프로토콜, 비밀 키, 현대 상거래, 온라인 프라이버시 등이 암호학적 무작위성에 크게 의존하고 있음.

Go 1의 math/rand 생성기

  • Linear-feedback shift register(LFSR) 방식을 사용함.
  • 내부 상태가 607개 uint64로 구성된 벡터로 완전히 노출되는 문제가 있음.
  • 생성기에서 607개 값을 읽으면 모든 상태가 노출되어 이후 값을 예측할 수 있음.

math/rand/v2의 PCG 생성기

  • Melissa O'Neill의 PCG 알고리즘 사용. 128비트 LCG에 후처리를 적용한 것임.
  • 전체 상태가 128비트 숫자 하나로, 업데이트는 128비트 곱셈과 덧셈으로 이루어짐.
  • Go에서는 O'Neill의 제안에 따라, XOR 기반 대신 곱셈 기반의 scramble 함수를 사용해 비트를 더 적극적으로 섞음.
  • Go 1 생성기보다 계산량이 많지만, 상태 저장에 필요한 메모리가 훨씬 적고, 초기 상태 값에 덜 민감하며, 다른 생성기가 통과하지 못하는 통계적 테스트도 통과함.
  • 하지만 PCG도 여전히 예측 불가능하지는 않음.

암호학적 무작위성

  • 궁극적으로는 운영체제가 물리적 장치의 잡음으로부터 실제 무작위성을 수집해야 함.
  • 충분한 무작위성(256비트 이상)을 수집하면, 암호학적 해시나 암호화 알고리즘으로 늘려서 임의의 길이의 난수열을 만들 수 있음.
  • Go의 crypto/rand 패키지는 이런 운영체제 인터페이스의 차이를 추상화하고 rand.Read라는 동일한 인터페이스를 제공함.

ChaCha8Rand 생성기

  • DJB의 ChaCha 스트림 암호를 변형해서 만든 새로운 생성기.
  • 8라운드 버전인 ChaCha8을 사용함. ChaCha20보다 2.5배 빠르면서도 안전함.
  • 32바이트 시드를 ChaCha8 키로 사용. 매 16블록마다 생성된 블록의 뒤 32바이트를 다음 16블록의 키로 사용해 전방향 비밀성 제공.
  • math/rand/v2의 rand.Float64, rand.N 등은 항상 이 생성기 사용.
  • math/rand도 이 생성기 사용. 단, rand.Seed가 호출되면 Go 1 생성기를 사용.
  • 런타임도 새 맵의 해시 시드를 고를 때 ChaCha8Rand 사용.

보안 실수 해결

  • Go 1.22는 math/rand를 강화함으로써 코드 변경 없이도 프로그램을 더 안전하게 만듦.
  • 예를 들어 math/rand의 Read를 잘못 사용해서 키 생성 등에 쓴 경우, Go 1.20에서는 심각한 보안 문제지만 Go 1.22에서는 그냥 실수에 불과함.
  • UUID 생성이나 프론트엔드 서버의 부하 분산 등 "암호"로 보이지 않는 용도에서도 ChaCha8Rand를 쓰면 Go 1 생성기보다 훨씬 강건해짐.

성능

  • ChaCha8Rand는 Go 1 생성기나 PCG와 비슷한 수준의 성능을 보임.
  • 32비트 코드에서는 128비트 곱셈이 필요한 PCG보다 ChaCha8Rand가 더 빠름.
  • 64비트 나눗셈을 피하는 math/rand/v2의 알고리즘 덕분에 N(1000) 연산에서는 Go 1 생성기보다 ChaCha8Rand나 PCG가 더 빠른 경우도 있음.
  • 전반적으로 ChaCha8Rand는 Go 1 생성기보다 느리지만 2배 이상 느린 경우는 없고, 일반적인 서버에서는 차이가 3ns를 넘지 않음.

GN⁺의 의견

  • Go 1.22의 ChaCha8Rand 적용은 보안성을 크게 높이면서도 성능 저하는 최소화한, 언어 차원의 모범적인 개선 사례라 할 만함. 개발자들이 자주 저지르는 실수를 언어 차원에서 원천 봉쇄한 것이 인상적임.
  • 본문에서도 언급했듯이 이런 실수는 Go에만 국한된 것이 아니라 다른 언어에서도 흔히 발견됨. 개발자의 실수에 시스템 보안이 좌우돼서는 안되므로, 다른 언어들도 Go처럼 "수학적" 난수 생성에도 암호학적으로 강력한 의사난수 생성기를 쓰는 방향으로 나아가야 할 것임.
  • 다만 ChaCha8Rand는 crypto_box나 xchacha20poly1305 같은 암호화 프리미티브에 쓰기에는 부적절함. 이런 용도라면 여전히 crypto/rand를 직접 사용해야 함.
  • Go 런타임이 맵 해시 시드 선택에도 ChaCha8Rand를 쓰도록 바꾼 것은 조금 의외였음. 해시 시드로 암호학적 난수가 반드시 필요한지는 분명치 않지만, 골치 아픈 공격 가능성을 원천 차단하려는 개발팀의 보안 의식이 돋보임.
  • 언어 차원의 기본 제공 패키지인 math/rand의 품질이 높아진 만큼, 앞으로는 응용에서 math/rand를 직접 쓸 일도 많아질 것 같음. 그간 math/rand의 예측 가능성 때문에 따로 난수 생성 라이브러리를 썼던 프로젝트라면 이번 변경의 혜택을 볼 수 있을 것임.
Hacker News 의견

요약해보면 다음과 같음:

  • Go 1.20에서 math/rand 패키지의 Read 함수가 deprecated 되면서, 이를 crypto/rand 대신 잘못 사용한 사례들이 발견됨. 이는 보안에 취약한 결정론적 난수 생성기를 사용하게 되는 실수로 이어짐.
  • Go의 기본 난수 생성기를 CSPRNG(암호학적으로 안전한 의사 난수 생성기)로 바꾸는 것이 보안을 위해 더 좋은 접근임. 명시적으로 PRNG가 필요한 경우에만 선택하도록 하는 것이 바람직함.
  • gosec이나 golangci-lint 같은 정적 분석 도구는 math/rand 사용에 대해 경고를 줌.
  • math/rand/v2 패키지는 ChaCha8 암호를 사용하고 시스템 엔트로피로 시드되어 "안전한" 인상을 주지만, 여전히 보안에 민감한 작업에는 부적합함. 이는 crypto/rand를 사용해야 함.
  • Go 1의 math/rand는 정확히는 additive lagged Fibonacci generator로 볼 수 있음.
  • 새 math/rand는 최악의 경우에도 기존 비보안 난수 생성기의 절반 정도의 속도를 보이며, 대부분의 벤치마크에서는 거의 차이가 없었음. Go는 표준 라이브러리에서 안전성과 성능 사이의 적절한 균형을 잡고 있음.
  • Java의 java.util.Random 같은 실수를 방지하는 개발자 친화적인 접근으로 평가됨.
  • ChaCha8을 쓰는 이유와 AES-GCM 등 하드웨어 가속을 지원하는 블록 암호를 쓰지 않는 이유에 대한 궁금증이 제기됨.