아직도 properties에 중요 정보를 그대로 두십니까??🫢
프로젝트를 진행하면서 yml 파일에 DB 연결 URL이나 계정, 비밀번호 같은 중요한 properties가 그대로 노출되어 있길래 이것을 암호화를 해 보았다.
Jasypt이란 쉽게 암호화 기능을 사용할 수 있도록 제공하는 Java 라이브러리로 http://www.jasypt.org/ 에 가면 더 자세한 정보를 확인할 수 있다.
이것을 사용해서 암호화와 복호화를 해보자.
<!-- Jasypt properties 암호화 -->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
<!-- 암호화 알고리즘 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.69</version>
</dependency>
<!-- Jasypt properties 암호화 -->
implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.4'
암/복호화 알고리즘은 기본 제공되는 PBE 알고리즘을 사용해도 되지만
나는 SHA256, AES128 사용을 위해 BouncyCastle 라이브러리를 사용했다. BouncyCastle은 PBE(Password Based Encryption)에 보다 많은 알고리즘을 제공해 준다.
// Jasypt config 클래스
package com.spring.blog.utils;
import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Collectors;
@Configuration
@EnableEncryptableProperties
public class JasyptConfigDES {
@Bean("jasyptEncryptor")
public StringEncryptor stringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
encryptor.setProvider(new BouncyCastleProvider());
encryptor.setPoolSize(2); // 암호화 요청의 pool 크기. 2를 권장
encryptor.setPassword("password"); // 암호화 키
encryptor.setAlgorithm("PBEWithSHA256And128BitAES-CBC-BC"); // 사용 알고리즘
return encryptor;
}
}
암호화키는 본인이 설정하고 싶은 문자열을 넣으면 된다.
이것 외에도 'string-output-type', 'key-obtention-iterations'같은 설정을 추가로 줄 수 있는데 생략하면 기본 설정 값으로 들어가서 생략해도 무방하다.
알고리즘은 위에서 dependency로 추가해준 암호화 알고리즘 사용했다.
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
encryptor.setProvider(new BouncyCastleProvider());
encryptor.setPoolSize(2);
encryptor.setPassword("password");
encryptor.setAlgorithm("PBEWithSHA256And128BitAES-CBC-BC");
String plainText = "ssr_lc";
String encryptedText = encryptor.encrypt(plainText);
String decryptedText = encryptor.decrypt(encryptedText);
System.out.println("Enc = " + encryptedText);
System.out.println("Dec = " + decryptedText);
}
이제 메인 메서드에서 암호화와 복호화 결과 출력을 해보자
테스트 코드로 짜서 확인해도 되지만 귀찮으니까…
(아래 참고에 테스트 코드로 암/복호화 결과 보는 방법을 추가했으니 테스트 코드가 궁금하면 아래로...)
암호화/복호화 하고싶은 값을 넣어서 프로젝트를 실행해보면
이렇게 암호화된 값이 출력되게 된다.
이 값을 yml에 있는 properties에 넣어주면 되는데 프로젝트가 실행될 때 복호화가 되도록
ENC("암호화 값")
이렇게 넣어주면 된다
yml에 DB 연결 정보 properties 암호화 처리 완료!!
여기까지만 처리하고 Github에 올리면 Bean에 들어있는 암호화 키도 같이 올라가는 것이므로 property를 확인하는데 단계만 하나 추가 될 뿐, 여전히 노출된 암호화 키를 이용해서 복호화를 할 수 있게 된다.
마치 현관을 잠그고 키를 현관 옆 소화기 밑에 두는...
이를 해결하기 위해서 암호화 키를 리소스로 만들어서 txt 파일에 저장해 불러와서 사용하고, 키가 저장된 txt파일을 ignore 처리해서 Github에 안올라가도록 만들면 안전하게 암호화 처리를 끝낼 수 있게 된다.
package com.spring.blog.utils;
import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Collectors;
@Configuration
@EnableEncryptableProperties
public class JasyptConfigDES {
@Bean("jasyptEncryptor")
public StringEncryptor stringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
encryptor.setProvider(new BouncyCastleProvider());
encryptor.setPoolSize(2);
encryptor.setPassword(getJasyptEncryptorPassword()); // 암호화 키
encryptor.setAlgorithm("PBEWithSHA256And128BitAES-CBC-BC"); // 알고리즘
return encryptor;
}
private String getJasyptEncryptorPassword() {
try {
ClassPathResource resource = new ClassPathResource("jasypt-encryptor-password.txt");
return Files.readAllLines(Paths.get(resource.getURI())).stream()
.collect(Collectors.joining(""));
} catch (IOException e) {
throw new RuntimeException("Not found Jasypt password file.");
}
}
}
리소스 파일을 읽는 메서드를 만들고
그 리스소 파일에서 암호화 키를 읽어오게 만들었다.
그리고 리소스에 있는 암호화 키를 가진 txt 파일은 gitignore 처리
### Jasypt Password Key igonre ###
/src/main/resources/jasypt-encryptor-password.txt
ClassPathResource은 리소스파일을 쉽게 가져올 수 있도록 해주는 클래스. src/main/resources/data/data.txt
에 있는 파일을 가져오려면 다음과 같이 사용하면 된다.
ClassPathResource resource = new ClassPathResource("data/data.txt");
@Test
public void jasypt_암호화_복호화() {
// given
String mysqlURL = "jdbc:mysql://내_DB_URL:3306/내_DB_schema?serverTimezone=Asia/Seoul&characterEncoding=UTF-8&allowMultiQueries=true";
String mysqlUserName = "myId";
String mysqlPassword = "myPassword";
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
encryptor.setProvider(new BouncyCastleProvider());
encryptor.setPoolSize(2);
encryptor.setPassword("password");
encryptor.setAlgorithm("PBEWithSHA256And128BitAES-CBC-BC");
// when
String encryptedTextMysqlURL = encryptor.encrypt(mysqlURL);
System.out.println("mysqlURL 암호화 값 : " + encryptedTextMysqlURL);
String encryptedTextMysqlUserName = encryptor.encrypt(mysqlUserName);
System.out.println("mysqlUserName 암호화 값 : " + encryptedTextMysqlUserName);
String encryptedTextMysqlPassword = encryptor.encrypt(mysqlPassword);
System.out.println("mysqlPassword 암호화 값 : " + encryptedTextMysqlPassword);
String decryptedTextMysqlURL = encryptor.decrypt(encryptedTextMysqlURL);
String decryptedTextMysqlUserName = encryptor.decrypt(encryptedTextMysqlUserName);
String decryptedTextMysqlPassword = encryptor.decrypt(encryptedTextMysqlPassword);
// then
assertThat(mysqlURL).isEqualTo(decryptedTextMysqlURL);
assertThat(mysqlUserName).isEqualTo(decryptedTextMysqlUserName);
assertThat(mysqlPassword).isEqualTo(decryptedTextMysqlPassword);
}