nhyunzi
[SpringBoot] Jasypt를 이용한 설정파일 암호화 (application.properties) 본문
application.yml이나 application.properties 파일에 DB 비밀번호 또는 키 값을 명시해두는 경우 중요 정보들이 외부로 노출되어 위험할 수 있다.
해결 방법
1. application.properties 파일을 .git ignore 파일에 추가하여 git push시 이 파일을 제외하도록 하는 방법
협업자들이 각자 application.properties파일의 사본을 갖고 있고 나머지 코드만 push하여 application.properties파일은 원격 레포지토리에 업로드 되지 않도록 하는 방법이다. 하지만 application.properties파일이 존재하지 않으면 Spring Boot애플리케이션의 설정 정보가 없기 때문에 빌드와 배포가 정상적으로 수행되지 않을 수 있다.
2. application.propeties 파일을 소스 코드에 포함시키면서 중요 정보를 jasypt로 암호화하여 관리하는 방법
두번째 방법을 이용하여 jasypt 라이브러리를 통해 중요 정보를 암호화하는 방법을 다뤄보려고 한다.
Jasypt(Java Simplified Encryption)란?
개발자가 암호화 작동 방식에 대한 깊은 지식 없이도 자신의 프로젝트의 설정 파일의 속성 값들을 암호화, 복호화 할 수 있는 JAVA 라이브러리다.
https://github.com/ulisesbocchio/jasypt-spring-boot
이 프로젝트의 README에 사용법이 잘 설명되어 있다.
이제 Spring Boot에서 이를 사용해보자
먼저, 의존성을 추가한다.
빌드 방식을 maven을 선택했기 때문에 pom.xml에 해당 라이브러리를 정의했다.
* 암호화 알고리즘 (Algorithm) : 3.0 버전 부터 기본 알고리즘이 PBEWithMD5AndDES → PBEWITHHMACSHA512ANDAES_256로 변경됐다.
또한 비공개 키, 인코딩 타입, 솔트 값 생성기 등을 설정할 수 있다.
나는 PBEWithMD5AndDES 알고리즘을 이용해 작성했다.
1. Config 클래스 생성
@Configuration
@EnableEncryptableProperties
public class JasyptConfig {
@Value("${jasypt.encryptor.password}")
private String key;
@Bean("jasyptEncryptor")
public StringEncryptor stringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(key); // 암호화키
config.setAlgorithm("PBEWithMD5AndDES"); // 알고리즘
config.setKeyObtentionIterations("1000"); // 반복할 해싱 회수
config.setPoolSize("1"); // 인스턴스 pool
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); // salt 생성 클래스
config.setStringOutputType("base64"); // 인코딩 방식
encryptor.setConfig(config);
return encryptor;
}
}
@Value("$jasypt.encryptor.password")
application.properties에서 Jasypt의 암호화에 필요한 비밀번호를 읽어오는 역할을 한다.
이 값은 암호화와 복호화를 위해 사용되는 비밀번호로 개발자가 잘 관리해야하는 값이다.
stringEncryptor 메서드내부에서는 암호화 방식을 설정해준다.
알고리즘 방식이나 출력형식등이 세팅된다. 세부적인 세팅은 공식문서를 참조한다.
setPassword : 암호화 및 복호화에 사용할 비밀 키를 설정한다. 외부에 노출되어서는 안된다.
setAlgorithm : 암호화에서 사용할 알고리즘을 설정한다.
setKeyObtentionIterations : 암호화 키 생성 반복 횟수를 설정한다.
setPoolSize : 암호화에서 사용할 스레드 풀 크기를 설정한다.
setSaltGeneratorClassName : 암호화에 사용할 salt 생성기를 지정한다.
setStringOutputType : 암호화에서 생성된 문자열 출력 형식을 설정한다.
* PBEWITHHMACSHA512ANDAES_256 알고리즘을 사용한다면,
config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256"); 으로 변경하고
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator"); IV 생성 클래스를 추가한다.
2. application.properties 설정 추가
기본적으로 위 처럼 DB정보가 기입되어 있을텐데 그 아래에 jqsypt 관련 설정을 추가한다.
위는 로컬 개발 환경의 예시지만, 실제 프로젝트에서는 AWS RDS를 사용했다. 클라우드 환경에서는 데이터베이스 접속 정보가 노출되면 보안 위험이 크기 때문에, Jasypt 라이브러리를 사용하여 중요 정보를 암호화 처리하는게 좋다.
jasyptEncryptor는 JasyptConfig에서 등록한 bean의 이름이다. 두 이름이 일치해야 한다.
password는 JasyptConfig 에서 @Value 어노테이션으로 가져와서 암호화와 복호화에 사용하게 될 비밀번호다.
3. @EnableEncryptableProperties 어노테이션 추가
@EnableAsync
@EnableScheduling
@EnableCaching
@SpringBootApplication
@EnableEncryptableProperties // 이부분을 추가한다.
public class RoommakeApplication {
public static void main(String[] args) {
SpringApplication.run(RoommakeApplication.class, args);
}
}
@EnableEncryptableProperties 어노테이션을 프로젝트의 main메서드가 있는 클래스에 추가한다.
이 어노테이션을 추가하지 않으면 런타임 상태에서 암호화된 설정값들을 자동으로 스프링이 변환해주지 않아서
복호화가 되지 않는다.
그런데, 앞서 jasypt를 사용하는 이유가 중요 정보를 암호화하는 목적이라고 했다. 하지만 현재 application.properties 파일에는 암복호화를 위한 password가 노출되어 있는 상태다. 마치 자물쇠와 열쇠를 함께 두는 꼴이나 마찬가지다.
password와 암호화 방식만 알면 문자열을 복호화하는 것은 쉽기 때문에 password가 노출되지 않도록 해야한다.
password가 노출되지 않도록 하는 방식으로는 VM option에 추가하는 방식, 환경변수로 등록하고 사용하는 방식이 있다.
# VM option에 추가하는 방식
1-1) 인텔리제이 addVM options에 비밀번호를 추가
인텔리제이에서는 설정파일의 값을 동적으로 넣어주는 옵션이 있다. 이를 이용해 application. properties 파일에 비밀번호가 노출되지 않은 상태에서도 어플리케이션을 구동할 수 있다.
Run -> Edid Configurations -> SpringBoot 프로젝트 -> Modify Options -> Add VM options
위와같이 VM 옵션을 사용하도록 설정하고 다음과 같은 값을 추가한다.
-Djasypt.encryptor.password=my_jasypt_password
-D를 쓴다음 property이름과 값을 적는다. 앞서 application.properties파일에서 encryptor password를 my_jasypt_password로 했기 때문에 이곳에 같은 값을 적는다.
*만약 이 설정값을 test에서도 사용하고 싶다면 maven의 Test에서 사용할 수 있도록 같은방식으로 추가한다.
Test코드에서는 본 프로젝트의 설정값을 가져오려면 시점문제를 고려하는 등 귀찮은 작업이 필요하기 때문에
여기서도 그냥 추가해준다.
1-2) maven-surefire-plugin 추가
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<systemPropertyVariables>
<jasypt.encryptor.password>${jasypt.encryptor.password}</jasypt.encryptor.password>
</systemPropertyVariables>
</configuration>
</plugin>
${jasypt.encryptor.password}는 IntelliJ의 VM 옵션에서 설정한 비밀번호를 참조한다. 이렇게 하면 Maven Surefire Plugin이 테스트를 실행할 때 VM 옵션에 저장된 비밀번호를 사용하여 암호화된 값을 복호화할 수 있다.
1-3) 암호화 문자열 생성
이제 테스트코드로 암복호화를 테스트해본다.
@SpringBootTest
public class JasyptConfigTest {
private String encryptKey;
@Test
@DisplayName("데이터베이스 URL 및 사용자 정보를 jasypt로 암호화")
void stringEncryptor() {
encryptKey = System.getProperty("jasypt.encryptor.password"); // 이부분에서 차이가 있다.
String url = "db_url";
String username = "db_username";
String password = "db_password";
String encryptUrl = jasyptEncrypt(url);
String encryptUsername = jasyptEncrypt(username);
String encryptPassword = jasyptEncrypt(password);
String decryptUrl = jasyptDecrypt(encryptUrl);
String decryptUsername = jasyptDecrypt(encryptUsername);
String decryptPassword = jasyptDecrypt(encryptPassword);
System.out.println("암호화된 URL: " + encryptUrl);
System.out.println("암호화된 사용자 이름: " + encryptUsername);
System.out.println("암호화된 비밀번호: " + encryptPassword);
System.out.println("복호화된 URL: " + decryptUrl);
System.out.println("복호화된 사용자 이름: " + decryptUsername);
System.out.println("복호화된 비밀번호: " + decryptPassword);
Assertions.assertThat(url).isEqualTo(decryptUrl);
}
private String jasyptEncrypt(String value) {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setAlgorithm("PBEWithMD5AndDES");
encryptor.setPassword(encryptKey);
return encryptor.encrypt(value);
}
private String jasyptDecrypt(String value) {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setAlgorithm("PBEWithMD5AndDES");
encryptor.setPassword(encryptKey);
return encryptor.decrypt(value);
}
}
encryptKey = System.getProperty("jasypt.encryptor.password");
VM 옵션으로 설정한 비밀번호를 JVM의 시스템 속성에서 가져와 테스트 케이스를 수행한다.
* PBEWITHHMACSHA512ANDAES_256 알고리즘을 사용한다면,
jasyptEncrypt와 jasyptDecrypt 각각에
encryptor.setAlgorithm("PBEWITHHMACSHA512ANDAES_256"); 으로 변경하고
encryptor.setIvGenerator(new RandomIvGenerator()); IV 생성기 추가한다.
# 환경변수로 등록하고 사용하는 방식
2-1) maven-surefire-plugin 추가
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
<configuration>
<systemPropertyVariables>
<jasypt.encryptor.password>${env.JASYPT_ENCRYPTOR_PASSWORD}</jasypt.encryptor.password>
</systemPropertyVariables>
</configuration>
</plugin>
${env.JASYPT_ENCRYPTOR_PASSWORD}
env는 시스템 환경변수를 의미하며, JASYPT_ENCRYPTOR_PASSWORD라는 환경변수의 값을 가져오는것을 의미한다.
2-2) 시스템 환경변수 추가
시스템 환경변수 편집 -> 고급 -> 환경변수 -> 사용자 변수 새로만들기 -> 입력후 확인
jasypt.encryptor.password는 Jasypt가 암호화된 값을 복호화하는데 사용하는 키(password)이다.
JASYPT_ENCRYPTOR_PASSWORD라는 환경변수에서 값을 읽어올수 있도록 사용자 변수를 추가한다.
2-3) 암호화 문자열생성하기
이제 테스트코드로 암복호화를 테스트해본다.
@SpringBootTest
public class JasyptConfigTest {
private String encryptKey;
@Test
@DisplayName("데이터베이스 URL 및 사용자 정보를 jasypt로 암호화")
void stringEncryptor() {
encryptKey = System.getenv("JASYPT_ENCRYPTOR_PASSWORD");
String url = "db_url";
String username = "db_username";
String password = "db_password";
String encryptUrl = jasyptEncrypt(url);
String encryptUsername = jasyptEncrypt(username);
String encryptPassword = jasyptEncrypt(password);
String decryptUrl = jasyptDecrypt(encryptUrl);
String decryptUsername = jasyptDecrypt(encryptUsername);
String decryptPassword = jasyptDecrypt(encryptPassword);
System.out.println("암호화된 URL: " + encryptUrl);
System.out.println("암호화된 사용자 이름: " + encryptUsername);
System.out.println("암호화된 비밀번호: " + encryptPassword);
System.out.println("복호화된 URL: " + decryptUrl);
System.out.println("복호화된 사용자 이름: " + decryptUsername);
System.out.println("복호화된 비밀번호: " + decryptPassword);
Assertions.assertThat(url).isEqualTo(decryptUrl);
}
private String jasyptEncrypt(String value) {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setAlgorithm("PBEWithMD5AndDES");
encryptor.setPassword(encryptKey);
return encryptor.encrypt(value);
}
private String jasyptDecrypt(String value) {
StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
encryptor.setAlgorithm("PBEWithMD5AndDES");
encryptor.setPassword(encryptKey);
return encryptor.decrypt(value);
}
}
encryptKey = System.getenv("JASYPT_ENCRYPTOR_PASSWORD");
시스템 환경변수에서 JASYPT_ENCRYPTOR_PASSWORD라는 변수의 값을 읽어오와 테스트를 수행한다.
위는 암호화 하려는 문자열들을 선언하고 암호화해서 콘솔에 출력하고 다시 복호화 해서 출력하는 코드다.
Jasypt Encryption and Decryption Online
Jasypt Encryption and Decryption Online
Jasypt online free tool for encryption and decryption.This tool supports one way and two way password encryptor using Jasypt as well as matching encrypted password using Jasypt.
www.devglan.com
* PBEWithMD5AndDES 알고리즘 방식을 사용한다면 이 사이트를 통해서 암호화가 가능하다.
앞선 설정이 잘 수행되었다면 다음과 같이 콘솔에 문자열이 출력된다. 이렇게 콘솔에 출력된 암호화 된 문자열을 이용해
application.proeprties의 중요 정보를 암호화한다.
암호화된 문자열을 스프링이 인지하도록 `ENC(암호화된문자열)` 의 형태로 적어주면 런타임시에 자동으로 스프링이 암호화된 문자열을 복호화하여 설정값으로 사용한다.
이렇게 jasypt라이브러리를 이용해 암호화와 복호화가 작동하는지 테스트 해보았다. 하지만 위 테스트코드로는 런타임시에 DB와의 연동이 잘 이루어지는지 테스트할 수 없다. 실제로 스프링이 복호화를 잘 수행하여 연동을 잘 마치는지 테스트 해보려면 실제 어플리케이션을 구동해보아야 한다.