[Spring] Bcrypt로 암호화하기

2021. 4. 11. 18:42Spring

[Spring] Bcrypt를 이용하여 비밀번호를 암호화하여 저장하는 방법

개요

  • 단순 텍스트에 비밀번호를 저장하는 것은 보안상 매우 취약하다. 이 글에서는 자바를 이용하여 데이터베이스에 해시된 패스워드를 저장하는 방법을 다룬다. 이렇게 한다면 BDA들 조차도 실제 패스워드를 뺴내기 불가능하다. 기존의 MD5, SHA-1, SHA-2로 패스워드를 해싱할 수도 있지만 솔트를 치는것은 추가적인 보안을 만들 수 있다. (여기서 솔트를 친다는 말은 원문에 추가적인 문자열을 넣어서 해시를 강화한다고 이해하면 된다.) 이 글에서는 jBCrypt를 사용할 건데 그것은 패스워드를 인코딩할 때 내부적으로 랜덤 솔트를 생성하여 단순 텍스트를 인코딩하고 DB에 저장한다.( 솔트를 하게 되면 같은 문자열로 부터 다른 해시 값이 생성된다.)

Bcrypt 인코딩은 무엇인가???

  • bcrypt는 비밀번호 해시함수로 Niels Provos와 David Mazieres에 의해 만들어졌고 Blowfish라는 암호에 기반( 사실 너무 개념적인 이야기여서 나도 모르곗댜...)Bcrypt는 조정할 수 있는 해시알고리즘을 써서 패스워드를 저장한다. Bcrypt는 패스워드를 해싱할 때 내부적으로 랜덤한 솔트를 생성하기 때문에 같은 문자열에 대해서 다른 인코드된 결과를 반환한다. 하지만 공통된 점은 매번 길이가 60인 String을 만든다.

  • 의존성 추가

    implementation group: 'org.mindrot', name: 'jbcrypt', version: '0.3m' // gradle
    • 나는 gradle여서 gradle로 의존성을 추가하였다.

Bcrypt 구현을 통한 패스워드 암호화

  • Bcrypt 라이브러리는 단순 텍스트 패스워드를 해시하기 위한 이미 완성된 구현체를 쓰게 해준다. hashpw() 메서드는 단순 텍스트와 랜덤한 솔트를 인자로 받는다.

    private String hashPassword(String plainTextPassword) { return      BCrypt.hashpw(plainTextPassword, BCrypt.gensalt()); 
    }

암호화된 것과 원문의 매칭

  • 원문과 암호화된 패스워드를 매칭하는 부분이다.

    private void checkPass(String plainPassword, String hasedPassword) { if           (BCrypt.checkpw(plainPassword, hasedPassword)) { // doSomething(); } 
    }

프로젝트에 적용된 예시 보기

  • EncryptHelper.interface

    public interface EncryptHelper {
        String encrypt(String password);
        boolean isMatch(String password, String hashed);
    }
    • 이름은 EncryptHelper라고 정의하였다. 암호화와 비밀번호를 매칭해주는 두 가지 기능을 추상화하여 만들었다. 실제 구현체를 만들어보자.
  • BcryptImpl.java

    public class BcryptImpl implements EncryptHelper {
        @Override
        public String encrypt(String password) {
            return BCrypt.hashpw(password,BCrypt.gensalt());
        }
    
        @Override
        public boolean isMatch(String password, String hashed) {
            return BCrypt.checkpw(password,hashed);
        }
    }
    • 애초에 BCrypt가 매우 간단하기 때문에 Wrapping을 해도 매우 간단하다. 내가 생각한 Wrapping을 한 이유는 다음과 같다.
      1. BCrypt 클래스의 이름과 내부 메서드 철자의 어려움
      2. 프로젝트의 특성에 맞게 새로 클래스를 구현할 수 있다.
  • 사용할 클래스는 정의 했으니 @Bean으로 등록할 차례이다.

    @EnableJpaAuditing
    @SpringBootApplication
    public class DanggeonApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DanggeonApplication.class, args);
        }
        @Bean
        public EncryptHelper encryptConfig() {
            return new BcryptImpl();
        }
    }
    • 일단 나는 Application에 @Bean을 등록했지만 따로 config파일을 만들어서 @Bean을 등록해도 된다.
  • Service에 EncryptHelper 주입하기

    private final UserRepository userRepository;
        private final EncryptHelper encryptHelper;
        private JwtTokenProvider jwtTokenProvider = new JwtTokenProvider();
    
    //    private final PasswordEncoder passwordEncoder;
        @Autowired
        public UserService(UserRepository userRepository, EncryptHelper encryptHelper) {
            this.userRepository = userRepository;
            this.encryptHelper = encryptHelper;
        }
            //...
    }

참고

'Spring' 카테고리의 다른 글

[Spring boot] stomp를 활용해서 1:1 채팅 만들기  (3) 2021.04.25
[Spring] @Valid  (0) 2021.04.11
Spring으로 Token 받기  (0) 2021.04.11
spring jpa localtime between  (0) 2021.03.23
Spring-JPA(@MappedSuperClass,@EntityListener)  (0) 2021.03.23