난수 생성 관련 클래스 정리
- Random : 빠르지만 예측 가능한 의사난수
- SecureRandom : 보안 목적에 적합한 암호학적 난수(CSPRNG)
💡 Random 클래스는 48비트 seed 기반 LCG(Linear Congruential Generator)를 사용하여 의사난수를 생성
의사난수_유사난수 생성기(PRNG, Pseudo Random Number Generator)
LCG 알고리즘 사용 → 예측 가능
48비트, seed 값 기반 → seed가 같으면 항상 같은 난수 순서
성능이 빠르고 가벼움
비보안 환경에서 적절
의사난수: 난수처럼 보이게 하기 위해 어떠한 알고리즘을 사용한 규칙적인 난수
랜덤성이 중요하지만 보안적 의미는 없는 경우
게임 내 랜덤 요소 (주사위, 몬스터 스폰)
확률 시뮬레이션
테스트 데이터 생성
UI 애니메이션 등 보안과 무관한 난수
X(n+1) = (a * X(n) + c) mod m
/*
필요조건
m > 0
0 < a < m
0 < c < m
0<= X0 < m (X의 초기값)
자바 내부에서 사용하는 상수
m = 2^48
a = 25214903917
c = 11
/*
@Test
@DisplayName("Random함수 테스트")
void randomTest() {
int i = 0;
while (i < 3) {
Random random = new Random(10);
for (int j = 0; j < 5; j++) {
System.out.println(random.nextDouble());
i++;
}
}
}
//임의의 값 넣지않으면 System.nanoTime()으로 시드생성
/*
0.730967787376657
0.8314409887870612
0.3971743421847056
출력 같은 값 반복
*/
public class RandomStringUtils {
private static final Random RANDOM = new Random();
}
java.util.Random 인스턴스는 암호학적으로 안전하지 않습니다.
민감한 보안 응용 프로그램에서는 SecureRandom을 사용하십시오.
💡 SecureRandom은 운영체제 엔트로피(Entropy)를 기반으로 한 암호학적 난수 생성기(CSPRNG_Cryptographically Secure Pseudo-Random Number Generator).
OS가 수집한 무작위 데이터(Entropy Source_무작위성 요소)기반으로 난수 생성
암호학적 PRNG 알고리즘(CSPRNG) 적용
seed 내부적으로 안전하게 초기화
암호학 표준(FIPS 140-2 등)에 부합
보안 강도(Security Strength)는 128~256비트 수준
OS 및 사용되는 PRNG 알고리즘에 따라 달라짐
Random에 비해 상대적으로 느림
Java 7+ 부터 스레드 안전
보안적으로 중요한 난수 필요 시
세션 토큰 생성
인증/인가 코드
OAuth state 값
비밀번호 salt 생성(비밀번호 해싱 시 필요한 난수)
암호화 키 생성
Nonce(일회용 숫자) 값 생성
import java.security.SecureRandom;
// 기본 알고리즘을 사용하여 SecureRandom 객체 생성 (운영체제에 따라 다를 수 있음)
SecureRandom secureRandom = new SecureRandom();
byte[] randomBytes = new byte[16]; //128비트 보안강도
secureRandom.nextBytes(randomBytes); // 16바이트의 암호학적으로 강력한 난수(바이트 배열) 생성
// 특정 알고리즘 이름을 명시적으로 지정하여 해당 알고리즘을 사용하는 SecureRandom 객체를 요청
SecureRandom secureRandom = SecureRandom.getInstance("NativePRNG");
// Java가 시스템에서 사용 가능한 가장 강력한 암호학적 난수 생성 알고리즘을 찾아 해당 인스턴스를 반환하도록 요청
// 가장 강력한 엔트로피 소스를 찾아야 하므로 객체를 처음 생성할 때 성능이 느려질 수 있음. (특히 엔트로피가 부족한 환경에서)
import java.security.SecureRandom;
// 반복실행 시 매번 새로운 SecureRandom 값 생성.
public class SecureRandomTest {
public static void main(String[] ags) {
for (int i = 0; i < 10; i++) {
secureRandomTest();
}
}
public static void secureRandomTest() {
SecureRandom ran = new SecureRandom();
byte[] randomBytes = new byte[16];
// 랜덤 비트 생성 후 배열 채움
ran.nextBytes(randomBytes);
for (byte b : randomBytes) {
// 난수 16진수 출력
System.out.printf("%02x ", b);
}
System.out.println();
}
}
/*
3f a9 12 7c 8d e4 5b 91 2a c0 6e f1 88 4d 73 b2
a4 0e 9b 5c 17 6a f8 2d 93 b1 4e 7f 0c d5 68 aa
9d 3c f0 61 2b 8e 14 7a c9 55 e2 0f b6 4a d8 91
6b 1f 8c e3 49 a0 d2 77 5e 9a 34 f5 0b 61 c8 2d
f1 84 2a 9c 7d 03 b5 e8 4f 6a d0 91 5c 3e 27 ab
매번 실행할 때마다 다르게 나옴
*/
Linux의 /dev/random 은 엔트로피가 충분하지 않으면 blocking 발생.
따라서 대부분의 시스템은 /dev/urandom 사용을 권장.
/dev/random
엔트로피가 충분할 때만 반환
엔트로피 부족 시 블록 → 성능 저하 / 응답 지연
/dev/urandom
엔트로피 부족해도 non-blocking, 계속 출력
대부분의 서버/웹 애플리케이션에서는 사실상 충분히 안전 + 성능 안정적
지금은 new SecureRandom()을 사용하게 되면, /dev/urandom 를 자동으로 호출.