암호화 기법
암호화 기법은 단방향 암호화 기법인 Hash와 양방향 암호화 기법인 Encryption이 존재한다.
Hash는 평문을 암호화된 문장으로 만들어 주고, Encryption은 평문을 암호화된 문장으로 만들어주고 + 암호화된 문장을 다시 평문으로 만드는 복호화 기능을 제공한다.
그래서 Bcrypt가 뭐임?
단방향 암호화를 위해 만들어진 해시 함수이다.
기존의 해시 함수들이 암호화를 위해 설계된 것이 아니기에 취약하여 만들어졌다고 한다.
취약점 극복을 어떻게 했음?
- 솔트(salt)
- 각 비밀번호에 솔트를 추가한다.(음식에 소금 무작위로 뿌린다는 느낌에서 온 단어라 생각하면 될듯?)
여기서 솔트란 무작위 데이터로, 동일한 비밀번호라도 솔트가 다르면 해시의 값이 달라진다. salt 자체는 비밀이 아니지만 해시 값을 바꾸는데 목적이 있다. salt는 비밀번호마다 모두 다르기에 같은 비밀번호라도 해시 값이 달라진다. 이 때문에 공격자는 해시값 데이터베이스를 생성할 수 없다. 이로 인해 rainbow attack과 같이 미리 해시 값을 계산한 공격을 무효화 해준다.
- 각 비밀번호에 솔트를 추가한다.(음식에 소금 무작위로 뿌린다는 느낌에서 온 단어라 생각하면 될듯?)
- 키 스트레칭
- 기존의 단방향 해시 알고리즘의 빠른 실행 속도가 취약점이 됐던것을 보완하기 위해 나온 방법이다.
단방향 해시 값을 계산한 후 그 해시 값을 다시 해시하고, 이를 반복한다. 컴퓨터 성능에 따라 해시 반복 횟수를 조절하여 보안등급을 올릴 수 있다. 만약 무차별 대입 공격(brute force attack)으로 50억번 반복하려 할때 로그인에 걸리는 시간을 0.2초로 해두어도 일반 사용자는 불편함을 못느끼지만 해커는 반복 횟수가 높아 오래걸려 공격하기 힘들어진다.
- 기존의 단방향 해시 알고리즘의 빠른 실행 속도가 취약점이 됐던것을 보완하기 위해 나온 방법이다.
Bcrypt 적용을 위한 코드 설명
package com.example.schedule_jpa.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 메서드
매개변수 rawPassword 는 원본 비밀번호이다.
리턴 값은 해시된 비밀번호 문자열이다.
메서드 설명
- withDefaults()
- Bcrypt 라이브러리에서 기본 설정을 사용하여 인스턴스를 생성하는 메서드이다. 이를 통해 개발자는 별도의 설정 없이 기본적인 비밀번호 해시화를 수행할 수 있다.
- hashToString
- Bcrypt 라이브러리에서 비밀번호를 해시화하고, 해시된 비밀번호를 문자열로 반환하는 메서드이다. 이 메서드는 원본 비밀번호를 안전하게 저장하고 비교할 수 있도록 해시 문자열을 생성한다. BCrypt.MIN_COST는 최소비용으로 비밀번호를 해시화 한다는 뜻이다.
- BCrypt.MIN_COST가 뭐임?
- Bcrypt는 해시된 비밀번호가 일정한 시간 동안 생성되도록 하기 위해 작업 비용을 설정할 수 있다. 이때 사용하는 것이 cost 파라미터이며, 이는 해시 연산의 반복 횟수를 결정한다. BCrypt.MIN_COST 는 cost 파라미터의 최소 값을 나타내며, 2로 정의되어 있다. cost 파라미터는 2의 거듭제공 형태로 증가하기 때문에 2면 4번 반복한다는 의미이다. cost 값이 높을 수록 해시 생성과 검증에 시간이 더 오래 걸리며, 이는 공격자가 비밀번호를 크래킹하는 것을 어렵게 만든다.
- BCrypt.MIN_COST가 뭐임?
- Bcrypt 라이브러리에서 비밀번호를 해시화하고, 해시된 비밀번호를 문자열로 반환하는 메서드이다. 이 메서드는 원본 비밀번호를 안전하게 저장하고 비교할 수 있도록 해시 문자열을 생성한다. BCrypt.MIN_COST는 최소비용으로 비밀번호를 해시화 한다는 뜻이다.
동작 설명
- rawPassword를 문자 배열로 변환한다. (rawPassword.toCharArray())
- BCrypt.withDefaults().hashToString 메서드를 사용하여 최소 비용(BCrypt.MIN_COST)으로 비밀번호를 해시화한다.
- 해시된 비밀번호 문자열을 반환한다.
matches 메서드
매개변수 rawPassword 는 원본 비밀번호이고, encodedPassword는 해시된 비밀번호이다.
리턴값은 비밀번호 일치 여부를 나타내는 부울 값이다.
클래스 설명
- BCrypt.Result
- 비밀번호 검증 결과를 나타내는 클래스이다. 이 클래스는 비밀번호 검증의 결과를 포함하며, 검증 성공 여부를 확인할 수 있는 다양한 정보를 제공한다.
- verified : 검증 결과가 성공인지 여부를 나타내는 부울 값이다. 검증이 성공하면 true, 실패하면 false를 반환한다.
위 코드에서는 result.verified로 사용됐다.
- verified : 검증 결과가 성공인지 여부를 나타내는 부울 값이다. 검증이 성공하면 true, 실패하면 false를 반환한다.
- 비밀번호 검증 결과를 나타내는 클래스이다. 이 클래스는 비밀번호 검증의 결과를 포함하며, 검증 성공 여부를 확인할 수 있는 다양한 정보를 제공한다.
메서드 설명
- verifyer()
- 비밀번호 검증을 수행하는 객체를 생성한다.
- verify() 메서드를 호출하려면 verifyer()를 통해 호출한다.
- verify()
- 실제로 비밀번호를 비교하는 메서드이다. verifyer 객체를 통해 호출되어 원본 비밀번호와 해시된 비밀번호를 비교한다.
동작 설명
- rawPassword를 문자 배열로 변환한다.
- BCrypt.verifyer().verify 메서드를 사용해 원본 비밀번호와 해시된 비밀번호를 비교한다.
- 비교 결과(BCrypt.Result)의 verified 속성을 통해 비밀번호가 일치하는지 확인한다.
- 결과를 반환한다.
사용 예시
Service 클래스에서 의존성 주입을 해야한다.
private final 필드로 PasswordEncoder 클래스가 생성될 때 의존성을 고정하고, 생성자를 통해 그 값을 할당한다.
이제 비밀번호가 요구되는 메서드에 사용하면된다.
만약 회원가입을 하는데 비밀번호가 요구된다면?
@Service
@RequiredArgsConstructor
public class UserService {
private final PasswordEncoder passwordEncoder;
public UserResponseDto save(String name, String email, String rawPassword) {
String encodedPassword = passwordEncoder.encode(rawPassword);
// 사용자 저장 로직
User user = new User(name, email, encodedPassword);
// 데이터베이스에 사용자 저장 및 UserResponseDto 반환
return new UserResponseDto(user.getId(), user.getName(), user.getEmail());
}
}
save 메서드에서 encode 메서드를 불러와 비밀번호를 해싱한다.
로그인할때 비밀번호가 요구되는건 당연하다.
public UserResponseDto login(@Email String email, String password, HttpServletRequest request) {
User findUser = userRepository.findByEmailOrElseThrow(email);
if(findUser != null){ //find 할때 이미 이메일은 검증됨
boolean matches = passwordEncoder.matches(password, findUser.getPassword());
if(!matches){
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}
}
return new UserResponseDto(findUser.getId(), findUser.getUserName(), findUser.getEmail());
}
로그인할때 user를 찾은뒤(로그인 시 입력한 이메일로 회원가입한 유저가 존재하는가?) 찾은 user가 존재한다면 찾은 유저의 비밀번호와 내가 로그인할때 입력한 비밀번호가 맞는지 실행한다. 틀리면 HttpStatus 코드 던지기
'spring' 카테고리의 다른 글
| 뉴스피드 트러블슈팅 (0) | 2025.02.16 |
|---|---|
| HttpServletResponse 메서드 (0) | 2025.02.12 |
| scheduleJPA 트러블슈팅 (1) | 2025.02.07 |
| @ModelAttribute 와 @RequestBody (1) | 2025.02.05 |
| Validation, BindingResult, BeanVaildation, Validator (1) | 2025.02.05 |