<일기>
어제는 공가라서 TIL 안 썼다.
최종 면접을 다녀왔다.
면접 시간은 2시간인데, 왕복 5시간이었다.. 11시에 출발해서 7시에 도착..
환승없이 지하철로 쭉 2시간을 가는데 집올 때는 퇴근시간이랑 겹쳐서 서서 갔다 ㅠㅠ
면접은.. 잘 모르겠다.. 합격 확률은 50% 미만으로 예상을 해 본다.
구글링해보니 대충 어떤 부분에서 질문이 나온다길래 그 부분을 중점적으로 준비를 했는데 1도 안 나왔다. ㅋㅋ
아니, 절대 예상을 못 한 질문만 나왔다 ㅋㅋㅋㅋ
그래도 나름 필살기라고 생각한 것을 어필하려고 어떻게든 방향을 틀면
면접관분들은 이악물고 다시 유턴을 해서 다른 질문을 하셨다..
아무래도 미리 정해둔 질문 리스트가 있고, 면접자가 뭐라고 하든 꼬리 질문따윈 안 하고 이 리스트의 질문을 다 해야해 라는 느낌.
면접자도, 면접관도, 회사도 모두 따듯했다.
질문만 빼고...
결과는 3주 뒤에 나온다고 하니, 담담히 내 할 일을 하면서 잊고 있어야 겠다,,
</일기>
문제
강의를 똑같이 따라 코드를 치는 중, 똑같은 평문으로, 같은 암호화 방식으로 비밀번호 암호화를 했더니 다른 문자열이 나왔다.
????????????
그렇다는 건 비밀번호를 정확히 입력해도 다른 비밀번호라고 에러를 뱉지 않나??
@Configuration
public class PasswordConfig { // bean 에는 passwordConfig 로 등록
@Bean
public PasswordEncoder passwordEncoder() { // 빈에는 passwordEncoder 로 저장
return new BCryptPasswordEncoder();
}
}
우선 스프링 시큐리티를 이용해서 PasswordEncoder 를 빈으로 등록해뒀다.
PasswordEncoder 를 우선 까보았다.
// (1)
public BCryptPasswordEncoder() {
this(-1);
}
// (2)
public BCryptPasswordEncoder(int strength) {
this(strength, null);
}
// (3)
public BCryptPasswordEncoder(int strength, SecureRandom random) {
this(BCryptVersion.$2A, strength, random);
}
// (4)
public BCryptPasswordEncoder(BCryptVersion version, int strength, SecureRandom random) {
if (strength != -1 && (strength < BCrypt.MIN_LOG_ROUNDS || strength > BCrypt.MAX_LOG_ROUNDS)) {
throw new IllegalArgumentException("Bad strength");
}
this.version = version;
this.strength = (strength == -1) ? 10 : strength;
this.random = random;
}
이후로 기본 생성자에서 연속으로 호출되는 생성자도 기술해뒀다.
private Pattern BCRYPT_PATTERN = Pattern.compile("\\A\\$2(a|y|b)?\\$(\\d\\d)\\$[./0-9A-Za-z]{53}");
private final Log logger = LogFactory.getLog(getClass());
private final int strength;
private final BCryptVersion version;
private final SecureRandom random;
public enum BCryptVersion {
$2A("$2a"),
$2Y("$2y"),
$2B("$2b");
private final String version;
BCryptVersion(String version) {
this.version = version;
}
public String getVersion() {
return this.version;
}
}
또한 BCryptPasswordEncoder 의 필드는 패턴, 로거, 암호화 강도, 버전, (보안적으로 강력한) 랜덤 숫자 생성기가 있다. (SecureRandom javadoc 설명에 이렇게 적혀있다.)
@SpringBootTest
public class PasswordEncoderTest {
@Autowired
PasswordEncoder passwordEncoder;
@Test
@DisplayName("수동 등록한 passwordEncoder를 주입 받아와 문자열 암호화")
void test1() {
String password = "Robbie's password";
// 암호화
String encodePassword = passwordEncoder.encode(password);
System.out.println("encodePassword = " + encodePassword);
String inputPassword = "Robbie";
// 복호화를 통해 암호화된 비밀번호와 비교
boolean matches = passwordEncoder.matches(inputPassword, encodePassword);
System.out.println("matches = " + matches); // 암호화할 때 사용된 값과 다른 문자열과 비교했기 때문에 false
}
}
그다음, 이번에 사용된 테스트 코드다.
@Override
public String encode(CharSequence rawPassword) {
if (rawPassword == null) {
throw new IllegalArgumentException("rawPassword cannot be null");
}
String salt = getSalt();
return BCrypt.hashpw(rawPassword.toString(), salt);
}
우선 비밀번호를 암호화 하는 encode 메서드 이다.
문제였던, 강의와 나의 인코딩된 비밀번호가 다르니 이 메서드의 salt에서 문제가 생긴다는 것을 알 수 있다.
private String getSalt() {
if (this.random != null) {
return BCrypt.gensalt(this.version.getVersion(), this.strength, this.random);
}
return BCrypt.gensalt(this.version.getVersion(), this.strength);
}
우리는 위 BCryptPasswordEncoder 에서 random 을 null으로 설정했으므로, if문을 거치지 않은 그 밑의 로직이 실행된다.
public static String gensalt(String prefix, int log_rounds) throws IllegalArgumentException {
return gensalt(prefix, log_rounds, new SecureRandom());
}
public static String gensalt(String prefix, int log_rounds, SecureRandom random) throws IllegalArgumentException {
StringBuilder rs = new StringBuilder();
byte rnd[] = new byte[BCRYPT_SALT_LEN];
if (!prefix.startsWith("$2")
|| (prefix.charAt(2) != 'a' && prefix.charAt(2) != 'y' && prefix.charAt(2) != 'b')) {
throw new IllegalArgumentException("Invalid prefix");
}
if (log_rounds < 4 || log_rounds > 31) {
throw new IllegalArgumentException("Invalid log_rounds");
}
random.nextBytes(rnd);
rs.append("$2");
rs.append(prefix.charAt(2));
rs.append("$");
if (log_rounds < 10) {
rs.append("0");
}
rs.append(log_rounds);
rs.append("$");
encode_base64(rnd, rnd.length, rs);
return rs.toString();
}
우선 첫 번째 gensalt 메서드로는 새로운 SecureRandom 을 생성해서 두 번째 gensalt 를 넣어준다.
현재 문제는 다른 인코딩 비밀번호 이므로, 다른 salt 가 문제의 원인이라고 유추할 수 있고, 다른 salt가 되는 부분은 중간 부분의 random.nextBytes(rnd); 가 문제가 될 것이라 유추할 수 있다.
public SecureRandom() {
/*
* This call to our superclass constructor will result in a call
* to our own {@code setSeed} method, which will return
* immediately when it is passed zero.
*/
super(0); // Random(long seed) 임
getDefaultPRNG(false, null);
this.threadSafe = getThreadSafe();
}
그리고 SecureRandom 에서 seed를 0으로 일단 설정이 된다.
아무래도 잘못 건든 것 같다.. 파도파도 계속 나오는데..
아무래도 처음 BCryptVersion 버전이 나와 다른게 아닐까.. 싶다.
'TIL ✍️' 카테고리의 다른 글
23년 11월 20일(월요일) - 35번째 TIL (0) | 2023.11.20 |
---|---|
23년 11월 17일(금요일) - 34번째 TIL (2) | 2023.11.17 |
23년 11월 14일(화요일) - 32번째 TIL (0) | 2023.11.14 |
23년 11월 13일(월요일) - 31번째 TIL (1) | 2023.11.13 |
23년 11월 10일(금요일) - 30번째 TIL (0) | 2023.11.10 |