회원 가입 과정에서 비밀번호의 암호화가 잘 되었는지를 테스트하고자 했는데, 지속적으로 실패했다. 내 생각은 다음과 같았다. '과정 전부 거치고 나온 Response DTO의 비밀번호랑 따로 PasswordEncoder로 인코딩한 비밀번호가 같은지를 검증하면 되지 않을까?' 뭐 꽤 괜찮은 생각이라고 생각했다. PasswordEncoder는 BCrype
그런데, 막상 진행해보니까 잘 되지를 않더라. 그래서 한 번, 같은 패스워드를 두 번 돌려서 결과를 확인해봤다.
@Test
void test(){
PasswordEncoder passwordEncoder1 = PasswordEncoderFactories.createDelegatingPasswordEncoder();
String password = "1234";
String a = passwordEncoder1.encode(password);
String b = passwordEncoder1.encode(password);
System.out.println("a : " + a);
System.out.println("b : " + b);
}
/////////////////////////
a : {bcrypt}$2a$10$Jsle3EQOA0/rK6edqXdn3.Y4JmRpv7SV1B1IxIb1YT.iEk2mtF59m
b : {bcrypt}$2a$10$NCl4JqwI693dj6kpjCFNleFRfK.FbIG6P5hhc9DjKz3w70jr0y8Cu
같은 값을 같은 PasswordEncoder에 넣었는데, 나오는 암호화 된 값은 달랐다. 관련해서 찾아보던 중 밸덩의 글과 bcrypt의 동작 과정을 읽고 해답을 알 수 있었다.
밸덩의 글에 따르면 암호화 해시는 여러 가지 기준을 만족해야 한다. 그 중 Collision resistance, Preimage resistance, Second preimage resistance 를 만족시키면 같은 값을 넣어도 다른 결과물이 나온다. 간단하게 살펴보면
- 해시 알고리즘 H, 서로 다른 메시지 a, b가 있다고 해보자.
- Collision resistance를 만족시키면 H(a) != H(b)이다.
- Preimage resistance를 만족시키면 암호화 된 값 c가 있을 때 H(x) = c를 만족시키는 x를 찾기 어려워야 한다.
- Second preimage resistance를 만족시키면 같은 암호화 된 값을 만들어내는 두 인자를 찾을 수 없어야 한다.
나는 Second preimage resistance에 집중했다. 'y라는 값이 두 번 나올 수는 있는데, 이게 어떻게 두 번 나왔는지는 몰라야한다.' 그러니까 H(x) = y가 나오고 H(z).= y가 나올 수도 있는데, x와 z를 찾는 것은 불가능해야한다는 말이다. 만약에 x == z면, 두 인자를 찾는 건 세상에서 제일 쉽다. 그냥 같으니까. 즉, "H(x) == H(z) 일 때, x != z여야 한다." 는 명제를 만들어 낼 수 있다. 참인 명제는 대우도 참이다. 즉, "x == z 일 때, H(x) != H(z)여야 한다" 도 참이 되는 것이다.
이제 bcrypt의 동작 과정을 보면 확실하게 알 수 있다. bcrypt는 암호화 과정에서 무작위 salt를 원문에 추가한다. 즉, x를 두 번 넣는다고 해도 다른 salt가 첨가되기 때문에 H(x) == H(x)가 될 수가 없는 것이다.
그럼 뭘 테스트해봐야하나를 찾아보니 PasswordEncoder의 내장 함수인 matches로 암호화한 값과 기존 값을 비교하는 테스트를 진행하는 분들도 있는데, 이게 굳이 필요한가 싶다. 암호화도 라이브러리 내장 함수고 matches도 내장 함순데 흠...
아무튼 PasswordEncdoer테스트가 어려운 이유를 공부하면서 bcrypt 암호화 원리와 암호화 해시의 기본 규칙도 학습할 수 있는 시간이었다. 암호화 테스트는 좀 더 고민해보자.
'Programming > Study' 카테고리의 다른 글
Spring Security의 인증 처리 흐름 (0) | 2023.12.06 |
---|---|
Spring Security를 사용할 때 csrf.disable()을 사용해도 되는 이유 - 토큰 사용 / 그래서 토큰은 어떻게 저장할건가? (2) | 2023.12.05 |
Querydsl 사용해보기 - 검색 기능 만들기 (1) | 2023.11.30 |
HTTP PUT 메서드로 자원을 수정할 때의 응답 : 200 vs 201 vs 202 (0) | 2023.11.27 |
public 함수와 private 함수의 배치 위치 (1) | 2023.11.20 |
댓글