Random 클래스는 의사 난수(Pseudo-Reandom Number)를 생성한다.
💡 의사 난수란?
진정한 난수가 아니라 특정 시드(Seed)를 기반으로 한 수학적 알고리즘을 통해 생성된 예측 가능한 일련의 값이다. 시드는Random객체가 생성될 때 초기 시드가 주어지거나(명시적으로 지정하지 않으면 보통 현재 시간) 내부적으로 설정된다. 이 시드가 동일하면Random객체는 항상 동일한 순서의 난수를 생성한다.
따라서 Random 클래스는 주로 일반적인 난수 생성이 필요한 경우에 사용된다. 예를 들면, 게임 내 주사위 굴리기, 간단한 테스트 데이터 생성, 비암호화 목적으로 무작위 요소 추가 등 보안이 중요하지 않은 상황에 적합하다.
SecureRandom에 비해 훨씬 빠르다는 장점을 가지고 있다.
예시 코드
import java.util.Random;
Random random = new Random();
int randomNumber = random.nextInt(100); // 0부터 99까지의 난수 생성
위와 같이 값을 넣어주지 않으면 시스템 시간(일반적으로 System.nanoTime())을 통해 시드가 정해지고, 이를 통해 랜덤 값을 규칙적으로 만들어준다.
import java.util.Random;
public class RandomTest {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
randomTest();
}
}
public static void randomTest() {
Random ran = new Random(10);
for(int i = 0; i < 10; i++) {
System.out.printf("%.3f ", ran.nextDouble());
}
ran = null;
System.out.println();
}
}

10회 모두 같은 패턴의 값이 나오게 된다. 만약 비밀번호와 같이 보안에서 사용되는 난수를 위해 Random을 사용하게 된다면, 보안적으로 큰 문제가 될 것이다.
SecureRandom 클래스는 암호학적으로 강력한 난수(Cryptographically Strong Random Number)를 생성한다.
Random 클래스에는 48 비트만 있는 반면, SecureRandom은 최대 128 비트를 포함할 수 있다. 따라서 SecureRandom에서 반복할 가능성은 더 적다.
이는 예측 가능성이 매우 낮아 보안이 중요한 응용 프로그램에 적합하다. SecureRandom은 시스템의 실제 무작위성 소스(Entropy Source), 예를 들면 하드웨어 이벤트, 운영 체제 정보 등을 활용하여 시드를 만든다. 따라서 외부에서 예측하거나 재현하기 매우 어렵다.
보안이 필수적인 상황, 특히 암호화 관련 작업에 사용해야 한다. 예를 들면 비밀 키 생성, 패스워드 해싱을 위한 솔트(Salt) 생성, 세션 토큰이나 Nonce(일회용 숫자) 생성이 있다.
실제 무작위성 소스를 사용하고 더 복잡한 알고리즘을 거치기 때문에 Random에 비해 느리다.
예시 코드
1️⃣ 기본 생성자 사용
import java.security.SecureRandom;
// 기본 알고리즘을 사용하여 SecureRandom 객체 생성 (운영체제에 따라 다를 수 있음)
SecureRandom secureRandom = new SecureRandom();
byte[] randomBytes = new byte[16];
secureRandom.nextBytes(randomBytes); // 16바이트의 암호학적으로 강력한 난수(바이트 배열) 생성
위와 같이 기본 생성자를 사용하는 방법이 가장 간단한 방법이다. JVM에 등록된 보안 제공자 중에서 기본값으로 설정된 알고리즘을 사용하여 SecureRandom 객체를 생성한다. OS에 따라 기본 알고리즘이 다를 수 있지만, 대부분의 경우 암호학적으로 충분히 강력하다.
2️⃣ 특정 알고리즘 요청
SecureRandom secureRandom = SecureRandom.getInstance("NativePRNG");
특정 알고리즘 이름을 명시적으로 지정하여 해당 알고리즘을 사용하는 SecureRandom 객체를 요청한다. 개발자가 특정 알고리즘(예: SHA1PRNG, NativePRNG)을 사용해야 할 때 유용하다. 만약 시스템에 요청한 알고리즘이 없다면 NoSuchAlgorithmException이 발생한다.
3️⃣ 가장 강력한 알고리즘 요청
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class SecureRandomTest {
public static void main(String[] ags) throws NoSuchAlgorithmException {
for (int i = 0; i < 10; i++) {
secureRandomTest();
}
}
public static void secureRandomTest() throws NoSuchAlgorithmException {
SecureRandom ran = SecureRandom.getInstanceStrong();
for(int i = 0; i < 10; i++) {
System.out.printf("%.3f ", ran.nextDouble());
}
ran = null;
System.out.println();
}
}
Java가 시스템에서 사용 가능한 가장 강력한 암호학적 난수 생성 알고리즘을 찾아 해당 인스턴스를 반환하도록 요청한다. 보안이 가장 중요한 상황에서 사용을 권장한다. 가장 강력한 엔트로피 소스를 찾아야 하므로 객체를 처음 생성할 때 성능이 느려질 수 있다. (특히 엔트로피가 부족한 환경에서)

SecureRandom을 통해 10회 난수를 생성하면 같은 패턴을 가지지 않음을 볼 수 있다.
따라서 보안이 전혀 중요하지 않다면 빠른 성능의 Random을 사용해도 괜찮다. 만약 암호화, 사용자 인증, 보안 토큰 등 보안이 조금이라도 중요한 모든 상황에서는 반드시 SecureRandom을 사용해야 한다.
SecureRandom.getInstanceStrong()에서는 디폴트로 /dev/random 디렉토리에서 시드 값을 얻어온다. 이는 자바 버그로 인해 엄청난 퍼포먼스 저하를 발생할 수 있다.
new SecureRandom()을 사용하게 되면, /dev/random을 호출하게 되어 이 퍼포먼스 저하를 방지할 수 있다.
혹은 SecureRandom.getInstanceStrong()를 계속 사용할 때는 다른 방식으로 경로를 수정해줄 수 있다.
📌 출처