• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

BCrypt 인증 관련 질문드립니다.

19.10.13 10:57 작성 조회수 942

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가 바뀌는건 신경쓰지 않아도 됩니다. 인코딩 할 때만 쓰이니까요.

덕분에 해시 기반 패스워드 동작 원리를 다시 한번 돌아보는 시간이 되었네요. 감사합니다.

2

쿠크다스님의 프로필

쿠크다스

2020.08.29

너무 늦게 봤네요.

자세한 정보 감사합니다.

새해 복 많이 받으시길 바라겠습니다.

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을 보시면 생성자에서 강도와 솔트를 생성자로 받는걸 볼 수 있습니다. 즉, 솔트와 강도는 고정값이지 매번 바뀌는 값이 아니니까 만드신 예제처럼 바뀌는 경우는 발생하지 않을 겁니다.