작성
·
1.3K
1
안녕하세요.
spring security 권장사항이 BCrypt라고 해서 조금 찾아보고
테스트를 해봤는데요.
<첫 번째 시도>
password = !@#$password1234
passwordHashed = $2a$10$foL9uBBw3knu9QoKmVb64.pRTRsxy96NQUQanjhOzl8D1yEoLs73m
isValidPassword = true
<두 번째 시도>
password = !@#$password1234
passwordHashed = $2a$10$abpfdjC6qWnj687evfjzx.bV0Xlkas7wcx0s8OT2UwQD1Huo54oyi
isValidPassword = true
보면 솔트를 랜덤으로 생성하기 때문에
인코딩된 패쓰워드가 다르게 나오는데
어떻게 인증이 되는건가요..?
어.. 그러니까 만약 Bcyrpt 솔트 기본값 10으로
회원등록을 하고 (첫 번째 시도)
이 걸로 다시 로그인을 하면 두 번째 시도의 값이
나와서 두 개가 다르다고 인식을 할 거 같은데
어떻게 옳은 패스워드로 스프링 시큐리티가 판단하는지
궁금합니다.
아니면 솔트를 사용하는게 아닌건지..궁금합니다.
답변 9
5
시간이 오래 지났지만 요즘 새 강좌를 만들다가 문득 이 질문이 다시 생각나서 찾아봤습니다. 우선 사과드립니다. 제가 잘못 알고 있었네요. 수강평에 남겨주신대로 테스트를 작성하지 않고 댓글을 남겨 죄송합니다.
결론부터 말씀드리자면, 기본 생성자를 사용한 BCryptPasswordEncode로 encoding을 할 때 salt는 매번 바뀌는게 맞습니다. 대신 matches로 plain 패스워드와 인코딩 된 패스워드를 비교할 때는 salt를 사용하지 않습니다. 오로지 plain 패스워드와 인코딩된 패스워드만 가지고 해시를 하고 그 결과는 인코딩 된 패스워드와 일치하게 되어있습니다. 따라서 salt가 바뀌는건 신경쓰지 않아도 됩니다. 인코딩 할 때만 쓰이니까요.
덕분에 해시 기반 패스워드 동작 원리를 다시 한번 돌아보는 시간이 되었네요. 감사합니다.
0
프로그램 재가동시 바뀝니다. (위 코드 테스트 결과도 fail되는군요..)
프로그램을 처음 초기화 할 경우 생성자에서 솔트를 랜덤 생성해서 넣어주기 때문이죠.
위에 이것에 대해서 다 글을 써놓았는데요.
제가 그래서 의문점이 생긴것이 그러면 어떻게 인코더는 프로그램을 재가동했을때
솔트를 알 수 있나 ? 이것 이었습니다.
왜냐하면 그 부분을 정확히 이해가 안된다면 프로그램 개발을 하고도 찝찝할 수 박에 없기 때문이죠.
-----------------------------------------------------------------------------------------------
테스트 결과 원래인코딩된 패스워드를 솔트로 제공하면 어떻게 알아내는것인지는 모르겠지만
그 전에 어떤 솔트 값을 넣었는지 몰라도 같은 인코딩 결과가 나오게 됩니다.
물론 이 내용도 위 글에서 다 써놓았습니다..
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String firstEncoded = passwordEncoder.encode("password");
String secondEncoded = passwordEncoder.encode("password");
System.out.println(firstEncoded);
System.out.println(secondEncoded);
<첫 번째 가동시>
$2a$10$bGPFYVPMIodBr3kPBNiA4u8MmOoqeDaTTqwSvwks57Qyci4fjNltK $2a$10$1OxvWR6cYdJYO6/QSFHHDeevmbuaCirH4ZJdezwKzihHuVnlwMynG
<두 번째 가동시>
$2a$10$h0GZ96jg5BR2QDQxWtxQKuehwx.VLOw2QYFQX0PXmZhT94Eh6kPki $2a$10$m.e0/kCIsuY3NODsVVw8xu0JZrprfY3Y2YhFduCEUOWLlpJFHUq1a
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String firstEncoded = BCrypt.hashpw("password", "$2a$10$bGPFYVPMIodBr3kPBNiA4u8MmOoqeDaTTqwSvwks57Qyci4fjNltK");
System.out.println(firstEncoded);
<첫 번째 가동시 인코딩된 값을 BCrypt.hashpw의 솔트 전달 부분에 전달했을 경우 인코딩된 값>
$2a$10$bGPFYVPMIodBr3kPBNiA4u8MmOoqeDaTTqwSvwks57Qyci4fjNltK
0
@Test
public void bcryptPassword() {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String firstEncoded = passwordEncoder.encode("password");
String secondEncoded = passwordEncoder.encode("password");
assertEquals(firstEncoded, secondEncoded);
}
BCrypt 패스워드 인코더의 솔트값은 바뀌질 않습니다.
0
허허 백선장님 .. 디버깅을 하며 분석하다
테스트를 해봤더니요.
String password = "!@#$password1234";
String passwordHashed = BCrypt.hashpw(password, BCrypt.gensalt());
String hashpw = BCrypt.hashpw(password, passwordHashed);
System.out.println("first hashed = " + passwordHashed);
System.out.println("second hashed = " + hashpw);
first hashed = $2a$10$csCCTJlus2OXEyAHmYPmNOCtkEj8lv61WnJRVkVELo0GKjc4bIif. second hashed = $2a$10$csCCTJlus2OXEyAHmYPmNOCtkEj8lv61WnJRVkVELo0GKjc4bIif.
이렇게 나오네요.
즉 첫 번째 인자에 rawpassword, 두 번째 인자에 인코딩된 비밀번호
를 넣으면
첫 번째 인장에 rawpassword, 두 번째 인자에 BCrypt.gensalt()넣었던 결과가 똑같이
나오게 되는군요.
0
음 계정을 만드는 부분
this.password = passwordEncoder.encode(this.password);
을 디버그 해보니
PasswordEncoderFactories 에서 bCrypt를 생성할때 SecureRandom random 인자에 null을 주면
BCryptPasswordEncoder
public String encode(CharSequence rawPassword) {
String salt;
if (strength > 0) {
if (random != null) {
salt = BCrypt.gensalt(strength, random);
}
else {
salt = BCrypt.gensalt(strength);
}
}
else {
salt = BCrypt.gensalt();
}
에서 보이듯이 gensalt를 실행해서 salt를 얻는데요.
이 부분을 반복적으로 실행해보니 항상 다른 salt가 나오던데..
그렇다면 고정된 값이 아니지 않나요..?
0
저.. 그런데 제가 궁금해서 더 테스트를 해봤는데요.
String passwordHashed = BCrypt.hashpw(password, null);
이렇게 테스트를 했더니 salt값으로 null을 넣을 수 없다고 하는데
스프링 시큐리티 PasswordEncoderFactories에서는 생성자에서 null로 입력해서 하는데
그 이후에 실제로 어떻게 hashing을 하는지 그 과정이 궁금합니다.
0
PasswordEncoderFactories에서
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
이렇게 하고
public BCryptPasswordEncoder() {
this(-1);
}
이렇게
public BCryptPasswordEncoder(int strength) {
this(strength, null);
}
이렇게 되어있네요..
0
https://docs.spring.io/spring-security/site/docs/4.2.12.RELEASE/apidocs/org/springframework/security/crypto/bcrypt/BCryptPasswordEncoder.html
JavaDoc을 보시면 생성자에서 강도와 솔트를 생성자로 받는걸 볼 수 있습니다. 즉, 솔트와 강도는 고정값이지 매번 바뀌는 값이 아니니까 만드신 예제처럼 바뀌는 경우는 발생하지 않을 겁니다.
너무 늦게 봤네요.
자세한 정보 감사합니다.
새해 복 많이 받으시길 바라겠습니다.