Backend - Spring Security SHA-256 Password Encoder 구현하기
기존에는 Spring Security에서 제공하는 BCryptPasswordEncoder를 사용하였으나 SHA-256 알고리즘을 통해 비밀번호를 암호화하도록 PasswordEncoder를 변경해야할 일이 생김.
구글링을 해보니 원래는 MessageDigestPasswordEncoder나 StandardPasswordEncoder를 통해 SHA-256 암호화를 처리할 수 있다고 했으나….확인해보니 현재 내가 사용하고는 Spring Security 버전(6.4.7)에서는 모두 deprecated되어 있다…
이것저것 클래스를 뒤져본 결과 AbstractPasswordEncoder라는 추상 클래스를 통해 쉽게 PasswordEncoder를 구현할 수 있다는 것을 알아내었고 바로 구현해보았다.
해당 추상 클래스는 아래와 같이 구성되어 있었다.
AbstractPasswordEncoder.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public abstract class AbstractPasswordEncoder implements PasswordEncoder {
private final BytesKeyGenerator saltGenerator = KeyGenerators.secureRandom();
protected AbstractPasswordEncoder() {
}
public String encode(CharSequence rawPassword) {
// Salt 값 랜덤 생성
byte[] salt = this.saltGenerator.generateKey();
// 평문 암호와 Salt 값을 통해 인코딩 및 병합 처리
byte[] encoded = this.encodeAndConcatenate(rawPassword, salt);
// 인코딩된 byte 배열을 16 진수로 인코딩한 후 문자열로 변환하여 반환
return String.valueOf(Hex.encode(encoded));
}
public boolean matches(CharSequence rawPassword, String encodedPassword) {
// 16 진수로 인코딩되어 있는 암호화된 문자 byte 배열로 디코딩
byte[] digested = Hex.decode(encodedPassword);
// 맨 앞 글자(0번 인덱스)부터 Salt의 길이만큼 digested 값에서 짤라냄
byte[] salt = EncodingUtils.subArray(digested, 0, this.saltGenerator.getKeyLength());
// 평문 비밀번호와 Salt 값으로 다시 인코딩한 결과를 digested 값이랑 비교
return matches(digested, this.encodeAndConcatenate(rawPassword, salt));
}
protected abstract byte[] encode(CharSequence rawPassword, byte[] salt);
protected byte[] encodeAndConcatenate(CharSequence rawPassword, byte[] salt) {
// Salt 값과 구현한 추상 메서드를 통해 byte 배열을 얻은 후 concatenate 메서드로 두 byte 배열을 병함
return EncodingUtils.concatenate(new byte[][]{salt, this.encode(rawPassword, salt)});
}
protected static boolean matches(byte[] expected, byte[] actual) {
return MessageDigest.isEqual(expected, actual);
}
}
추상 메서드로 선언된 encode 메서드를 아래와 같이 구현하여 SHA-256 알고리즘을 처리하였다.
Sha256PasswordEncoder.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Slf4j
public class Sha256PasswordEncoder extends AbstractPasswordEncoder {
private static final String ALGORITHM = "SHA-256";
@Override
protected byte[] encode(CharSequence rawPassword, byte[] salt) {
try{
// SHA-256 계산을 위해 MessageDigest 인스턴스를 가져옴
MessageDigest digest = MessageDigest.getInstance(ALGORITHM);
// 평문과 Salt 값을 합침
digest.update(Utf8.encode(rawPassword));
digest.update(salt);
// 해시 계산 후 결과 반환
return digest.digest();
} catch (NoSuchAlgorithmException e) {
log.error(ALGORITHM + " algorithm not found.");
throw new RuntimeException(ALGORITHM + " algorithm not available.", e);
}
}
}
This post is licensed under CC BY 4.0 by the author.