적용 계기
github의 프로젝트 Repository를 public으로 변경하게 되면서,
application.yml의 datasource: url, username, password이 외부에 공개되다 보니 이 값을 암호화할 필요성이 생기게 되었습니다.
DB에 접근 허용 IP가 제한되어있어 외부에서 접속은 안되긴 하지만.. 그래도 꼭 필요한 작업이라 Jasypt를 통해 암호화를 적용하였습니다.
혹시라도 application.yml에 설정 등 중요한 정보가 있을 경우 악용될 수 있으니 조심해야 할 것 같습니다. 🤨
Jasypt란?
Jasypt(Java Simplified Encryption)는 애플리케이션 개발에서 중요한 정보를 쉽게 암호화하고 복호화할 수 있도록 도와주는 라이브러리입니다. Spring Boot와 함께 사용할 때, Jasypt를 통해 애플리케이션 설정 파일에 포함된 민감한 데이터를 안전하게 보호할 수 있습니다.
Jasypt의 주요 기능
• 암호화 및 복호화: 데이터를 암호화하고 필요할 때 복호화하는 기능을 제공합니다.
• 사용이 간편함: 간단한 API를 통해 복잡한 암호화 작업을 쉽게 수행할 수 있습니다.
• 설정 파일 보호: 데이터베이스 비밀번호, API 키 등과 같은 민감한 정보를 보호하는 데 유용합니다.
적용 방법
1. Gradle 의존성 추가
build.gradle 파일에 Jasypt 의존성을 추가합니다.
dependencies {
implementation "com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5"
}
작성일 기준 최신 버전인 3.0.5를 적용하였습니다.
https://github.com/ulisesbocchio/jasypt-spring-boot
GitHub - ulisesbocchio/jasypt-spring-boot: Jasypt integration for Spring boot
Jasypt integration for Spring boot. Contribute to ulisesbocchio/jasypt-spring-boot development by creating an account on GitHub.
github.com
2. 테스트코드 작성
JasyptConfig 클래스를 생성하기 전에, JasyptTest 테스트 클래스를 생성하여 암호화 값을 생성해 봅시다.
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.jasypt.iv.RandomIvGenerator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class JasyptTest {
private StringEncryptor encryptor;
@BeforeEach
void setUp() {
encryptor = createStringEncryptor();
}
@Test
void jasypt() {
String value = "vennygo"; // 암호화할 값
String encryptedText = encryptor.encrypt(value);
String decryptedText = encryptor.decrypt(encryptedText);
System.out.println(encryptedText);
assertThat(value).isEqualTo(decryptedText);
}
private StringEncryptor createStringEncryptor() {
String encryptKey = System.getProperty("jasypt.encryptor.password");
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(encryptKey); // 암호화/복호화에 사용할 비밀키를 설정
config.setAlgorithm("PBEWithHMACSHA512AndAES_256"); // 사용할 알고리즘 설정
config.setPoolSize("1"); // 암호화/복호화에 사용할 스레드 풀 크기 설정, 단일 스레드를 사용하므로 "1"로 설정
config.setStringOutputType("base64"); // 암호화된 문자열을 표현할 출력 형식 설정
config.setKeyObtentionIterations("1000"); // 키를 생성하기 위해 알고리즘에 적용할 반복 횟수 지정, 이 값이 높을 수록 암호화된 문자열의 안전성이 높아짐
config.setIvGenerator(new RandomIvGenerator()); // Initial Vector (IV) 생성기를 설정합니다. IV는 암호화된 텍스트의 초기 값을 결정하는데 사용되며, 보안을 위해 암호화마다 다른 값을 사용해야 합니다.
encryptor.setConfig(config);
return encryptor;
}
}
작성한 코드에서, createStringEncryptor() 메서드의 encryptKey를 설정해 줍니다.
이 부분 -> String encryptKey = System.getProperty("jasypt.encryptor.password");
encryptKey를 소스상에 노출시키지 않도록 VM options에 추가하였습니다.
- IntelliJ: 'Run' -> 'Edit Configurations...' 클릭
- 'Modify options' 클릭 -> 'Add VM options' 체크
- VM options 영역에 아래 내용 입력, '암호' 부분에 자신이 사용할 값을 입력합니다. (저는 예시로 vennygoSecretKey 입력)
-Djasypt.encryptor.password=암호
그리고 build.gradle에
systemProperty "jasypt.encryptor.password", System.getProperty("jasypt.encryptor.password")를 추가해 줍니다.
IntelliJ IDEA에서 Gradle을 사용하여 JUnit 테스트를 실행할 때 설정한 시스템 속성이 올바르게 전달되게 합니다. 이거 안 하면 null..!
tasks.named('test') {
useJUnitPlatform()
systemProperty "jasypt.encryptor.password", System.getProperty("jasypt.encryptor.password")
}
이제 JasyptTest 클래스의 jasypt()를 실행하면 "vennygo" 텍스트의 암호화된 값이 로그에 찍히게 됩니다.
3. JasyptConfig 클래스 생성
테스트 클래스를 통해 암호화된 값을 알았으니, 이제 JasyptConfig 클래스를 생성하여 아래와 같이 코드를 작성합니다.
테스트코드에 작성하였던 encryptKey는 @Value로 작성하였습니다.
@Value("${jasypt.encryptor.password}")
private String encryptKey;
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.jasypt.iv.RandomIvGenerator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JasyptConfig {
@Value("${jasypt.encryptor.password}")
private String encryptKey;
@Bean("jasyptStringEncryptor")
public StringEncryptor stringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(encryptKey);
config.setAlgorithm("PBEWithHMACSHA512AndAES_256");
config.setPoolSize("1");
config.setStringOutputType("base64");
config.setKeyObtentionIterations("1000");
config.setIvGenerator(new RandomIvGenerator());
encryptor.setConfig(config);
return encryptor;
}
}
- 알고리즘 차이 (PBEWithHMACSHA512AndAES_256 vs PBEWithMD5AndDES)
1. PBEWithHMACSHA512AndAES_256
• HMAC-SHA-512: 키드 해시 메시지 인증 코드(HMAC)와 함께 사용되는 SHA-512 해시 함수로, 512비트 해시 값을 생성합니다.
• AES-256 (Advanced Encryption Standard): 대칭키 블록 암호 알고리즘으로, 256비트 키를 사용합니다.
• 보안 수준: HMAC-SHA-512와 AES-256는 매우 높은 보안 수준을 제공합니다.
• HMAC-SHA-512: 강력한 해시 함수로, 충돌 저항성이 매우 높습니다.
• AES-256: 현재로서는 실질적으로 해독 불가능한 강력한 암호화 알고리즘입니다.
• 사용 시기: 이 알고리즘은 현재의 보안 표준을 충족하며, 높은 보안 수준이 요구되는 경우에 권장됩니다.
2. PBEWithMD5AndDES
• MD5: 메시지 다이제스트 알고리즘 5, 해시 함수로, 128비트 해시 값을 생성합니다.
• DES (Data Encryption Standard): 대칭키 블록 암호 알고리즘으로, 56비트 키를 사용합니다.
• 보안 수준: MD5와 DES는 현대의 표준에서는 취약하다고 여겨집니다.
• MD5: 충돌 저항성이 낮아 해시 충돌이 쉽게 발생할 수 있습니다.
• DES: 키 길이가 짧아, 현재의 컴퓨팅 능력으로는 쉽게 브루트포스 공격을 할 수 있습니다.
• 사용 시기: 이 알고리즘은 더 이상 권장되지 않습니다. 현대적인 보안 요구 사항을 충족하지 못합니다.
권장 알고리즘: 아래와 같은 이유로 PBEWithHMACSHA512AndAES_256을 권장
• 강력한 암호화: AES-256은 매우 강력한 암호화 알고리즘으로, 현재로서는 해독이 거의 불가능합니다.
• 강력한 해시 함수: SHA-512는 강력한 해시 알고리즘으로, 충돌 저항성이 뛰어납니다.
• 현대적인 보안 요구 사항 충족: 이 알고리즘은 최신 보안 표준을 충족하며, 민감한 데이터 보호에 적합합니다.
4. VM options 추가
테스트 클래스 (JasyptTest.java)에서 추가했던 방식과 동일하게 VM options를 추가해 줍니다.
저는 예시로 'vennygoSecretKey'로 캡처하였지만, 실제로는 특수문자 등을 섞어서 입력해 주었습니다. 🙂
-Djasypt.encryptor.password=암호
5. application.yml 파일 수정 (application-local.yml 도 포함)
위에서 생성한 테스트코드 (JasyptTest.java)로 암호화된 값을 복사하여, 기본 값을 대체해 줍니다.
암호화된 값은 ENC(암호화된값)로 감싸야합니다.
저는 datasource의 url, username, password 값을 모두 암호화된 값으로 대체하였습니다.
6. 서버 환경변수 적용
저는 서버를 카페24를 통해 호스팅 하고 있는데.. (Tomcat).. 추후에 변경 예정..!
위에처럼 만 적용해서 배포하면 @Value("${jasypt.encryptor.password}")를 못 찾아서 에러가 납니다..!
그래서 Tomcat 설정파일에 환경변수를 추가하였습니다.
Tomcat 서버에서 환경 변수를 설정하려면 catalina.sh 또는 catalina.bat 파일에 적절한 방식으로 추가해야 합니다.
catalina.sh는 Unix/Linux/Mac 환경에서, catalina.bat는 Windows 환경에서 사용됩니다.
Unix/Linux/Mac 환경에서 catalina.sh 파일 수정
-> yourSecretKey 부분에 VM options에서 설정하였던 키 값을 입력해 줍니다.
#!/bin/sh
# 환경 변수 설정
export JASYPT_ENCRYPTOR_PASSWORD=yourSecretKey
# 나머지 스크립트 내용
...
Windows 환경에서 catalina.bat 파일 수정
-> yourSecretKey 부분에 VM options에서 설정하였던 키 값을 입력해 줍니다.
@echo off
rem 환경 변수 설정
set JASYPT_ENCRYPTOR_PASSWORD=yourSecretKey
rem 나머지 스크립트 내용
...
파일 수정 후 톰캣 재시작하니 에러 없이 동작되었습니다. 휴 🥹
그리고 추가로, Github에 application.yml파일의 이전 commit history가 남아있어 history도 아래 명령어를 통해 모두 삭제하고
신규로 application.yml 파일 다시 commit 해주었습니다..ㅎㅎ (application.yml 파일 백업 필수)
git filter-branch -f --prune-empty --index-filter "git rm -r --cached --ignore-unmatch **/application.yml" HEAD
git push -u origin master --force
git filter-branch -f --prune-empty --index-filter "git rm -r --cached --ignore-unmatch **/application.yml" HEAD
1. git filter-branch: Git 히스토리를 재작성하는 명령어입니다.
2. -f: 강제 실행 옵션입니다. 이미 filter-branch가 실행된 경우에도 강제로 실행합니다.
3. --prune-empty: 빈 커밋들을 제거합니다. application.yml 파일을 제거한 이후 빈 커밋들이 생성될 수 있습니다.
4. --index-filter "git rm -r --cached --ignore-unmatch **/application.yml":
• --index-filter: 각 커밋에 대해 인덱스를 재작성하는 필터를 설정합니다.
• git rm -r --cached --ignore-unmatch **/application.yml: 현재 디렉토리 및 모든 하위 디렉토리에서 application.yml 파일을 제거하고, 이 변경 사항을 인덱스에 반영합니다. --cached 옵션은 작업 디렉토리에서는 파일을 제거하지 않고 인덱스에서만 제거합니다. --ignore-unmatch 옵션은 해당 파일이 존재하지 않아도 오류를 발생시키지 않고 진행합니다.
• **/application.yml: 모든 하위 디렉토리에서 application.yml 파일을 찾습니다.
5. HEAD: 현재 작업 중인 브랜치의 모든 커밋에 대해 명령어를 실행합니다.
git push -u origin master --force
1. git push: 로컬 저장소의 변경 사항을 원격 저장소로 푸시하는 명령어입니다.
2. -u origin master: -u 옵션은 푸시할 원격 저장소와 브랜치를 설정합니다. origin은 원격 저장소의 이름을, master는 로컬에서 푸시할 브랜치를 지정합니다.
3. --force: 강제로 푸시하는 옵션입니다. 이 옵션을 사용하면 원격 저장소의 히스토리를 강제로 변경할 수 있습니다. 이는 히스토리를 재작성한 후 사용하는 경우 필요한 옵션이며, 주의해서 사용해야 합니다.
'Framework > Spring' 카테고리의 다른 글
[Spring] Spring Security 기본 설정 방법 (Spring Boot 3, SecurityFilterChain) (0) | 2024.07.04 |
---|---|
[Spring] RESTful API 컨트롤러의 @GetMapping 에서 데이터를 받는 방법 (0) | 2024.07.03 |
[Spring] Spring Boot 기본, 자주 사용하는 어노테이션 (애너테이션) (0) | 2024.07.02 |
[Spring Boot] application.yml이란? application.yml 설정 방법 (0) | 2024.06.29 |
[Spring Boot] application.properties와 application.yml 차이 (0) | 2024.06.29 |