설명
보안 난수 생성기를 시스템 시간이나 고정 값만으로 초기화하면 공격자가 시드를 추측하여 토큰의 탐색 범위를 줄일 수 있습니다. SecureRandom의 실제 동작은 공급자에 따라 다르며, 모든 구현이 같은 시드로 동일한 출력을 내는 것은 아닙니다.
Java 문서에 따르면 PRNG 방식의 SecureRandom에서 첫 nextBytes() 전에 setSeed()를 호출하면 자동 시딩이 수행되지 않을 수 있으므로 호출자가 충분한 엔트로피를 제공해야 합니다. 반면 이미 확보한 시드에 대한 setSeed()는 기존 값을 대체하지 않고 보충합니다. 시간 값을 추가했다는 이유만으로 이미 충분한 엔트로피가 사라진다고 단정하면 안 됩니다.
잠재적 영향
- 인증/세션 탈취: 예측 가능한 토큰이나 세션 ID를 추측해 다른 사용자의 세션을 가로챌 수 있습니다.
- 권한 오용/가짜 요청: CSRF 토큰, API 키 유사 토큰을 예측해 권한 있는 요청을 위조할 수 있습니다.
- 일회용 코드(OTP) 예측: 비밀번호 재설정 코드나 2차 인증 코드가 추측 가능해집니다.
- 무차별 대입 범위 축소: 시간 기반 seed로 탐색 공간이 크게 줄어 공격 성공 가능성이 높아집니다.
해결 방법
- 일반적인 토큰 생성에는
new SecureRandom()을 사용하고 공급자의 엔트로피 기반 초기화에 맡기세요. 첫 사용 전에 시간이나 상수만으로 시딩하지 마세요. - 명시적 시드가 꼭 필요하다면 충분히 예측 불가능한 값을 사용하세요.
generateSeed(n)은 다른 난수 생성기를 초기화할 시드 바이트를 얻는 API이며, 보통은 별도 시딩 없이 사용할 수 있습니다. - 보안 토큰·세션 식별자에는
java.util.Random을 사용하지 마세요. 충분한 난수 길이, 만료 및 용도에 맞는 재사용 방지 정책을 적용하세요. Base64는 표현 형식일 뿐 엔트로피를 추가하지 않습니다. - 암호 키 생성에는
KeyGenerator같은 전용 API를 사용하세요. 비밀번호에서 키를 유도해야 한다면 적절한 비밀번호 기반 키 유도 함수를 사용하세요.
예시
변경 전
java
import java.security.SecureRandom;
import java.util.Base64;
public class InsecureTokenService {
// 취약: 시간 기반 seed로 초기화
public String issueToken() {
SecureRandom rng = new SecureRandom();
rng.setSeed(System.currentTimeMillis()); // 예측 가능
byte[] buf = new byte[16]; // 128비트
rng.nextBytes(buf);
return Base64.getUrlEncoder().withoutPadding().encodeToString(buf);
}
}
변경 후
java
import java.security.SecureRandom;
import java.util.Base64;
public class SecureTokenService {
private static final SecureRandom RNG = new SecureRandom(); // 공급자의 엔트로피로 자동 초기화
public String issueToken() {
byte[] buf = new byte[32]; // 256비트로 예측 난이도 향상
RNG.nextBytes(buf);
return Base64.getUrlEncoder().withoutPadding().encodeToString(buf);
}
// (선택) seed를 꼭 주어야 하는 특수 케이스라면 안전한 랜덤 seed 사용
public String issueTokenWithExplicitSeed() {
SecureRandom sr = new SecureRandom();
byte[] strongSeed = sr.generateSeed(32); // 예측 불가능한 seed
SecureRandom seeded = new SecureRandom(strongSeed);
byte[] buf = new byte[32];
seeded.nextBytes(buf);
return Base64.getUrlEncoder().withoutPadding().encodeToString(buf);
}
}
변경 전에는 첫 난수 생성 전에 추측 가능한 시간으로 시딩합니다. 변경 후의 기본 경로는 공급자의 자동 초기화에 맡깁니다. 선택적 두 번째 메서드는 충분한 난수 시드를 별도로 얻는 경우를 보여 주며, 일반적인 토큰 생성에 필수인 절차는 아닙니다.