🙋 들어가며
프로젝트를 진행할 때 보안에 예민해서 노출시키지 않는 값들이 있습니다.
여러 방법으로 노출시키지 않는 방법이 있지만 그중에 Jasypt라는 라이브러리가 존재합니다.
오늘은 어떻게 사용하는지 가볍게 알아보겠습니다. 😋
🔐 Jasypt란?
Jasypt는 Java Simplified Encryption의 약자입니다.
공식 문서에 따르면 다음과 같이 설명되어 있습니다.
Java library which allows the developer to add basic encryption capabilities to his/her projects with minimum effort, and without the need of having deep knowledge on how cryptography works.
개발자가 암호학에 대한 지식이 없어도 기본적인 암호화 기능을 사용할 수 있게 하는 라이브러리입니다.
오래된 라이브러리이지만 설명 그대로 쉽게 사용할 수 있어 지금도 많이 사용합니다.
😎 의존성 추가하기
우선 라이브러리에 대한 의존성을 추가해야 합니다.
// gradle
implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.4'
starter가 붙어서 많은 의존성이 추가될 것 같지만 작고 귀엽습니다. 😋
👻 기본 사용법
Jasypt는 다양한 암호화 툴을 제공합니다.
String
을 포함한 Decimal
, Integer
, Byte
이렇게 총 4가지를 지원합니다.
우리는 yml 파일에 작성되는 값들을 숨겨야하기 때문에 문자열 암호화 도구를 사용해 보겠습니다.
이름 그대로 가장 기본적인 암호화 도구로 테스트를 작성해 보았습니다.
@Test
void encryptString_Success_EqualsWithDecrypted() {
// given
String password = "password";
String target = "hejow";
PBEStringCleanablePasswordEncryptor encryptor = initEncryptor(password);
// when
String encrypted = encryptor.encrypt(target);
// then
assertEquals(target, encryptor.decrypt(encrypted)); // true
}
private PBEStringCleanablePasswordEncryptor initEncryptor(String password) {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword(password);
encryptor.setAlgorithm("PBEWithMD5AndDES");
return encryptor;
}
여기까지 테스트는 이상이 없지만 생성되는 값이 항상 똑같지 않습니다.
따라서 다음 테스트가 정상적으로 실행됩니다.
@Test
void isEncryptedAlwaysSame_Fail_ByDifferentSalt() {
// given
String target = "john";
PBEStringCleanablePasswordEncryptor encryptor = initEncryptor(password);
// when
String encrypted = encryptor.encrypt(target);
// then
assertAll(
() -> assertNotEquals(encrypted, encryptor.encrypt(target)),
() -> assertNotEquals(encrypted, encryptor.encrypt(target)),
() -> assertNotEquals(encrypted, encryptor.encrypt(target))
);
}
🧂 Salt?
앞선 테스트의 이름을 유심히 보면 다른 Salt 때문에 실패했다고 명시했습니다.
여기서 Salt란, 암호학에서 해시 처리할 때 사용되는 추가적인 랜덤한 값을 의미합니다.
Salt를 생성하는 generator를 encryptor에 넣어주면 항상 같은 값을 생성하게 됩니다.
@Test
void sameEncrypted_Success_WithFixedSaltGenerator() {
// given
String target = "moon";
String salt = "fixedSalt";
StandardPBEStringEncryptor encryptor = initEncryptor(password);
encryptor.setSaltGenerator(new StringFixedSaltGenerator(salt));
// when
String encrypted = encryptor.encrypt(target);
// then
assertAll(
() -> assertEquals(encrypted, encryptor.encrypt(target)),
() -> assertEquals(encrypted, encryptor.encrypt(target)),
() -> assertEquals(encrypted, encryptor.encrypt(target))
);
}
🙈 Property 암호화하기
여기까지 인트로였고, 이제 원래 목적에 맞게 사용하는 법을 안내드리겠습니다.
🍥 Configuration 작성
다음은 많은 블로그에서 작성된 코드입니다.
개인적으로 “개발자가 작성하는 코드의 의미를 꼭 알아야 한다”라고 생각합니다. (근데 많은 블로그는 안 그런 것 같습니다.)
각 옵션에 대해서 살펴보면서 어떤 점이 잘못되었는지 살펴보겠습니다.
@Configuration
@EnableEncryptableProperties
public class JasyptConfig {
@Value("${jasypt.encryptor.password}")
private String password;
@Bean("jasyptStringEncryptor")
public StringEncryptor stringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(password);
config.setAlgorithm("PBEWithMD5AndDES");
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setIvGeneratorClassName("org.jasypt.iv.NoIvGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
return encryptor;
}
}
🗝️ PooledPBEStringEncryptor
Jasypt가 제공하는 Encryptor는 StandardPBE
타입과 PooledPBE
타입이 존재합니다.
두 타입이 제공하는 기능은 정확하게 동일하나 딱 한 가지 차이점이 존재합니다.
다음은 PooledPBEStringEncryptor
의 Javadoc입니다.
PooledPBE
은 StandardPBE
을 PoolSize만큼 가지고 있다가 요청이 오면 라운드-로빈 방식으로 처리합니다.
쉽게 설명하면 커넥션을 미리 생성해 둔 커넥션 풀과 같은 구조입니다.
🔑 StandardPBEStringEncryptor
이 Encrytor를 생성하면 다음과 같이 초기화됩니다.
public StandardPBEStringEncryptor() {
super();
this.byteEncryptor = new StandardPBEByteEncryptor();
this.base64 = new Base64();
}
기본적으로 Base64 인코딩을 사용한다는 것을 알 수 있고 byteEncrytor
도 살펴보겠습니다.
StandardPBEByteEncryptor
는 다음과 같이 초기화됩니다.
public StandardPBEByteEncryptor() {
super();
}
내부적으로 어떤 값 초기화하지는 않지만 필드를 살펴보면 이미 초기값들이 선언되어 있습니다.
public static final String DEFAULT_ALGORITHM = "PBEWithMD5AndDES";
public static final int DEFAULT_KEY_OBTENTION_ITERATIONS = 1000;
public static final int DEFAULT_SALT_SIZE_BYTES = 8;
public static final int DEFAULT_IV_SIZE_BYTES = 16;
private String algorithm = DEFAULT_ALGORITHM;
private String providerName = null;
private Provider provider = null;
private char[] password = null;
private int keyObtentionIterations = DEFAULT_KEY_OBTENTION_ITERATIONS;
private SaltGenerator saltGenerator = null;
private int saltSizeBytes = DEFAULT_SALT_SIZE_BYTES;
private IvGenerator ivGenerator = null;
private int ivSizeBytes = DEFAULT_IV_SIZE_BYTES;
private PBEConfig config = null;
// e.t.c ...
이 초기값들은 기존 Configuration에서 추가한 설정과 중복되는 부분이 많습니다.
그리고, 개인적으로 불필요한 리플렉션을 사용하는 것을 지양합니다.
@Bean("jasyptStringEncryptor")
public StringEncryptor stringEncryptor() {
// ...
// 중복!!
config.setAlgorithm("PBEWithMD5AndDES");
config.setKeyObtentionIterations("1000");
config.setStringOutputType("base64");
// 의미 없음
config.setPoolSize("1");
config.setProviderName("SunJCE");
// 불필요한 리플렉션
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setIvGeneratorClassName("org.jasypt.iv.NoIvGenerator");
return ...;
}
😜 개선된 Configuration
앞서 살펴봤던 문제점을 모두 반영해서 수정하면 다음 설정만 있어도 충분합니다.
특히, 우리는 초기화될 때만 사용하므로 StandardPBEStringEncryptor
을 사용해도 무방할 것 같습니다.
@Configuration
@EnableEncryptableProperties
public class JasyptConfig {
@Value("${jasypt.encryptor.password}")
private String password;
@Bean("jasyptStringEncryptor")
public StringEncryptor stringEncryptor() {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setPassword(password);
encryptor.setSaltGenerator(new RandomSaltGenerator());
encryptor.setIvGenerator(new NoIvGenerator());
return encryptor;
}
}
🔫 적용하기
다음과 같이 yml 파일이 작성되어 있다고 가정해보겠습니다.
딱 봐도 너무나도 중요한 설정으로 보입니다. 당연히 노출시키면 안되겠죠?
중요한설정:
진짜중요함:
API키: password
우리만 사용할 키를 정한 다음, 암호화 사이트를 들어갑니다.
다음 부분만 주의해서 선택한 다음 암호화합니다.
다음과 같이 yml 파일을 작성하면 깔끔하게 암호화가 되었습니다 😋😋
중요한설정:
진짜중요함:
API키: ENC(Fia8u6rJmpYLgVqIe8G6haCE/U3/zfogeXlek+HY0zetfCDlj+VOeVcT9DJ8S9KN)
😋 정리
Jasypt는 간편하게 암호화를 지원하는 하나의 라이브러리입니다.
일반적으로 스프링의 환경 변수를 숨기기 위해서 많이 사용합니다.
@Configuration
을 통해서 등록할 수 있고 간단하게 설정해 주면 됩니다.
(사실 코드가 많은 블로그 글들이 싫어서 작성했습니다.)
-Reference:
😋 지극히 개인적인 블로그지만 댓글과 조언은 제 성장에 도움이 됩니다 😋
'언어 공부 > Java' 카테고리의 다른 글
[Java] Jacoco 잘 사용하기 (feat. 심화) (2) | 2023.10.26 |
---|---|
[Java] Fixture Monkey 사용해보기 (5) | 2023.10.17 |
[Java] JVM 알아보기 (0) | 2023.08.25 |
[Java] 올바른 Collection 선택하기 (0) | 2023.08.09 |
[Java] Hash란? (feat. Hash Collection) (0) | 2023.07.21 |
댓글