🔐 BCryptPasswordEncoder
BCryptPasswordEncoder는 Spring Security에서 가장 많이 사용되는 비밀번호 해싱 도구이다.
또한, bcrypt 알고리즘을 기반으로 비밀번호를 단방향 해싱하고, 자동으로 salt를 추가해주는 보안 알고리즘이다.
🤔 bcrypt 알고리즘?
bcrypt는 비밀번호를 안전하게 저장하기 위한 해싱 알고리즘이다. 비밀번호 보안을 위한 대표적인 알고리즘 중 하나이며,
Spring Security, Django, Laravel 등 거의 모든 프레임워크에서 사용되고 있다.
📌 bcrypt의 주요 특징
- 단방향 해싱: 복호화가 불가능한 해시값 생성
- 자동 Salt 포함: 같은 비밀번호도 다른 해시값 생성 (레인보우 테이블 공격 방지)
- 계산 비용 조절 가능: work factor 또는 cost factor를 통해 암호화 속도 조절
- 멱등성 불가: 같은 입력값도 매번 다른 결과 (보안 ↑)
- 보안성 높음: 현재까지 알려진 실용적인 공격 없음
🔄 해싱 구조
$2a$10$EixZaYVK1fsbw1ZfbX3OXePaWxn96p36LFtQDQ3G4wXvQq1EFKlhW
\_/ \__/\_______________________________________________/
| | |
알고리즘 비용(factor) 해시 값 (salt 포함)
- $2a$: 알고리즘 버전
- 10$: cost factor (2¹⁰ 반복 = 1024번 연산)
- 나머지: salt + 해시된 비밀번호
🔧 Cost Factor (Work Factor)
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12); // 2^12번 연산
- bcrypt는 슬로우 해시 함수(slow hash function)이다.
- cost factor는 연산 반복 횟수를 2의 제곱으로 늘려서 해킹에 걸리는 시간을 증가시킨다.
- 일반적으로 10~12 사이 사용
- 당연히 숫자가 높을수록 보안은 좋아지지만 처리 속도는 느려진다.
🤔 비밀번호는 왜 단방향 해싱해야 할까?
- 비밀번호를 DB에 평문(plain text)으로 저장하면 해킹 시 모든 사용자 비번이 노출될 수 있다.
- 단방향 해시는 입력값을 고정된 길이의 암호화된 값으로 변환하되, 역으로 복호화할 수 없기 때문에 안전하다.
✅ BCryptPasswordEncoder 사용 예제
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class PasswordEncoderExample {
public static void main(String[] args) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String rawPassword = "mySecret123!";
String encodedPassword = encoder.encode(rawPassword);
System.out.println("Encoded Password: " + encodedPassword);
// 비밀번호 일치 여부 확인
boolean isMatch = encoder.matches(rawPassword, encodedPassword);
System.out.println("Match result: " + isMatch);
}
}
- BCryptPasswordEncoder는 내부적으로 salt를 자동 생성해서 넣어준다.
- → 매번 같은 비밀번호를 encode해도 다른 결과가 나옴.
- DB에 해시된 비밀번호만 저장하고, 로그인 시 matches() 메서드로 비교한다.
⚙ 생성자 옵션
new BCryptPasswordEncoder(int strength);
- strength: 해시 계산 비용(보안 수준) 조절
- 기본값은 10 (높을수록 더 느림, 더 안전)
- 일반적으로 10~12 사이면 충분
🤔 그럼 비교는 어떻게 하지?
bcrypt의 매력 포인트 중 하나가 바로 "해시 값 안에 salt랑 cost 정보까지 같이 포함되어 있다"는 것이다.
ㄴ 이 덕분에 DB에 오직 해시값 하나만 저장해도 되고, 그걸로 검증까지 다 가능진다.
🧪 실제 bcrypt 해시 예시
$2a$10$DOWSD9DNuwzVuvTRZjC6A.YrObI91ZgPNf4XEVXLw6vH/7PksnAfu
- $2a$: bcrypt 버전 (버전에 따라 호환성 달라짐)
- 10$: cost factor → 반복 횟수: 2¹⁰ = 1024번 해싱
- DOWSD9DNuwzVuvTRZjC6A.: 22자리의 salt
- YrObI91ZgPNf4XEVXLw6vH/7PksnAfu: 최종 해시 결과
- Spring Security에서 제공하는 matches() 메서드는 이렇게 비교한다.
🔍 어떻게 비교하냐면
boolean isMatch = encoder.matches(rawPassword, encodedPasswordFromDB);
- DB에 저장된 bcrypt 해시 문자열을 matches()가 받으면
- 문자열에서 salt + cost 값을 자동으로 추출
- 로그인 시 입력된 비밀번호에 같은 salt, 같은 cost로 해싱 수행.
- 그 결과가 저장된 해시와 논리적으로 같은지 비교한다.
즉, encoded()는 결과가 다를 수 있지만, matches()는 정확히 일치하는지 판단할 수 있도록 설계되어 있다!
🔁 흐름 요약
[입력한 비밀번호] + [DB 해시에서 추출한 salt/cost 정보] → 새로 해시 → 기존 해시와 비교
✅ 장점
- DB에 해시 하나만 저장하면 됨 → 별도로 salt 저장 안 해도 됨
- 다른 유저가 같은 비밀번호를 쓰더라도 서로 다른 해시값이 됨
- brute-force나 rainbow table 공격에 훨씬 강함
🤔 그럼 bcrypt 해시에 salt랑 cost 정보까지 다 들어있으면, 해커가 DB만 털면 비밀번호를 알 수 있는 거 아냐?
- 정답은 ❌ "아니다. 쉽게는 못 알아낸다."
- 왜냐하면 bcrypt는 단방향 해시이기 때문이다.
👉 해커가 알 수 있는 건:
- bcrypt 해시 ($2a$10$...)
- salt (해시 안에 있음)
- 해싱 알고리즘과 cost 값
❌ 하지만 알 수 없는 건:
- 해시를 거꾸로 되돌려서 원래 비밀번호를 알아내는 방법은 없음(단방향 해시)
- bcrypt는 설계 자체가 복호화가 불가능한 구조이다.
- 그리고 bcrypt는 단순 SHA처럼 빠른 게 아니라, ⏱ intentionally 느리게 계산된다. (보통 2^10 ~ 2^12번 반복).
- → 해커가 비밀번호 하나 추측해볼 때마다 수 ms ~ 수백 ms 걸린다.
- → 💸 수백만 개 대입하려면 시간/비용 엄청나다. (Brute-force 어려움)
😈 해커가 할 수 있는 건?
for (추측한 비밀번호 후보들):
bcrypt.hash(후보, 해시에서 추출한 salt)
해시값이 DB에 저장된 거랑 같으면 성공
- 이론적으로는 brute-force 시도
- 그런데 비밀번호가 복잡하면 성공률 급하락
- bcrypt는 느림 → 대량 시도는 시간/비용 부담
💥 시나리오:
“해커가 로그인하는 순간 사용자가 입력한 비밀번호 원문(password) 를 탈취했다!
그리고 이미 DB도 털려서 bcrypt 해시값 ($2a$10$...) 도 가지고 있다.”
그런데 중요한 건 이것이 아니다.
🔑 1. 그 비밀번호를 원래부터 알고 있었어야 한다는 점
- 해커가 "확인"할 수 있다는 건, 이미 비밀번호를 어딘가에서 가로챘다는 뜻이다.
- 그럼 bcrypt는 그 자체로 더 이상 방어선이 아니게 되는 상황이에요.
- → 이건 암호 알고리즘의 취약점이 아니라,로그인 정보를 가로챈 보안 사고인 거예요.
예: 키로깅, 네트워크 스니핑, 브라우저 취약점 등
🔐 2. bcrypt는 비밀번호를 유출되지 않도록 저장하는 거지 유출 이후를 막는 건 아니다.
- 비밀번호가 탈취된 순간부터는 bcrypt고 뭐고 의미가 없음
- bcrypt는 "DB가 털려도 안전하게 저장하는 용도"
- → 네트워크 상의 보안은 TLS(HTTPS), WebAuthn, MFA 같은 걸로 따로 보강해야 한다.
'Spring' 카테고리의 다른 글
[Spring] 스프링 빈과 스프링 컨테이너 (0) | 2025.01.12 |
---|---|
[Spring] 예외 처리 방식 (0) | 2024.12.11 |
[Spring] Security JWT (0) | 2024.11.10 |
[Spring] 세션, 토큰, 쿠키 (0) | 2024.11.08 |
[Spring] @Profile (0) | 2024.11.04 |