[Spring Security] (3) PasswordEncoder

Park Yeongseo·2023년 9월 9일
0

Spring Security

목록 보기
3/13
post-thumbnail

1. PasswordEncoder 계약의 정의

스프링 시큐리티에 사용자 암호를 검증하는 방법을 알려준다. PasswordEncoder는 인증 프로세스에서 암호가 유효한지를 확인한다. 모든 시스템은 인코딩된 암호를 저장하며 아무도 읽을 수 없게 해시를 저장하는 게 좋다. PasswordEncoder도 암호를 인코딩할 수 있다.

public interface PasswordEncoder {
	
	String encode(CharSequence rawPassword);
	boolean matches(CharSequence rawPassword, String encodedPassword);
	
	default boolean upgradeEncoding(String encodedPassword) {
		return false;
		//true를 반환하도록 메서드를 재정의하면 인코딩 암호를 보안 향상을 위해 재인코딩한다.
	}
}

2. PasswordEncoder의 제공된 구현 선택

  • NoOpPasswordEncoder 암호를 인코딩하지 않고 일반 텍스트로 유지
  • StandardPasswordEncoder SHA256을 이용해 해시. 구식이므로 새 구현에는 쓰지 말 것.
  • Pbkdf2passwordEncoder PBKDF2를 이용
  • BCryptPasswordEncoder bcrypt 강력 해싱 함수로 암호를 인코딩
  • SCryptPasswordEncoder scrypt 해싱함수로 암호를 인코딩.

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

암호 일치를 위해 다양한 구현을 적용해야 하는 경우다. 인코딩 알고리즘이 변경되는 경우, 여러 종류의 해시를 지원해야 하게 되는데, DelegatingPasswordEncoder 객체를 이용할 수 있다.

PasswordEncoder의 한 구현으로, 자체 인코딩 알고리즘은 없고 같은 계약의 다른 구현 인스턴스에 작업을 위임한다. 해시는 해당 해시를 의미하는 알고리즘의 이름을 나타내는 접두사로 시작한다.

e.g.) 암호에 {noop} 접두사가 붙으면 NoOpPasswordEncoder에 구현을 위임함.

DelegatingPasswordEncoder의 정의 방법

//config

@Bean
public PasswordEncoder passwordEncoder(){
	Map<String, PasswordEncoder> encoders = new HashMap<>();

	encoders.put("noop", NoOpPasswordEncoder.getInstance());
	encoders.put("bcrypt", new BCryptPasswordEncoder());
	
	//...

	return new DelegatingPasswordEncoder("bcrypt", encoders); //default 지정
}

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

PasswordEncoder passwordEncoder = PasswordEncoderFactories
								.createDelegatingPasswordEncoder();

4. 스프링 시큐리티 암호화 모듈(SSCM)

암호화 및 복호화 함수와 키 생성 기능은 자바 언어에서 기본 제공되지 않으므로 개발자가 이런 기능에 보다 쉽게 접근하기 위한 종속성을 추가할 때 제약이 있다.

스프링 시큐리티는 별도의 라이브러리를 이용할 필요 없이 프로젝트의 종속성을 줄일 수 있는 자체 솔루션을 제공한다. 암호 인코더도 SSCM의 일부분이다.

키 생성기

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

BytesKeyGeneratorStringKeyGenerator는 키 생성기의 두 가지 주요 유형을 나타내는 인터페이스로, 팩터리 클래스 KeyGenerators로 직접 만들 수 있다.

public interface StringKeyGenerator {
	String generateKey();
}

//------------------------------------------------------

StringKeyGenerator keyGenerator = KeyGenerators.string();
String salt = keyGenerator.generateKey();
//8바이트 키를 생성하고 16진수 문자열로 인코딩하고, 메서드는 이 작업의 결과를 문자열로 반환
public interface BytesKeyGenerator{
	int getKeyLength();
	byte[] generateKey();
}

//------------------------------------------------------

BytesKeyGenerator keyGenerator = KeyGenerators.secureRandom();
byte [] key= keyGenerator.generateKey();
int keyLength = keyGenerator.getKeyLength();

기본 BytesKeyGenerator는 8바이트 길이의 키를 생성한다. 다른 키 길이를 지정하려면 키 생성기 인스턴스를 얻을 때 KeyGenerators.secureRandom() 메서드에 원하는 값을 전달하면 된다.

secureRandom() 메서드는 매번 고유한 키를 생성하지만, 같은 키 생성기를 호출하면 같은 키 값을 반환하는 구현이 적합한 경우도 있다. 이 경우는 KeyGenerators.shared(int length) 메서드로 생성하면 된다.

BytesKeyGenerator keyGenerator = KeyGenerators.secureRandom(16);

//------------------------------------------------------

BytesKeyGenerator keyGenerator = KeyGenerators.shared(16);
byte [] key1 = keyGenerator.generateKey();
byte [] key2 = keyGenerator.generateKey();
//key1, key2는 동일한 값을 가짐.

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

암호기는 암호화와 복호화 작업을 지원하며 SSCM에는 이를 위해 BytesEncryptorTextEncryptor의 두 유형의 암호기가 정의되어 있다.

팩터리 클래스 Encryptors는 여러 가능성을 제공하는데, BytesEncryptorEncryptors.standard() 또는 Encryptors.stronger() 메서드를 이용하면 된다. 내부적으로 표준 바이트 암호기는 256바이트 AES 암호화를 이용하고, 더 강력한 암호기 인스턴스는 Encryptors.stronger()를 호출하면 된다. 이는 256바이트 AES 암호화 작업 모드로 GCM을 이용한다. (표준모드는 CBC)

BytesEncryptor e = Encryptors.stronger(password, salt)

TextEncryptor에는 세 주요 형식이 있는데, Encryptors.text(), Encryptors.delux(), Encryptors.queryableText() 메서드들이다. 이외에도 값을 암호화하지 않는 더미 TextEncryptor를 반환하는 메서드도 있다(Encryptors.noOpText())

Encryptors.text()Encryptors.delux()의 경우 같은 메서드를 반복 호출해도 다른 출력이 반환되는데, 그 이유는 암호화 프로세스에 임의의 초기화 벡터가 생성되기 때문이다.

실제 상황에서는 이러한 작동 방식을 원하지 않을 수 있다. 이러한 유형의 입력을 쿼리 가능 텍스트라하는데, 이 암호기는 순차 암호화 작업에서 입력이 같으면 같은 출력을 생성하도록 한다.

0개의 댓글