[Java]Random vs SecureRandom

이권민·2025년 12월 14일

목차

  1. Random
  2. SecureRandom

난수 생성 관련 클래스 정리

- Random : 빠르지만 예측 가능한 의사난수

- SecureRandom : 보안 목적에 적합한 암호학적 난수(CSPRNG)

Random

💡 Random 클래스는 48비트 seed 기반 LCG(Linear Congruential Generator)를 사용하여 의사난수를 생성

특징

  • 의사난수_유사난수 생성기(PRNG, Pseudo Random Number Generator)

  • LCG 알고리즘 사용 → 예측 가능

  • 48비트, seed 값 기반 → seed가 같으면 항상 같은 난수 순서

    • 0 ~ (2^48 - 1) 의 내부 상태값 가짐
  • 성능이 빠르고 가벼움

  • 비보안 환경에서 적절

의사난수: 난수처럼 보이게 하기 위해 어떠한 알고리즘을 사용한 규칙적인 난수

사용 예시

랜덤성이 중요하지만 보안적 의미는 없는 경우

  • 게임 내 랜덤 요소 (주사위, 몬스터 스폰)

  • 확률 시뮬레이션

  • 테스트 데이터 생성

  • UI 애니메이션 등 보안과 무관한 난수

LCG 알고리즘

  • 선형합동생성기(1차식, 나머지연산)
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

/*

  • 빠르고 간단하지만 출력된 난수 몇 개만 보면 seed를 거꾸로 계산 가능
    @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
	출력 같은 값 반복
    */
  • 공격자가 시드 생성시간을 알고있거나, seed값을 알고있으면 위험

Apache RandomStringUtils

  • 문자/숫자 랜덤 문자열을 간단한 메서드 호출로 생성해주는 클래스
public class RandomStringUtils {
    private static final Random RANDOM = new Random();
}
  • RandomStringUtils도 내부적으로 Random을 사용, 보안적인 토큰/세션ID/인증코드 생성에는 사용 불가

공식문서 경고

java.util.Random 인스턴스는 암호학적으로 안전하지 않습니다.
민감한 보안 응용 프로그램에서는 SecureRandom을 사용하십시오.

SecureRandom

💡 SecureRandom은 운영체제 엔트로피(Entropy)를 기반으로 한 암호학적 난수 생성기(CSPRNG_Cryptographically Secure Pseudo-Random Number Generator).

특징

  • OS가 수집한 무작위 데이터(Entropy Source_무작위성 요소)기반으로 난수 생성

    • Linux: /dev/random, /dev/urandom
    • window: BCryptGenRandom(), Non Blocking
    • Mac: /dev/random, /dev/urandom, arc4random() Non Blocking
    • 키보드 입력 간격, 마우스 움직임, 디스크 I/O 타이밍 등을 수집.seed 자동 관리
  • 암호학적 PRNG 알고리즘(CSPRNG) 적용

    • SHA1PRNG(SHA1 사용). 예전 버전에서 사용
      • 난수를 만들 때마다, 내부 상태값에 64비트 카운터를 붙여서 SHA-1 해시를 계산. 160비트
    • NativePRNG, DRBG, BSD random subsystem, Windows CryptoAPI 등
      • 운영체제 또는 플랫폼이 제공하는 안전한 난수 생성 메커니즘 사용
  • seed 내부적으로 안전하게 초기화

  • 암호학 표준(FIPS 140-2 등)에 부합

  • 보안 강도(Security Strength)는 128~256비트 수준

    • OS 및 사용되는 PRNG 알고리즘에 따라 달라짐

    • Random에 비해 상대적으로 느림

  • Java 7+ 부터 스레드 안전

    • 내부적으로 동기화(synchronization) 되어 있기 때문에
      여러 스레드가 동시에 사용해도 안전
    • 매 요청마다 객체 생성할 필요 x

사용 예시

보안적으로 중요한 난수 필요 시

  • 세션 토큰 생성

  • 인증/인가 코드

  • 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
매번 실행할 때마다 다르게 나옴
*/

SecureRandom 블로킹 이슈 & 해결법

Linux의 /dev/random 은 엔트로피가 충분하지 않으면 blocking 발생.
따라서 대부분의 시스템은 /dev/urandom 사용을 권장.

/dev/random

  • 엔트로피가 충분할 때만 반환

  • 엔트로피 부족 시 블록 → 성능 저하 / 응답 지연

/dev/urandom

  • 엔트로피 부족해도 non-blocking, 계속 출력

  • 대부분의 서버/웹 애플리케이션에서는 사실상 충분히 안전 + 성능 안정적

  • 지금은 new SecureRandom()을 사용하게 되면, /dev/urandom 를 자동으로 호출.

참조

profile
이것저것이것 개발자

0개의 댓글