비밀번호를 데이터베이스에 그대로 저장하면 안 되는 이유
비밀번호를 SHA-256으로 해시해서 저장하면 안전할까요? 결론부터 말하면 아닙니다. SHA-256은 빠른 게 장점인 해시 함수입니다. 그런데 비밀번호 저장에는 그 "빠름"이 정확히 약점이 됩니다. 공격자가 데이터베이스를 탈취했다면, 빠른 해시 함수일수록 GPU로 초당 수십억 개의 후보 비밀번호를 대입해볼 수 있습니다. 우리가 원하는 건 반대입니다 — 정상적인 로그인 한 번은 신경 쓰이지 않을 정도로 빠르되, 무차별 대입은 일부러 느리게 만드는 함수.
BCrypt가 기본값인 이유
스프링 시큐리티는 PasswordEncoder 인터페이스로 이 문제를 추상화하고, 기본 구현으로 BCryptPasswordEncoder를 씁니다.
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}@Service
public class UserService {
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
public void signUp(String username, String rawPassword) {
String encoded = passwordEncoder.encode(rawPassword);
userRepository.save(new User(username, encoded));
}
public boolean login(String username, String rawPassword) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new BadCredentialsException("invalid"));
return passwordEncoder.matches(rawPassword, user.getPasswordHash());
}
}matches를 쓰는 이유가 중요합니다. BCrypt는 매번 다른 솔트(salt)를 자동으로 섞어 넣기 때문에, 같은 비밀번호를 두 번 인코딩해도 결과 문자열이 다릅니다.
passwordEncoder.encode("password123!"); // $2a$10$abc...xyz
passwordEncoder.encode("password123!"); // $2a$10$def...uvw (다른 결과)그래서 encode(input).equals(저장된값) 같은 비교는 항상 false가 나와 틀린 코드입니다. matches(원본, 저장된해시)가 내부적으로 저장된 해시에서 솔트를 꺼내 같은 방식으로 다시 계산해 비교해줍니다. 이 API를 왜 이렇게 설계했는지 모르고 쓰면 신입 개발자가 가장 먼저 걸려 넘어지는 지점이기도 합니다.
비용 계수는 시간이 지날수록 올려야 한다
BCryptPasswordEncoder()의 기본 강도(cost factor)는 10입니다. 이 값은 해시 계산에 드는 연산량을 지수적으로 늘립니다. 하드웨어가 빨라질수록 같은 강도 10이 상대적으로 약해지므로, 신규 프로젝트라면 12 정도로 올리는 걸 권합니다.
new BCryptPasswordEncoder(12);다만 이미 강도 10으로 저장된 기존 사용자 비밀번호를 일괄로 재해시할 방법은 없습니다 — 원본 비밀번호를 서버가 알지 못하기 때문입니다. 강도를 올렸다면 다음 로그인 시점에 재해시하는 방식(로그인 성공 직후 새 강도로 다시 인코딩해서 덮어쓰기)이 현실적인 마이그레이션 경로입니다.
Argon2를 고려할 시점
2015년 Password Hashing Competition에서 우승한 Argon2는 GPU 병렬 공격에 BCrypt보다 강한 저항성을 갖도록 설계되었습니다. 스프링 시큐리티도 Argon2PasswordEncoder를 제공합니다. 다만 메모리 사용량 튜닝이 BCrypt보다 까다롭고, 대부분의 국내 서비스 규모에서는 BCrypt로 충분하다는 게 제 판단입니다. 금융권처럼 규정으로 특정 알고리즘을 요구받는 게 아니라면, 익숙하고 검증된 BCrypt를 기본값으로 두고 넘어가도 됩니다.