[Spring Security] 암호 처리

황승수 (Seungsu)·2023년 4월 10일
1

Spring Security

목록 보기
3/3

인코딩, 암호화, 해싱

인코딩(Encoding)

주어진 입력에 대한 모든 변환을 의미한다.

암호화(Encryption)

출력을 얻기 위해 입력 값과 키를 모두 지정하는 특정한 유형의 인코딩이다.
(x, k1) -> y로 나타낼 수 있으며, 복호화는 (y, k2) -> x로 나타낸다.
암호화와 복호화 과정에서 사용된 키가 같으면 대칭키(k1=k2), 다르면 비대칭키라고 한다.
비대칭 키 경우 (k1, k2)를 키 쌍이라고 하고, k1을 공개키, k2를 개인키라고 한다.
개인키의 소유자만이 데이터를 복호화할 수 있다.

해싱(Hashing)

함수가 한 방향으로만 작동하는 특정한 유형의 인코딩이다.
해싱 함수의 출력 y에서 입력 x를 얻을 수 없기 때문에 출력 y가 입력 x에 해당하는지 확인할 수 있는 방법이 반드시 존재해야 한다.
즉 해싱 함수가 x -> y라면 일치 함수 (x, y) -> boolean도 존재해야 한다.

해싱 함수의 입력에 임의의 값을 추가할 수 있다. (x, k) -> y에서 임의의 값 k를 솔트(Salt)라고 하고, 솔트는 해싱 함수를 더 강하게 만들어 결과에서 입력을 얻는 역함수의 적용 난도를 높인다.


PasswordEncoder 계약

PasswordEncoder는 인증 프로세스에서 AuthenticationProvider의 요청을 위임받아 사용자의 암호를 검증한다. 이 인터페이스는 두 개의 추상 메서드와 한 개의 기본 구현이 있는 메서드를 정의한다.

public interface PasswordEncoder {
	String encode(CharSequence rawPassword);
    String matches(CharSequence rawPassword, CharSequence encodedPassword);
    
    default boolean upgradeEncoding(String encodedPassword) {
    	return false;
    }
}
  • encode() : 주어진 문자열을 변환해 반환
  • matches() : 원시 암호와 인코딩된 문자열의 일치 여부 반환
  • upgradeEncoding() : true 설정 시 인코딩된 암호를 한번 더 인코딩

PasswordEncoder 계약을 구현할 때, encode() 메서드로 변환된 문자열은 항상 matches() 메서드로 검증될 수 있도록 구현해야 한다.


PasswordEncoder 구현

다음은 스프링 시큐리티에서 기본 제공하는 PasswordEncoder 구현 옵션이다.

  • NoOpPasswordEncoder : 암호를 인코딩하지 않고 일반 텍스트로 유지
  • StandardPasswordEncoder : SHA-256 해싱 (강도가 약한 해싱 알고리즘)
  • Pbkdf2PasswordEncoder : PBKDF2를 이용
  • BCryptPasswordEncoder : bcrypt 강력 해싱 함수로 인코딩
  • SCryptPasswordEncoder : scrypt 해싱 함수로 인코딩

이 중에서 널리 쓰이는 BCryptPasswordEncoder를 생성하는 방식은 다음과 같다.

// 기본 인스턴스 생성
PasswordEncoder p = new BCryptPasswordEncoder();

// 강도 계수를 지정하여 인스턴스 생성
// 지정된 로그 라운드 값은 해싱 작업이 이용하는 반복 횟수에 영향을 준다. 
PasswordEncoder p = new BCryptPasswordEncoder(4);

// SecureRandom 인스턴스를 변경하여 생성
SecureRandom s = SecureRandom.getInstanceStrong();
PasswordEncoder p = new BCryptPasswordEncoder(4, s);

DelegatingPasswordEncoder


만약 현재 사용하는 알고리즘에서 취약성이 발견되어 신규 등록 사용자부터 자격 증명을 변경하려고 한다. 이때 기존 사용자의 자격 증명을 변경하기가 쉽지 않은 상황이라면 애플리케이션에서는 필히 다양한 인코딩 옵션을 지원해야 한다.

이처럼 일부 애플리케이션에서는 특정 상황에서 다양한 암호 인코더를 갖추고 조건에 따라 선택하는 방식이 필요한 경우가 있다.

DelegatingPasswordEncoder는 PasswordEncoder 인터페이스의 한 구현이며 자체적으로 인코딩 알고리즘을 구현하지 않고 접두사를 기준으로 같은 계약의 다른 인스턴스에게 작업을 위임한다. DelegatingPasswordEncoder의 기본 구현은 아래와 비슷하다.

@Configuration
public class SecurityConfig {
	// ...
    
    @Bean
    public PasswordEncoder passwordEncoder() {
    	Map<String, PasswordEncoder> encoders = new HashMap<>();
        
        encoders.put("noop", NoOpPasswordEncoder.getInstance());
        encoders.put("bcrypt", new BCryptPasswordEncoder());
        encoders.put("scrypt", new SCryptPasswordEncoder());
        
        return new DelegatingPasswordEncoder("bcrypt", encoders);
    }
}

DelegatingPasswordEncoder는 접두사가 없으면 생성자의 첫번째 매개변수로 지정된 기본 인코더를 이용한다.

스프링 시큐리티는 편의를 위해 표준 제공 PasswordEncoder의 구현에 대한 맵을 가진 DelegatingPasswordEncoder를 생성하는 방법을 제공한다.

PasswordEncoder pe = PasswordEncoderFactories.createDelegatingPasswordEncoder();

출처

도서: Spring Security in Action

0개의 댓글