[Spring Security] PasswordEncoder

컴공생의 코딩 일기·2023년 3월 18일
0

스프링 시큐리티

목록 보기
2/2
post-thumbnail

PasswordEncoder

Spring Security에서는 비밀번호를 안전하게 저장할 수 있도록 비밀번호의 단방향 암호화를 지원하는 PasswordEncoder 인터페이스와 구현체들을 제공한다.

public interface PasswordEncoder {

	// 비밀번호를 암호화(단방향)
	String encode(CharSequence rawPassword);
	
    // 암호화된 비밀번호와 암호화되지 않은 비밀번호와 일치하는지 비교
	boolean matches(CharSequence rawPassword, String encodedPassword);
	
    // 인코딩된 암호화를 다시 한번 인코딩 할 때 사용 (true 일 경우 다시 인코딩, 기본값 -> false 
	default boolean upgradeEncoding(String encodedPassword) {
		return false;
	}

}

PasswordEncoder의 제공된 구현 클래스

  • NoOpPasswordEncoder : 암호를 인코딩하지 않고 일반 텍스트로 유지(절대 사용하면 안된다. 테스트 용도로만 사용)
  • StandardPasswordEncoder : SHA-256을 이용해 암호를 해시한다. (강도가 약한 해싱 알고리즘이기 때문에 지금은 많이 사용되지 않는다.)
  • Pbkdf2PasswordEncoder : PBKDF2를 이용한다.
  • BCryptPasswordEncoder : bcrypt 강력 해싱 함수로 암호를 인코딩한다.
  • SCryptPasswordEncoder : scrypt 해싱 함수로 암호를 인코딩한다.

DelegatingPasswordEncoder

DelegatingPasswordEncoder는 여러 인코딩 알고리즘을 사용할 수 있게 해주는 기능이다. 예를 들어 특정 애플리케이션 버전부터 인코딩 알고리즘이 변경된 경우가 있다. 현재 사용되는 알고리즘에서 취약성이 발견되어 다른 인코딩 알고리즘으로 변경하고자 할 때 사용하기 좋다.

  • DelegatingPasswordEncoderPasswordEncoder 인터페이스의 한 구현이며 자체 인코딩 알고리즘을 구현하는 대신 같은 계약의 다른 구현 인스턴스에 작업을 위임한다.
  • DelegatingPasswordEncoder는 암호의 prefix를 기준으로 올바른 PasswordEncoder 구현에 작업을 위임한다.
  • DelegatingPasswordEncoder는 각 인스턴스를 맵에 저장한다.
  • NoOpPassowrd Encoder{noop}가 할당되고 BcryptPasswordEncoder{bcrypt}가 할당된다.
@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(16384, 8, 1, 32, 64));
		// BCryptPasswordEncoder를 사용
        return new DelegatingPasswordEncoder("bcrypt", encoders);
    }
}

DelegatingPasswordEncoder는 접두사를 기준으로 암호를 비교한다. 접두사가 없으면 DelegatingPasswordEncoder는 기본 인코더를 이용하며 기본 PasswordEncoderDelegatingPasswordEncoder 인스턴스를 만들 때 첫 번째 매개변수로 지정한다.

스프링 시큐리티는 편의를 위해 모든 표준 제공 PasswordEncoder의 구현에 대한 맵을 가진 DelegatingPasswordEncoder를 생성하는 방법을 제공한다. PasswordEncoderFactories 클래스에는 bcrypt가 기본 인코더인 DelegatingPasswordEncoder의 구현을 반환하는 정적 메서드 createDelegatingPasswordEncoder()가 있다 구현 방법은 아래와 같다.

PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

암호화 모듈(SSCM)이 제공하는 다른 옵션

  • 키 생성기 - 해싱 및 알고리즘을 위한 키 생성하는 객체
  • 암호기 - 데이터를 암호화 및 복호화하는 객체

키 생성기

키 생성기는 특정한 종류의 키를 생성하는 객체로서 일반적으로 암호화나 해싱 알고리즘에 필요하다.

  • StringKeyGenerator
    • 문자열 키 생성기를 이용해 문자열을 얻을 수 있다.
    • 일반적으로 이 키는 해싱 또는 알고리즘의 솔트 값으로 이용된다.
 public interface StringKeyGenerator {
    // 키 값을 나타내는 문자열 하나를 반환하는 generateKey() 메서드 하나만 있다.
	String generateKey();
}

// 솔트 값을 가져오는 방법
// 8바이트 키를 생성하고 이를 16진수 문자열로 인코딩하며 메서드는 이러한 작업의 결과를 문자열로 반환한다.
// KeyGenerators 다양한 키 generator를 만들 수 있는 여러가지 편리한 팩토리 메서드를 제공한다.
StringKeyGenerator keyGenerator = KeyGenerators.string();
String salt = keyGenerator.generateKey();
  • BytesKeyGenerator : 바이트 키 생성기를 이용해 바이트를 얻을 수 있다.
public interface BytesKeyGenerator {
	// 키 길이(바이트 수)를 반환하는 메서드
    // 기본 ByteKeyGenerator는 8바이트 길이의 키를 생성
	int getKeyLength();
    // byte[] 키를 반환하는 generateKey() 메서드
	byte[] generateKey();
}

// 다른 키 길이를 길이를 지정하려면 키 생성기 인스턴스를 얻을 때 KeyGenerators.secureRandom() 메서드에 원하는 값을 전달하면 된다.
// KeyGenerators.secureRandom() 메서드는 생성한 BytesKeyGenerator는 generateKey() 메서드가 호출될 때마다 고유한 키를 생성한다.
BytesKeyGenerator keyGenerator = KeyGenerators.secureRandom(16);

같은 키 생성기를 호출하면 같은 키 값을 반환하는 구현이 적합 할 때가 있다. 이때는 KeyGenerators.shared(int length) 메서드로 BytesKeyGenerator를 생성할 수 있다.

BytesKeyGenerator keyGenerator = KeyGenerators.shared(16);
byte[] key1 = keyGenerator.generateKey();
byte[] key2 = keyGenerator.generateKey();

암호기

  • 암호기는 암호화 알고리즘을 구현하는 객체이다.
  • 시스템의 구성 요소간에 데이터를 전송하거나 데이터를 저장할 때 암호화가 필요하다.
  • 암호기는 암호화와 복호화 작업을 지원하며 SSCM에는 이를 위해 BytesEncryptorTextEncryptor라는 두 유형이 암호기가 정의돼 있다.
  • Encryptor 클래스는 대칭 encryptor를 생성하는 팩토리 메소드를 제공한다.

TextEncryptor

// 데이터를 문자열로 관리
public interface TextEncryptor {
	// 데이터를 받는 메서드
	String encrypt(String text);
    // 데이터를 출력하는 메서드
	String decrypt(String encryptedText);
}

TextEncryptor는 세가지 주요 기능이 있다.

  • Encryptor.text() : Encryptors.standard() 메서드로 암호화 작업을 관리
  • Encryptor.delux() : Encryptors.stronger() 메서드로 암호화 작업을 관리
  • Encryptor.queryableText() : 순차 암호화 작업에서 입력이 같으면 같은 출력을 생성하게 해주는 메서드
String salt = KeyGenerators.string().generateKey();
String password = "secret";
tring valueToEncrypt = "HELLO";
   
// 솔트와 암호를 이용하는 TextEncryptor 객체 생성
TextEncryptor e = Encryptors.text(password, salt);
String encrypted = e.encrypt(valueToEncrypt);
String decrypted = e.decrypt(encrypted);
-----------------------------------------------------------------------------
TextEncryptor e = Encryptors.queryableText(password, salt);
String encrypted = e.encrypt(valueToEncrypt);
String decrypted = e.decrypt(encrypted);

BytesEncryptor

// 데이터를 다이트로 관리
public interface BytesEncryptor {
	// 데이터를 받는 메서드
	byte[] encrypt(byte[] byteArray);
    // 데이터를 출력하는 메서드
	byte[] decrypt(byte[] encryptedByteArray);
}

BytesEncryptorEncryptors.standard() 또는 Encryptor.stronger() 메서드를 사용할 수 있다.

String salt = KeyGenerators.string().generateKey();
String password = "secret";
String valueToEncrypt = "HELLO";
    
BytesEncryptor e = Encryptors.standard(password, salt);
byte[] encrypted = e.encrypt(valueToEncrypt.getBytes());
byte[] decrypted = e.decrypt(encrypted);

내부적으로 표준 바이트 암호기는 256바이트 AES 암호화를 이용해 입력을 암호화한다. 더 강력한 암호기 인스턴스를 만들려면 Encryptors.stronger() 메서드를 호출하면 된다.

BytesEncryptor e = Encryptors.stronger(password, salt);
profile
더 좋은 개발자가 되기위한 과정

0개의 댓글