[Spring] bcrypt - 비밀번호 암호화 원리 / 비밀번호 복호화가 필요없는 이유

minjonyyy·2025년 2월 18일

[Spring]

목록 보기
4/6
post-thumbnail

지난 숙련 주차에서 비밀번호 암호화에 대해서 알게 되었고, 직접 과제 코드에 적용해 보았다.
추가로 과제 발제 때 튜터님께서 말씀하셨던 것처럼 비밀번호 복호화 는 필요없는 이유 에 대해서도 생각해보았다.


암호화란?

: 어떠한 평문을 알아볼 수 없도록 암호문으로 바꾸는 것을 말한다.
만약 데이터가 유출이 되더라도! 비밀번호나 혹은 중요한 정보가 담겨있는 어떠한 것들을 해석하지 못하는 데에 의의가 있다.

  • 사용자의 비밀번호는 절대 데이터베이스에 노출되게 저장하지 않는다.
    -> 해킹을 당한다면??? 많은 사용자들의 개인정보가.. 🌀
    -> 그게 아니더라도, 관리자나 내부 개발자도 믿을 수 없다!

➡️ 따라서 무조건 암호화하여 저장한다.

암호화의 종류

1. 단방향 암호화 (One-Way Encryption)

: 단방향 암호화는 주로 비밀번호 저장과 같은 경우에 사용되어, 원본 데이터를 복원할 수 없는 방식으로 변환된다.

  • 해시 함수를 사용
    : 임의의 길이를 가진 데이터를 입력받아 고정된 길이의 값, 즉 해시값을 출력하는 함수이다.
    : 해시값은 입력 데이터로부터 유도되기 때문에 동일한 입력은 항상 동일한 해시값을 갖게 된다.

  • 대표적인 해시 함수로는 SHA-256, bcrypt, scrypt, PBKDF2 등이 존재한다.

  • 역상 저항성 (preimage resistance)
    : 어떤 해시 함수가 특정한 값을 출력하는 입력값을 찾기 어려움을 의미한다.
    -> 해시값에서 원본 데이터로의 역변환이 얼마나 어려운 지에 대한 척도

2. 양방향 암호화 (Two-Way Encryption)

: 양방향 암호화는 말 그대로 복원할 수 있는 암호화이다.

  • 대칭 키 암호화
    : 동일한 키가 데이터를 암호화하고 해독하는 데 사용된다.
    : 대표적인 대칭 키 알고리즘으로는 AES (Advanced Encryption Standard) 가 존재한다.

  • 주로 데이터를 안전하게 전송하거나 저장하기 위해 사용된다.
    -> 예를 들어, 데이터를 HTTPS를 통해 전송하는 경우에 양방향 암호화가 필요!


Bcrypt 를 사용한 비밀번호 암호화

위에서 말했듯이, 단방향 암호화에서는 해시 함수를 사용한다.
그래서 같은 비밀번호가 입력된다면??? 똑같이 암호화가 된다는 것..

하지만 Bcrypt는 같은 비밀번호를 암호화하더라는 해시 값이 매번 다르게 도출되는 방식을 사용한다.

🧂 솔트(salt)

: 실제 비밀번호 이외에 추가적으로 랜덤한 데이터 값을 더해 해시 값을 계산하는 방법이다.
➡️ 비밀번호의 복잡도를 키워서 보안이 높아진다!

사용자 비밀번호 -> 솔트 값 생성 -> 해싱

비밀번호 길이도 더욱 더 길어지고, 따라서 크래킹하는데 시간이 늘어나기 때문에 해킹이 어려워진다.

그렇다면... 비밀번호가 확인 로직에서도 다른 솔트 값을 부여하면 어떻게 비교할까???

BcryptPasswordEncoder

: Spring Security 프레임워크에서 제공하는 클래스이다.
-> 사용자가 제출된 비밀번호와 암호화되어 저장된 비밀번호의 일치 여부를 확인하는 메서드가 제공된다.
👏🏻👏🏻👏🏻 정말 똑똑하다..

또한, BcryptPasswordEncoder는 Bcrypt의 로그 라운드라고 하는 강도(strength)를 설정할 수 있는데, 강도가 클 수록 암호를 해시하기 위해 더 많은 작업을 수행해야한다.
: 기본값 = 10 / 4~31 사이의 값을 설정할 수 있음

그럼, 제공하는 메서드 3가지를 살펴보자

1. String encode(String rawPassword)

: 비밀번호를 암호화해주는 메서드이다.
해싱 과정에서 무작위로 생성한 salt가 포함되어, 같은 비밀번호를 인코딩해도 매번 다른 결과값이 반환된다.

2. boolean matches(String rawPassword, String encodedPassword)

: 제출된 인코딩하지 않은 비밀번호와 저장소에 있는 인코딩된 비밀번호가 일치하는지 확인하는 메서드이다.
-> 일치하면 true, 일치하지 않으면 false 반환

저장된 비밀번호 자체를 디코딩하는 것은 아니다!!!

3. boolean upgradeEncoding(String encodedPassword)

: 더 나은 보안을 위하여 인코딩된 비밀번호를 다시 인코딩해야 하는 경우 true 반환, 그렇지 않으면 false를 반환하는 메서드이다.
-> 디폴트값으로 항상 false를 반환하므로, 필요할 때 오버라이딩하여 인코딩된 비밀번호의 안정성을 체크하는 로직을 구현할 수 있다.


코드에 적용해보자!

1. build.gradle 에 의존성 추가

implementation 'at.favre.lib:bcrypt:0.10.2'

2. config 패키지에 아래의 클래스 추가

import at.favre.lib.crypto.bcrypt.BCrypt;
import org.springframework.stereotype.Component;

@Component
public class PasswordEncoder {

    public String encode(String rawPassword) {
        return BCrypt.withDefaults().hashToString(BCrypt.MIN_COST, rawPassword.toCharArray());
    }

    public boolean matches(String rawPassword, String encodedPassword) {
        BCrypt.Result result = BCrypt.verifyer().verify(rawPassword.toCharArray(), encodedPassword);
        return result.verified;
    }
}

앞서 말했던 encode 메서드matches 메서드를 사용하였다.

3. authService 에 적용

@Transactional
    public UserResponseDto signup(String email, String username, String password) {
        userRepository.findByEmail(email).ifPresent(user -> {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "이미 존재하는 이메일입니다.");
        });
		
        // 인코딩 적용!!
        String encodedPassword = passwordEncoder.encode(password);

        User user = new User(email, username, encodedPassword);
        User createdUser = userRepository.save(user);

        return new UserResponseDto(createdUser.getUserId(), createdUser.getUsername());
    }


    public LoginResponseDto login(LoginRequestDto requestDto) {
        User findUser = userRepository.findUserByEmailOrElseThrow(requestDto.getEmail());
		
        // matches 함수를 통해 비밀번호 비교하여 로그인!!!
        if(!passwordEncoder.matches(requestDto.getPassword(), findUser.getPassword())){
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다.");
        }
        return new LoginResponseDto(findUser.getUserId());

    }

비밀번호 복호화가 필요없는 이유?

튜터님께서 말씀하셨던 비밀번호 복호화의 필요여부에 대한 내용이다.

  1. 우선 우리는 스프링에서 제공하는 Bcrypt를 사용하기 때문에, 애초에 복호화 과정이 불가능하다. 😅

  2. 과제에서는 단순한 회원가입/로그인 로직만 구현하였기 때문에 복호화 과정이 필요가 없다.

-> 비밀번호 찾기 를 구현하고 싶다면?
: 사실 우리가 사용하는 웹사이트들을 생각해보면, 비밀번호 찾기를 한다고 비밀번호를 다시 알려주진 않는다...
다시 알려주는 과정이 있다면 애초에 비밀번호 암호화가 소용 없는 게 아닐까???

-> 사용자 인증 (이메일 혹은 아이디를 통한 인증 코드 전송 or 비밀번호 재설정 링크 전송) 과정만 구현한다면, 비밀번호는 언제든 새로 설정하면 되기 때문이다!!!!!

  • 이렇게 새로운 비밀번호를 설정하는 과정에서도 matches 함수를 통해 이전에 사용했던 비밀번호를 재사용하지는 않았는지를 확인할 수도 있겠구나..!

0개의 댓글