왜 이런 의문을 갖게 되었냐 하면.. 프로젝트 패키지를 설치하던 도중 여러 warning message 중에 내 눈을 사로 잡는 게 있었다.
npm WARN deprecated uuid@3.4.0: Please upgrade to version 7 or higher.
Older versions may use Math.random() in certain circumstances, which is known to be problematic.
See <https://v8.dev/blog/math-random> for details.
Math.random()
은 0 이상 1 미만의 부동 소수점 난수를 생성하는 함수이다.
7 버전 이하의 uuid는 Math.random()
을 사용하고 있고, 이는 문제가 생길 수 있다는 내용이었다.
Math.random()이 사용된 부분을 뒤져보니, uuid/lib/rng-browser.js 였다. crypto 가 없는 환경에서는 Math.random() 을 사용하는 내용이다.
var getRandomValues = (typeof(crypto) != 'undefined' && crypto.getRandomValues && crypto.getRandomValues.bind(crypto)) ||
(typeof(msCrypto) != 'undefined' && typeof window.msCrypto.getRandomValues == 'function' && msCrypto.getRandomValues.bind(msCrypto));
if (getRandomValues) {
var rnds8 = new Uint8Array(16);
module.exports = function whatwgRNG() {
getRandomValues(rnds8);
return rnds8;
};
} else {
// Math.random()-based (RNG)
var rnds = new Array(16);
module.exports = function mathRNG() {
for (var i = 0, r; i < 16; i++) {
if ((i & 0x03) === 0) r = Math.random() * 0x100000000;
rnds[i] = r >>> ((i & 0x03) << 3) & 0xff;
}
return rnds;
};
}
난수를 직접 생성할 때에는 Math.random()
을 사용하고 있었던 것 같은데 왜 위험한지? 에 대해서 궁금해졌고, Math.random()
이 난수를 생성하는 방식에 대해서 알아보게 되었다.
Math.random()은 실제로 난수를 생성하는 것이 아니라, 의사 난수(pseudo-random number)라는 것을 생성한다고 한다.
의사 난수(pseudo-random number)는 컴퓨터 프로그램이 수학적인 알고리즘을 사용하여 생성한 난수다. 진정한 무작위성이 아닌, 시작값인 시드(seed)와 알고리즘에 의해 계산된 일련의 값이다.
실제로 컴퓨터는 무작위한 숫자를 생성하는 데 한계가 있으며, 물리적인 엔트로피 소스를 사용하여 완전한 무작위성을 달성하는 것은 어렵다. (예를 들어서 주사위를 던지는 행위 등)
대신, 의사 난수 생성기는 초기값인 시드(seed)를 사용하여 무작위처럼 보이는 수열을 생성한다. 같은 시드로 시작하면 항상 같은 수열을 생성하며, 다른 시드를 사용하면 다른 수열을 생성한다.
시드 기반 난수로 암호화 키를 생성하는 경우 키 값의 일부를 알고 있다면 나머지 키 값도 유추하는게 가능해질 수 있다. 패턴을 통해서 쉽게 추측할 수 없거나, 시간이 지나도 반복되지 않는 진짜 난수가 필요하다는 것이다.
그리고 이러한 난수 생성 알고리즘을 PRNG(pseudo-random number generator) 라고도 한다.
PRNG은 진짜 무작위한 값을 생성할 수 없고, 특정 공식을 사용해 무작위로 보일 수 있지만, 결국 난수를 계속해서 생성하면 반복되어 어떠한 패턴을 나타내게 된다.
난수 생성 알고리즘은 이 패턴이 나오기까지의 기간 즉, 몇 번의 반복 끝에 패턴을 유추할 수 있는지에 따라 품질이 달라지며, 이 수가 크면 클 수록 크래킹하기 어려워 지고, 더 랜덤인 함수인 척을 잘 할 수 있다.
그렇다면 JavaScript가 사용하는 PRNG는 무엇일까? -> 자바스크립트가 결정하는 것은 아니고 브라우저에 따라 다르지만, 현재 주요 브라우저는 xorshift128+ 라는 알고리즘을 사용하고 있다고 한다. 하지만 각 브라우저별로 해당 알고리즘을 어떻게 사용하고 있는지는 알 수 없다.
Math.random()은 다른 언어와 달리 내부 시드값을 정할 순 없지만, 내부 시드값과 Math.random 함수에서 사용하는 알고리즘이 어떻게 사용되는지 밝혀진다면 치명적일 것이다.
보안 목적으로 필요한 모든 임의의 값 (공격자에게 공격 받을 수 있는 가능성이 있는 모든 값)는 암호학적으로 안전한 의사 난수 생성기 (Cryptographically Secure Pseudo-Random Number Generator, CSPRNG)를 사용하여 생성해야 한다.
Cryptographically Secure = 암호학적으로 안전한
Math.random()
does not provide cryptographically secure random numbers. Do not use them for anything related to security. Use the Web Crypto API instead, and more precisely the [window.crypto.getRandomValues()](<https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues>)
method.var array = new Uint32Array(1);
window.crypto.getRandomValues(array);
console.log("Random number: " + array[0]);
!https://velog.velcdn.com/images/jiwonyyy/post/2bcdaf60-6066-4ee4-af72-14937d453fa5/image.png
병 안에 색소가 들은 반투명한 액체와 왁스가 담겨 있으며 전구를 켜면 전구의 열을 받은 왁스가 녹아 병의 위쪽으로 상승했다가 냉각되어 다시 내려오고를 반복한다. 이것이 마치 용암의 움직임과 비슷하기 때문에 라바 램프라는 이름이 붙었다.
CloudFlare는 100개의 라바 램프를 회사 사무실 입구에 전시했는데 라바 램프의 움직임, 지나가는 사람의 모습, 일광 변화와 같은 다양한 요인에 따라 변경되는 픽셀 디스플레이를 고속카메라로 촬영해서 임의의 숫자 난수를 만들어서 쓴다고 한다.