[Java] Random보단 SecureRandom 를 사용하자.
🙄서론
Random을 사용하다가 소나큐브에서 Critical 버그가 발생했다.
그 후 알아본 내용을 정리해본다.
😉본론
- 에러 메세지
(번역은 구글번역을 통해 되었기 때문에 오역이 있을 수 있습니다..)
"Random" objects should be reused
"랜덤" 객체는 재사용되어야 한다.
Random 값이 필요할 때마다 새 개체를 만드는 것은 비효율적이며
JDK에 따라 임의의 숫자가 생성되지 않을 수 있습니다.
효율성과 임의성을 높이려면 단일 Random을 만든 다음 저장하고 다시 사용하세요.
Random()생성자는 별개의 값마다에 씨앗을 설정하려고 시도합니다.
그러나 시드가 무작위로 또는 균일하게 분포 될 것이라는 보장은 없습니다.
일부 JDK는 현재 시간을 시드로 사용하므로 생성 된 숫자가 전혀 무작위가 아닙니다.
이 규칙은 Random메서드가 호출되고 로컬 랜덤 변수에 할당 될 때마다 새 항목 이 생성 되는 경우를 찾습니다 .
// 비 준수 코드 예
public void doSomethingCommon() {
Random rand = new Random(); // Noncompliant; new instance created with each invocation
int rValue = rand.nextInt();
//...
// 준수 코드 예
private Random rand = SecureRandom.getInstanceStrong(); // SecureRandom is preferred to Random
public void doSomethingCommon() {
int rValue = this.rand.nextInt();
//...
🤨 왜 Random을 사용하지 말란거지?
기본적으로 우리가 Random함수를 사용하는 목적은 난수를 생성하기 위해서이다.
하지만, 우리가 생각했던 것 처럼 정말 무작위로 랜덤으로 생성되는 것이 아니라
'의사난수'가 생성되는 것이다.
- 의사난수가 뭐지?
'의사난수'란 난수처럼 보이게 하기 위해 어떠한 알고리즘을 사용한 규칙적인 난수를 생성하는 것을 의미한다.
결론적으로 정말로 찐인 난수가 아닌 규칙적으로 만들어진 난수처럼 보이는 값이란 것.
- 그럼 Random은?
Random을 생성하기 위해서는 시드(seed)라는 씨앗(?)이라고 불리는 '기준이 되는 값'을 셋팅해준다.
Random rand = new Random(); // seed = System.nanoTime()
이처럼 값을 넣어주지 않으면 시스템 시간을 통해 시드가 정해지고,
이를 통해 랜덤 값을 규칙적으로 만들어준다.
이는 시드값이 동일하다면 매번 같은 값을 보여줄 것이다.
예제
@Test
@DisplayName("랜덤값 테스트1")
void randomTest1() {
for(int i = 0; i < 5; i++) {
randomTest2();
}
}
@Test
@DisplayName("랜덤값 테스트2")
void randomTest2() {
Random ran = new Random(10);
for(int i = 0; i < 10; i++) {
System.out.printf("%.3f ", ran.nextDouble());
}
ran = null;
System.out.println();
}
이처럼 전부 같은 숫자가 나오게 된다.
이는 우리가 원하는 랜덤 값이 아니다..
만약 비밀번호 같이 보안에서 사용되는 난수를 만들기 위해 Random을 사용하게 된다면
이는 적합하지 않을 것이다.
java.util.Random 인스턴스는 암호화로 안전하지 않습니다.
대신 SecureRandom보안에 민감한 응용 프로그램에서 사용할 암호화 보안 의사 난수 생성기를
사용하는 것을 고려하십시오.
자바7 API 설명 발췌
- 그럼 SecureRandom은 다른가?
간단하게 차이점을 비교를 해보는게 가장 이해하기 쉬울 것 같다.
🎈Random VS SecureRandom
1. 크기
- Random 클래스에는 48 비트만 있는 반면, SecureRandom은 최대 128 비트를 포함 할 수 있습니다.
따라서 SecureRandom에서 반복 할 가능성은 더 적습니다.
2. 시드 생성
- Random은 시스템 시간을 시드로 사용하거나 시드를 생성합니다.
따라서 공격자가 시드가 생성 된 시간을 알고 있으면 쉽게 재현 할 수 있습니다.
그러나 SecureRandom은 OS에서 임의 데이터를 가져 와서
(키 입력 사이의 간격이 될 수 있습니다. 대부분의 OS는 이러한 데이터를 수집하여 파일에 저장합니다.
-Linux / solaris의 경우 / dev / random 및 / dev / urandom)
이를 시드로 사용합니다.
3.코드 깨기
- Random의 경우 2 ^ 48 번만 시도하면 되며 오늘날의 고급 CPU를 사용하면 실제 시간에 깨뜨릴 수 있습니다.
그러나 SecureRandom을 위해서는 2 ^ 128 번의 시도가 필요하며,
오늘날의 고급 기계에서도 중단 되려면 몇 년이 걸릴 것입니다.
4. 생성 기능
- 표준 Oracle JDK 7 구현은 Linear Congruential Generator를 사용하여
java.util.Random에서 임의의 값을 생성합니다.
Secure Random은 SHA1을 사용하여 의사 난수를 생성하는 SHA1PRNG 알고리즘을 구현합니다.
알고리즘은 진정한 난수 (엔트로피 소스 사용)에 대해 SHA-1 해시를 계산 한 다음
각 작업에서 1 씩 증가하는 64 비트 카운터와 연결합니다.
5. 보안
- 결과적으로 java.util.Random이 중요한 응용 프로그램이나 중요한 데이터를 보호하기 위해
랜덤 클래스를 사용해서는 안됩니다.
발췌주소
간단하게 설명해서 Random의 업그레이드 버전이라고 생각하면 쉬운 것 같다.
정말 다른 값을 호출하는지 예제를 봐야겠다.
예제
@Test
@DisplayName("랜덤값 테스트1")
void randomTest1() throws NoSuchAlgorithmException {
for(int i = 0; i < 5; i++) {
randomTest2();
}
}
@Test
@DisplayName("랜덤값 테스트2")
void randomTest2() throws NoSuchAlgorithmException {
SecureRandom ran = SecureRandom.getInstanceStrong();
for(int i = 0; i < 10; i++) {
System.out.printf("%.3f ", ran.nextDouble());
}
ran = null;
System.out.println();
}
정말 Random과 다르게 불러올 때마다 전부 다른 값을 보여주었다.
간단하게 랜덤 값을 생성하는 것이 아닌, 정말 암호화가 필요한 작업을 진행할 때에는Random이 아닌 SecureRandom을 사용해야 하는 것이 맞는 것 같다.
- SecureRandom 문제는 없을까?
SecureRandom.getInstanceStrong() 에서는 디폴트로 /dev/random 디렉토리에서 시드 값을 얻어온다.
이는 자바 버그로 인해 엄청난 퍼포먼스 저하를 발생할 수 있다.
new SecureRandom()을 사용하게 되면, /dev/urandom 를 호출하게 되어 이 퍼포먼스 저하를 방지할 수 있다.
혹은, SecureRandom.getInstanceStrong() 를 계속 사용할 때에는 다른 방식으로 경로를 수정해줄 수 있다.
Linux
# vi /etc/profile.d/java.sh 또는 vi /etc/bashrc
JAVA_OPTS="$JAVA_OPTS -Djava.security.egd=file:/dev/./urandom"
Window
내컴퓨터 > 고급 시스템 설정 > 고급 > 환경 변수(N).. > 시스템 변수(S) > 새로 만들기(W)..
변수 이름(N) : JAVA_OPTS
변수 값(V) : %JAVA_OPTS% -Djava.security.egd=file:/dev/./urandom
공통
java.security 파일 추가 및 수정
jdk 8 이하 : $JAVA_HOME/jre/lib/security/java.security
jdk 9 이상 : $JAVA_HOME/conf/security/java.security
securerandom.source=file:/dev/./urandom
😎결론
그냥 간단하게 소나큐브에 뜬 에러 호기심으로 인해 많이 타고타고 올라가버렸다..
덕분에 난수생성이 어떻게 되는지 알 수 있게 되어서 나쁘지 않았다.
결론적으로
소나큐브에서 발생한 에러는
Random을 매번 생성하지 말고, SecureRandom 을 사용하여 한 번만 호출해서 사용할 수 있다.
Referensce.
첫 번째 사이트 강추.
https://st-lab.tistory.com/76
https://github.com/ksundong/TIL/blob/master/Java/Difference-with-SecureRandom-and-Random.md
https://www.geeksforgeeks.org/random-vs-secure-random-numbers-java/
https://stackoverflow.com/questions/295628/securerandom-init-once-or-every-time-it-is-needed
https://github.com/pravusid/TIL/blob/master/Security/secure-random.md
https://blog.eomsh.com/37
https://developmentlee.tistory.com/31
https://lng1982.tistory.com/261
https://gampol.tistory.com/entry/Tomcat-%EA%B5%AC%EB%8F%99-%EC%8B%9C-devurandom-%EB%B8%94%EB%A1%9C%ED%82%B9-%EC%9D%B4%EC%8A%88%EC%A7%80%EC%97%B0%EC%8B%9C%EC%9E%91-%EB%AC%B8%EC%A0%9C