study/spring-security-in-action

4장 암호 처리

fd27 2023. 9. 2. 21:51

이 블로그 내용은 아래 도서를 학습한 내용입니다.
학습 도서 정보
스프링 시큐리티 인 액션(Spring Security in Action)
로렌티우 스필카 지음
최민석 옮김
위키북스


 

  • PasswordEncoder의 구현 및 이용
  • 스프링 시큐리티 암호화 모듈에 있는 툴 이용

 

4.1 PasswordEncoder 계약의 이해

4.1.1 PasswordEncoder 계약의 정의

public interface PasswordEncoder {
    String encode(CharSequence var1);

    boolean matches(CharSequence var1, String var2);

    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

 
encode: 암호의 해시를 제공하거나 암호화를 수행하는 일을 한다.
matches: 인코딩된 문자열이 원시 암호화 일치하는지 확인한다.
upgradeEncoding: true를 반환하도록 재정의 하면 인코딩된 암호를 보안 향상을 위해 다시 인코딩 한다.

4.1.2 PasswordEncoder 계약의 구현

PasswordEncoder의 가장 단순한 구현

public class PlainTextPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence rawPassword) {
        return rawPassword.toString();
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return rawPassword.equals(encodedPassword);
    }
}

SHA-512를 이용ㅇ하는 PasswordEncoder 구현

public class Sha512PasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence rawPassword) {
        return hashWithSHA512(rawPassword.toString());
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(encode(rawPassword));
    }

    private String hashWithSHA512(String input) {
        StringBuilder result = new StringBuilder();
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-512");
            byte [] digested = md.digest(input.getBytes());
            for (byte b : digested) {
                result.append(Integer.toHexString(0xFF & b));
            }
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Bad algorithm");
        }
        return result.toString();
    }
}

 

  • NoOpPasswordEncoder: 암호를 인코딩하지 않고 일반 텍스트로 유지한다.
  • StandardPasswordEncoder: SHA-256을 이용해 암호를 해시한다. 이 구현은 이제 구식이며 새 구현에는 쓰지 말아야 한다.
  • Pbkdf2PasswordEncoder: PBKDF2를 이용한다
  • BCryptPasswordEncoder: bycrypt 강력 해싱 함수로 암호를 인코딩 한다.
  • SCryptPasswordENcoder: scrypt 해싱 함수로 암호를 인코딩 한다.

 

4.1.3 PasswordEncoder의 제공된 구현 선택

NoOpPasswordEncoder

PasswordEncoder p = NoOpPasswordEncoder.getInstance();

NoOpPasswordEncoder 클러스는 싱글톤으로 설계돼서 클래스 바깥에서는 생성자를 직접 호출할 수 없지만, getInstance() 메서드로 클래스의 인스턴스를 얻을 수 있다.
 

StandardPasswordEncoder

PasswordEncoder p = new StandardPasswordEncoder();
PasswordEncoder p = new StandardPasswordEncoder("secret");

비밀값은 생성자의 매개변수로 전달한다.
StandardPasswordEncoder는 이제 구식이므로 쓰지않는게 좋다.
 

Pbkdf2PasswordEncoder

PasswordEncoder p = new Pbkdf2PasswordEncoder();
PasswordEncoder p = new Pbkdf2PasswordEncoder("secret");
PasswordEncoder p = new Pbkdf2PasswordEncoder("secret", 185000, 256);

Params

  • 프로세스에 이용되는 키의 값
  • 암호 인코딩의 반복 횟수
  • 해시의 크기

두번째와 세번째 값을 늘릴수록 암호는 더 강력해진다. 그러나 성능에 영향을 주므로 신중하게 절충해야한다.
 

BCryptPasswordEncoder

PasswordEncoder p = new BCryptPasswordEncoder();
PasswordEncoder p = new BCryptPasswordEncoder(4);

SecureRandom s = SecureRandom.getInstanceStrong();
PasswordEncoder p = new BCryptPasswordEncoder(4, s);

Params

  • 로그라운드: 해싱 작업이 이용하는 반복 횟수에 영향을 준다. 반복 횟수는 2 로그라운드로 계산된다. 반복 횟수를 계산하기 위한 로그라운드 값은 4~31 사이어야 한다.
  • SecureRandom: 인코딩에 이용되는 인스턴스

반복을 결정하는 값과, 해시에 사용되는 인스턴스를 지정해준다는 말인듯
인수가 없는 인스턴스로 만들어서 사용해도 된다.
 

SCryptPasswordEncoder

PasswordEncoder p = new SCryptPasswordEncoder();
PasswordEncoder p = new SCryptPasswordEncoder(16384, 8, 1, 32, 64);

Params

  • CPU 비용
  • 메모리 비용
  • 병렬화 계수
  • 키 길이
  • 솔트 길이

 

4.1.4 DelegatingPasswordEncoder를 이용한 여러 인코딩 전략

DelegatingPasswordEncoder는 여러종류의 해시를 지원한다.
현재 사용되는 알고리즘에서 취약성이 발견되어 신규 등록 사용자의 자격 증명을 변경하고 싶지만, 기존 자격 증명을 변경하기 쉽지 않을때 사용할 수 있다.
 
DelegatingPasswordEncoder는 PasswordEncoder 인터페이스의 한 구현이며 자체 인코딩 알고리즘을 구현하는 대신 같은 계약의 다른 구현 인스턴스에 작업을 위임한다.
해시는 해당 해시를 의미하는 알고리즘의 이름을 나타내는 접두사로 시작한다. DelegatingPasswordEncoder는 암호의 접두사를 기준으로 올바른 PasswordEncoder 구현에 작업을 위임한다.

@Configuration
public class ProjectConfig {

    @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);
    }
}

{noop}12345 => NoOpPasswordEncoder 사용
{bcrypt}$2a$10... => BCryptPasswordEncoder 사용
 
스프링 시큐리티는 편의를 위해 모든 표준 제공 PasswordEncoder의 구현에 대한 맵을 가진 DelegatingPasswordEncoder를 생성하는 방법을 제공한다.

PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

기본 인코더는 bcrypt이다.
 

인코딩, 암호화, 해싱

> 인코딩(Encoding)
주어진 입력에 대한 모든 변환을 의미한다. 예를 들어 문자열을 뒤집는 함수 x가 있을 때 x -> y를 ABCD에 적용하면 DCBA가 나온다.
 
> 암호화(Encryption)
출력을 얻기 위해 입력 값과 키를 모두 지정하는 특정한 유형의 인코딩이다. 키를 이용하면 나중에 누가 출력에서 입력을 얻는 함수를 호출할 수 있는지 선택할 수 있다. 암호화를 함수로 나타낸 가장 간단한 형식은 다음과 같다.
(x, y) -> y
여기에서 x는 입력이고 k는 키며 y는 암호화 결과다. 이 방식으로 알려진 함수에 키를 이용해 출력에서 입력을 얻을 수 있다.
(y, k) -> x
이를 역함수 복호화(Reverse Function Decryption)라고 한다. 암호화에 쓰는 키와 복호화에 쓰는 키가 같으면 이를 일반적으로 이를 대칭 키 라고 한다.
 
암호화((x, k1) -> y) 및 복호화 ((y, k2) -> x)에 다른 키를 쓰면 비대칭 키(Asymmetric Key)로 암호화가 수행된다고 말한다.
그리고 (k1, k2)를 키 쌍(Key Pair)이라고 한다. 암호화에 이용되는 k1을 공개 키(Public key)라고 하고 복호화에 이용되는 k2를 개인 키(Private Key)라고 한다. 이와 같이 개인 키의 소유자는 데이터를 복호화할 수 있다.
 
>  해싱(Hashing)
함수가 한 방향으로만 작동하는 특정한 유형의 인코딩이다. 따라서 해싱 함수의 출력 y에서 입력 x를 얻을 수 없다. 그러나 출력 y가 입력 x에 해당하는지 확인할 수 있는 방법이 반드시 있어야 하므로 해시는 인코딩과 일치를 위한 한 쌍의 함수로 볼 수 있다.
해싱 함수가 x -> y라면 일치 함수 (x, y) -> boolean도 있다.
때때로 해싱 함수는 입력에 임의의 값을 추가할 수도 있다: (x, k) -> y.
이 값을 솔트(Salt)라고 한다.
솔트는 함수를 더 강하게 만들어 결과에서 입력을 얻는 역함수의 적용 난도를 높인다.
 
 
스프링 시큐리티에서 인증 흐름을 위한 주 계약을 나타내는 인터페이스

  • UserDetails
    • 스프링 시큐리티가 관리하는 사용자를 나타낸다.
  • GrantedAuthority
    • 애플리케이션의 목적 내에서 사용자에게 허용되는 작업을 정의한다(예: 읽기, 쓰기, 삭제 등).
  • UserDetailsService
    • 사용자 이름으로 사용자 세부 정보를 검색하는 객체를 나타낸다.
  • UserDetailsManager
    • UserDetailsService의 더 구체적인 계약이다. 사용자 이름으로 사용자를 검색하는 것 외에도 사용자 컬렉션이나 특정 사용자를 변경할 수도 있다.
  • PasswordEncoder
    • 암호를 암호화 또는 해시하는 방법과 주어진 인코딩된 문자열을 일반 텍스트 암호와 비교하는 방법을 지정한다.

 

4.2 스프링 시큐리티 암호화 모듈에 관한 추가 정보

4.2.1 키 생성기 이용

4.2.2 암호화 복호화 작업에 암호기 이용

 

요약

  • PasswordEncoder는 인증 논리에서 암호를 처리하는 가장 중요한 책임을 담당한다.
  • 스프링 시큐리티는 해싱 알고리즘에 여러 대안을 제공하므로 필요한 구현을 선택하기만 하면 된다.
  • 스프링 시큐리티 암호화 모듈(SSCM)에는 키 생성기와 암호기를 구현하는 여러 대안이 있다.
  • 키 생성기는 암호화 알고리즘에 이용되는 키를 생성하도록 도와주는 유틸리티 객체다.
  • 암호기는 데이터 암호화와 복호화를 수행하도록 도와주는 유틸리티 객체다.