프로퍼티 암호화의 필요성을 절실히 느낀 건 소셜 로그인 기능을 구현하고서였다. 남 일 같지 않던 '개인키 노출로 인한 AWS 과금 이슈'를 접한 무렵이기도 했고, 로컬 단위의 소규모 프로젝트라 해도 클라이언트 ID/패스워드 및 API 키와 같은 중요 정보를 깃허브(GitHub)에 공개적으로 올려두는 건 무슨 이유에서든 엄두가 나지 않았다.
실사용 DB 등을 다룰 나중을 위해서라도 암호화에 대한 학습이 당장 필요해진 셈이었는데, 어디 여쭐 곳 없이 구글링하던 중 발견한 방법이 바로 Jasypt였다.
1. Jasypt
❗ Jasypt(Java Simplified Encryption)는 개발자로 하여금 암호화 과정이 어떻게 치러지는지에 대한 깊은 지식 없이도 최소한의 노력을 들여 기본적인 암호화 기능을 추가할 수 있도록 해 주는 자바 라이브러리이다(Jasypt is a 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).
- 무엇보다 스프링(Spring) 기반 어플리케이션에 적용하는 데 적합하며, Spring Security와도 함께 쓰일 수가 있다(Suitable for integration into Spring-based applications and also transparently integrable with Spring Security).
- 보다 자세한 설명은 공식 홈페이지와 깃허브에서 조회 가능하다.
2. 의존성(dependency) 추가
❗ 최신 버전 등 상세 정보는 MVNRespository에서 확인할 수 있다: https://mvnrepository.com/artifact/com.github.ulisesbocchio/jasypt-spring-boot-starter
2-1. Maven
- 메이븐(Maven) 환경인 경우 pom.xml 파일에 의존성(dependency)을 추가한다.
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
2-2. Gradle
- 그레이들(Gradle) 환경이라면 build.gradle 파일에 의존성(dependency)을 추가한다.
implementation group: 'com.github.ulisesbocchio', name: 'jasypt-spring-boot-starter', version: '3.0.4'
3. Config 설정
3-1. 환경설정 파일(application.yml) 작성
❗ 암호화 알고리즘(jasypt.encryptor.algorithm)이 3.0.0 버전부터 PBEWithMD5AndDES → PBEWITHHMACSHA512ANDAES_256로 변경되었다. 여기서는 이전 알고리즘으로 작성을 계속한다.
# jasypt config
jasypt:
encryptor:
bean: jasyptStringEncryptor
property:
prefix: ENC(
suffix: )
algorithm: PBEWithMD5AndDES
iv-generator-classname: org.jasypt.iv.NoIvGenerator
- 프리픽스(prefix)와 서픽스(surrfix) 설정에 따라 ENC(암호화된 텍스트) 형태를 지닌다.
# datasource config
spring:
datasource:
driver-class-name: oracle.jdbc.driver.OracleDriver
url: jdbc:oracle:thin:@localhost:1521:xe
username: ENC(암호화된 username)
password: ENC(암호화된 password)
- DB 접속 정보로 예를 들면 위와 같다.
3-2. JasyptConfiguration 클래스 작성
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@Configuration
public class JasyptConfiguration {
@Autowired
private Environment environment;
public JasyptConfiguration(Environment environment) {
this.environment = environment;
}
@Bean("jasyptStringEncryptor")
public StringEncryptor stringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(environment.getProperty("jasypt.encryptor.password"));
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
return encryptor;
}
}
- 환경설정 파일 application.yml에서 명시한 빈(bean) 이름 그대로 등록해준다: @Bean("jasyptStringEncryptor")
- 암호화 알고리즘은 환경설정 파일에서 정의해두었으므로 여기서는 작성하지 않는다.
- 만약 앞선 환경설정 파일에서 알고리즘을 다루지 않았다면, 여기서 다음과 같이 추가적인 작성이 필요하다: config.setAlgorithm(알고리즘);
- 그밖에는 반복할 해싱 횟수, 인스턴스 풀(pool) 사이즈, 솔트 생성 클래스명, 인코딩 방식에 관한 설정이니 그대로 작성해준다.
- 중요한 건 setPassword(패스워드) 부분이다. 여기서는 Environment 객체 통해 프로퍼티 소스(property source)를 읽어오고, 그곳에 jasypt.encryptor.password로 정의된 정보를 불러오도록 설정했다.
- 만약 프로젝트 관련 프로퍼티들을 임의의 외부 파일로 관리하고 있다면, ConfigurableEnvironment 인터페이스 통해 구현하고 다음과 같이 호출해 활용할 수 있을 것이다.
- 하지만 현재 소개 중인 프로젝트에서는 VM arguments로 등록해 실행 시 요청되는 방법을 택했으므로 아래 코드는 생략한다.
//외부 파일 호출
//Context 생성
ConfigurableApplicationContect context = new GenericXmlApplicationContext();
//Environment 생성
ConfigurableEnvironment environment = context.getEnvironment();
//PropertySources 호출
MutablePropertySources propertySources = environment.getPropertySources();
try {
propertySources.addLast(new ResourcePropertySource("classpath:파일명"));
} catch(Exception e) {
e.printStackTrace();
}
4. 암호화 테스트 코드 작성
4-1. 온라인 사이트 활용 암복호화
❗ 온라인 사이트에서 간단하게 암복호화 진행이 가능하다: https://www.devglan.com/online-tools/jasypt-online-encryption-decryption
4-2. 암호화 테스트 코드 작성
import org.assertj.core.api.Assertions;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@SpringBootTest
public class JasyptApplicationTests {
@Autowired
private Environment environment;
@Test
@DisplayName("Jasypt 암복호화 테스트")
public void testJasyptEncryptToDecrypt() {
String plainText = ""; //입력 후 console에서 encrypted result 확인
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(environment.getProperty("jasypt.encryptor.password"));
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
String encrypted = encryptor.encrypt(plainText);
String decrypted = encryptor.decrypt(encrypted);
log.info("jasypt encrypted text : {}", encrypted);
Assertions.assertThat(plainText).isEqualTo(decrypted);
}
}
- JUnit5 활용해 테스트 코드를 만들어놓고, 필요에 따라 '평문(일반 텍스트) → 암호문(암호화된 텍스트)'로의 암호화, 혹은 그 반대로의 복호화를 진행할 수 있다.
- 상단의 코드에서 plainText = ""; 부분에 해당되는 문자를 넣어 테스트 진행하면 암호화된 텍스트로 반환된다. 그 텍스트 그대로 환경설정 파일에 활용하면 된다.
5. VM arguments 등록 및 호출
'그렇다면 위 과정을 통해 애써 암호화 처리한 패스워드를 어떻게 비공개로 다룰 수 있을까? 테스트 코드 통해 암복호화가 가능하다면 사실상 해당 소스를 볼 수 있는 모든 사용자가 패스워드를 다루게 되는 것 아닐까?' 하는 고민과 의심은 나 역시 있었다. 그래서 택한 게 VM arguments로 등록하는 방법이었다.
❗ -Djasypt.encryptor.password=(암복호화용 패스워드)
- VM arguments로 등록할 경우 Spring Boot App은 물론이고 JUnit Test 실행 시에도 암복호화용 패스워드(encrypt key)가 요구된다. 다시 말해 깃허브(GitHub)에서 프로젝트를 임포트(import) 받아 실행한다한들 jasypt.encryptor.password로 등록했던 키(key)가 일치하지 못하면 BindException 발생으로 어플리케이션/테스트 파일의 실행조차 되지 않는 것이다.
- VM arguments 등록 방법은 간단하다: Run → Run Configurations... → Spring Boot App 또는 JUnit 하위에서 해당하는 어플리케이션/테스트 파일 선택 → VM arguments 란에 -Djaypt.encryptor.password=패스워드 작성 → 적용(Apply) 및 실행(Run).
- 이밖에 GitHub Actions 통한 호출 방법도 있다.
- ①깃허브(GitHub) 레포지토리 세팅(Settings) → 보안(Security) → 시크릿(Secrets) → 액션(Actions) → 새 레포지토리 시크릿 생성(New Repository Secrets)에서 변수명(Name)/내용(Secret) 입력 후 환경 변수 등록한다.
- ②저장된 환경 변수는 ${{secrets.변수명}} 형태로 접근이 가능하다.