... 생략 ...
## MySQL 데이터베이스 연결 설정
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://121.196.xxx.xxx:3306/user?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
## Redis 캐시 연결 설정
redis.host=121.196.xxx.xxx
redis.port=6379
redis.password=111111
## SMS 서비스 연결 설정
ali.sms.access_key_id=2zHmLdxAes7Bbe2w
ali.sms.access_key_secret=bImWdv6iy0him8ly
... 생략 ...
이 예제는 특정 스프링 부트 프로젝트의 application.properties 파일에서 추출한 내용입니다.
⚠️
속삭여요, 많은 개발자들이 이렇게 작성하는 걸 아세요?
초보적인 시각으로 보면 문제가 없어 보이지만, 실무에서는 여러 프로젝트(오픈소스 포함)에서 이런 방식을 사용하는 것을 목격했습니다.
하지만 깊이 생각해보면 다음과 같은 위험성이 있습니다:
아니! 많은 프로젝트의 설정 파일에 데이터베이스 비밀번호, 캐시 비밀번호, 또는 제3자 서비스 키 등 중요한 정보가 암호화 없이 노출되어 있습니다!
누군가는 "이건 제 자신의 프로젝트니까 문제 안 있겠지?"라고 말할 수 있지만, 실제로는 위험합니다. 이전에 한 개발자가 자신의 GitHub 저장소에 프로젝트 코드를 업로드했고, 설정 파일을 처리하지 않아 데이터베이스가 유출된 사례가 있습니다. 해당 회사는 호텔 관리 회사였기 때문에 결과는可想而知 (예상할 수 있겠죠).
다른 관점에서 보면, 해당 프로젝트의 설정 파일이 모든 중요한 정보를 암호화했다면 이런 사고는 발생하지 않았을 것입니다. 따라서 프로젝트 설정 파일에서도 중요한 정보는 암호화해야 합니다!
암호화 대상은 무엇인가요?
일반적으로 프로젝트 설정 파일에서 정보 보안과 관련된 항목은 반드시 처리해야 합니다. 대표적인 예로는 다음과 같습니다:
- 데이터베이스 및 캐시 비밀번호
- 중간웨어, 메시지 큐 비밀번호
- 제3자 서비스 Access Key
- 기타 제3자 서비스 통신 정보
- ... 등등
결론적으로 모든 중요한 필드는 명문으로 노출시키면 안 됩니다!
암호화 방법은 어떻게 하나요?
과정은 매우 간단하며, 최소한의 단계로 구성됩니다. 가장 기본적인 예시로 살펴보겠습니다:
- 기본 스프링 부트 프로젝트 생성
설명은 생략하겠습니다.
- jasypt-spring-boot 암호화 라이브러리 추가
jasypt-spring-boot이라는 즉석형 암호화 컴포넌트를 통해 Jasypt 강력한 암호화 라이브러리를 도입합니다.
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
- 암호화 키 설정
스프링 부트의 application.properties 파일에 다음 설정을 추가합니다:
jasypt.encryptor.password=CodeSheep
이는 jasypt가 사용자 정의 암호화 키를 통해 설정 파일 내 중요 항목을 암호화한다는 의미입니다.
- 암호화 테스트
테스트 용이성을 위해 스프링 부트 프로젝트의 시작 클래스를 확장하여 암호화 테스트 코드를 실행합니다.
@SpringBootApplication
public class SpringBootConfigEncryptApplication implements CommandLineRunner {
@Autowired
private ApplicationContext appCtx;
@Autowired
private StringEncryptor codeSheepEncryptorBean;
public static void main(String[] args) {
SpringApplication.run(SpringBootConfigEncryptApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
Environment environment = appCtx.getBean(Environment.class);
// 원본 암호 값 가져오기
String dbPassword = environment.getProperty("spring.datasource.password");
String redisPassword = environment.getProperty("redis.password");
String smsSecret = environment.getProperty("ali.sms.access_key_secret");
// 암호화 처리
String encryptedDbPass = encrypt(dbPassword);
String encryptedRedisPass = encrypt(redisPassword);
String encryptedSmsKey = encrypt(smsSecret);
// 암호화 전후 결과 출력
System.out.println("MySQL 원본 비밀번호: " + dbPassword);
System.out.println("Redis 원본 비밀번호: " + redisPassword);
System.out.println("SMS 원본 Secret: " + smsSecret);
System.out.println("=========================");
System.out.println("MySQL 암호화 결과: " + encryptedDbPass);
System.out.println("Redis 암호화 결과: " + encryptedRedisPass);
System.out.println("SMS 암호화 결과: " + encryptedSmsKey);
}
private String encrypt(String origin) {
return codeSheepEncryptorBean.encrypt(origin);
}
}
실행 결과:
MySQL 원본 비밀번호: 123456
Redis 원본 비밀번호: 111111
SMS 원본 Secret: bImWdv13da894mly
=========================
MySQL 암호화 결과: IV7SyeQOfG4GhiXeGLboVgOLPDO+dJMDoOdmEOQp3KyVjruI+dKKeehsTriWPKbo
Redis 암호화 결과: litUkxJ3fN6+//Emq3vZ+y4o7ZOnZ8doOy7NrgJIDLoNWGG0m3ygGeQh/dEroKvv
SMS 암호화 결과: MAhrOs20DY0RU/c1IKyLCt6dWZqLLOO4wUcK9GBgSxNII3C+y+SRptors+FyNz55xNDslhDnpWllhcYPwZsO5A==
- 설정 파일 수정
위에서 얻은 암호화 결과를 사용하여 설정 파일의 원문 값을 교체합니다. 예시:
이와 같이 모든 중요한 정보를 처리하는 것이 강력히 권장됩니다!
- 암호 해독 확인
@SpringBootApplication
public class SpringBootConfigEncryptApplication implements CommandLineRunner {
@Autowired
private ApplicationContext appCtx;
@Autowired
private StringEncryptor codeSheepEncryptorBean;
public static void main(String[] args) {
SpringApplication.run(SpringBootConfigEncryptApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
Environment environment = appCtx.getBean(Environment.class);
// 암호화된 값 가져오기
String dbPassword = environment.getProperty("spring.datasource.password");
String redisPassword = environment.getProperty("redis.password");
String smsSecret = environment.getProperty("ali.sms.access_key_secret");
// 해독 결과 출력
System.out.println("MySQL 해독 비밀번호: " + dbPassword);
System.out.println("Redis 해독 비밀번호: " + redisPassword);
System.out.println("SMS 해독 Secret: " + smsSecret);
}
}
출력 결과:
MySQL 해독 비밀번호: 123456
Redis 해독 비밀번호: 111111
SMS 해독 Secret: bImWdv13da894mly
코드에서 사용할 때 jasypt-spring-boot 컴포넌트가 ENC() 구문으로 감싸진 암호화 항목을 자동 해독하여 데이터를 복원합니다.
개념적 의문점
이제 많은 개발자들이 궁금해할 수 있는 질문들입니다:
- 암호화 키를 ENC()에 넣어야 하는데 왜 ENC인가요?
- 정보 보안 관련 항목이 암호화 되었지만, 사용자 정의 암호화 키가 유출되면 해독이 가능한 건 아닌가요?
이러한 질문들에 대한 답을 계속 알아보겠습니다.
사용자 정의 암호화 접두사/접미사
jasypt의 기본 제공 ENC로 암호화 항목을 표시할 필요 없습니다. 사용자 정의 접두사/접미사를 사용할 수도 있습니다. 예를 들어 CodeSheep()로 암호화 항목을 표시하려면 설정 파일에서 다음과 같이 구성할 수 있습니다:
jasypt.encryptor.property.prefix=CodeSheep(
jasypt.encryptor.property.suffix)=
이 경우 암호화 항목을 CodeSheep()로 표시할 수 있습니다:
더 안전한 암호화
위와 같은 암호화 과정은 정보 보안 관련 설정 항목을 더 안전하게 만듭니다. 이는 분명한 사실입니다!
하지만 설정 파일 내 사용자 정의 암호화 키(jasypt.encryptor.password=CodeSheep)가 유출된다면 암호화 항목도 해독될 수 있습니다. 이를 방지하기 위해 몇 가지 작업이 필요합니다.
- 사용자 정의 암호화 알고리즘 적용
이전 실험에서는 기본 암호화 규칙을 사용했습니다. 하지만 사용자 정의 암호화 키가 유출될 경우 보안이 취약할 수 있습니다. 사용자 정의 암호화 규칙을 적용할 수 있습니다.
사용자 정의 암호화 규칙은 매우 간단합니다. 사용자 정의 암호화기 구성 클래스를 제공하면 됩니다. 예를 들어 사용자 정의 암호화기 이름을 codeSheepEncryptorBean으로 지정할 수 있습니다:
@Configuration
public class CodeSheepEncryptorCfg {
@Bean(name = "codeSheepEncryptorBean")
public StringEncryptor codesheepStringEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword("CodeSheep");
config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256");
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator");
config.setStringOutputType("base64");
encryptor.setConfig(config);
return encryptor;
}
}
참고로 Bean 이름은 명시적으로 지정해야 합니다(기본 이름은 jasyptStringEncryptor). 여기서 사용자 정의 이름을 사용한다면 Spring Boot의 application.properties 파일에서 Bean 이름을 지정해야 합니다:
jasypt.encryptor.bean=codeSheepEncryptorBean
- 암호화 키를 설정 파일에서 제거
암호화 키가 설정 파일에 존재하는 것도 위험 요소입니다. 세 가지 다른 방법으로 암호화 키를 처리할 수 있습니다:
- 방법 1: 프로그램 실행 시 커맨드라인 인수로 전달
java -jar yourproject.jar --jasypt.encryptor.password=CodeSheep
- 방법 2: 프로그램 실행 시 환경 변수로 전달
java -Djasypt.encryptor.password=CodeSheep -jar yourproject.jar
- 방법 3: 시스템 환경 변수로 전달 예를 들어 시스템 환경 변수 JASYPT_ENCRYPTOR_PASSWORD = CodeSheep를 설정하고, Spring Boot의 설정 파일에 다음과 같이 구성할 수 있습니다:
jasypt.encryptor.password=${JASYPT_ENCRYPTOR_PASSWORD:}
이렇게 하면 보안성이 크게 향상됩니다.