https://20240228.tistory.com/153
최근에 비밀번호 암호화에 대해서 공부했었다.
그중에 단방향 암호화 라이브러리인 Spring Security Crypto를 사용해서 프로젝트에 적용해 보겠다.
라이브러리 Dependency 찾기
Maven Repository : Spring Security Crypto 검색
build.gradle
특정 버전 명시 O
dependencies {
implementation group: 'org.springframework.security', name: 'spring-security-crypto', version: '6.3.3'
}
특정 버전 명시 X
dependencies {
implementation 'org.springframework.security:spring-security-crypto'
}
회원가입 서비스에 적용하기
public void signUp(SignUp signup) {
Optional<User> findDuplicateUser = userRepository.findByEmail(signup.getEmail());
if(findDuplicateUser.isPresent()){
throw new AlreadyExistsEmailException();
}
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(
16,
8,
1,
32,
64);
String encryptedPassword = encoder.encode(signup.getPassword());
log.info("encryptedPassword:{}", encryptedPassword);
User user = User.builder()
.name(signup.getName())
.password(encryptedPassword)
.email(signup.getEmail())
.build();
userRepository.save(user);
}
내가 적용한 암호화 부분
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder(
16, // cpuCost
8, // memoryCost
1, // parallelization
32, // keyLength
64);// saltLength
String encryptedPassword = encoder.encode(signup.getPassword());
log.info("encryptedPassword:{}", encryptedPassword);
SCryptPasswordEncoder 분석
SCryptPasswordEncoder 생성
SHA-1 이상 해시와 8바이트 이상 랜덤 생성 솔트를 결합
당연히 솔트 부분은 내가 랜덤하게 넣어줘야 할 줄 알았는데 알아서 랜덤으로 넣어주는 게 신기하다.
이러면 어떻게 나중에 DB에 저장된(암호화된) 비밀번호와 입력한 비밀번호가 동일한지 비교를 어떻게 하는지 궁금하다.
SCryptPasswordEncoder
private final BytesKeyGenerator saltGenerator;
public SCryptPasswordEncoder(int cpuCost, int memoryCost, int parallelization, int keyLength, int saltLength) {
// 생략 ...
this.saltGenerator = KeyGenerators.secureRandom(saltLength);
}
SCryptPasswordEncoder 생성자이다.
해당 코드를 보면 생성자에서 객체 생성시점에 BytesKeyGenerator 값을 할당을 하는데
이때 KeyGenerators.secureRandom(saltLength);를 통해서 할당한다.
KeyGenerators
public static BytesKeyGenerator secureRandom(int keyLength) {
return new SecureRandomBytesKeyGenerator(keyLength);
}
여기서 다시 SecureRandomBytesKeyGenerator(keyLength); (생성자 호출)
SecureRandomBytesKeyGenerator
final class SecureRandomBytesKeyGenerator implements BytesKeyGenerator {
private static final int DEFAULT_KEY_LENGTH = 8;
private final SecureRandom random;
private final int keyLength;
SecureRandomBytesKeyGenerator(int keyLength) {
this.random = new SecureRandom();
this.keyLength = keyLength;
}
// 생략 ...
}
SecureRandomBytesKeyGenerator 클래스에 SecureRandom 생성하고 salt 길이 또한 저장을 한다.
SecureRandom은 랜덤한 바이트 salt 생성에 사용
SecureRandomBytesKeyGenerator
@Override
public int getKeyLength() {
return this.keyLength;
}
@Override
public byte[] generateKey() {
byte[] bytes = new byte[this.keyLength];
this.random.nextBytes(bytes);
return bytes;
}
이부분을 암호화할 때 사용한다.
this.random.nextBytes(bytes) 이부분은 랜덤한 salt 생성
org.bouncycastle.crypto.generators.SCrypt
브레이크 포인트를 찍고 디버깅 모드로 분석하던중 예외가 발생했다.
java.lang.ClassNotFoundException: org.bouncycastle.crypto.generators.SCrypt
build.gradle 추가로 해결
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
SCryptPasswordEncoder.matches() - 비교 메서드
SCryptPasswordEncoder.matches()
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (encodedPassword == null || encodedPassword.length() < this.keyLength) {
this.logger.warn("Empty encoded password");
return false;
}
return decodeAndCheckMatches(rawPassword, encodedPassword);
}
SCryptPasswordEncoder.decodeAndCheckMatches()
private boolean decodeAndCheckMatches(CharSequence rawPassword, String encodedPassword) {
String[] parts = encodedPassword.split("\\$");
if (parts.length != 4) {
return false;
}
long params = Long.parseLong(parts[1], 16);
byte[] salt = decodePart(parts[2]);
byte[] derived = decodePart(parts[3]);
int cpuCost = (int) Math.pow(2, params >> 16 & 0xffff);
int memoryCost = (int) params >> 8 & 0xff;
int parallelization = (int) params & 0xff;
byte[] generated = SCrypt.generate(Utf8.encode(rawPassword), salt, cpuCost, memoryCost, parallelization,
this.keyLength);
return MessageDigest.isEqual(derived, generated);
}
암호화되지 않은 비밀번호와 암호화된 비밀번호를 메서드를 매개변수로 받고
내부에서 암호화되지 않은 비밀번호를 암호화했던 비밀번호와 동일한 방식(해시 알고리즘, 키 길이, salt, ...)으로
암호화해서 같은지 비교하고 같으면 true, 다르면 false를 반환한다.
'Spring > Spring Security' 카테고리의 다른 글
[Spring Security] Remember Me (0) | 2024.09.02 |
---|---|
[Spring Security] Spring Session JDBC (2) | 2024.09.02 |
[Spring Security] 시큐리티 폼 로그인 커스텀 설정 (0) | 2024.09.02 |
CSRF? + Spring Security 기본 설정 (4) | 2024.08.29 |
JWT를 이용한 인증 (0) | 2024.08.26 |