Jasypt(Java Simplified Encryption)
0. 들어가면서
애플리케이션 설저에 노출되면 안 되는 민감한 정보들이 있다. 대표적으로 데이터베이스 접속 정보를 예로 들 수 있다. 환경 변수를 통해 주입해주는 것이 안전하지만, 암호화 된 값을 사용하는 방법도 존재한다. 이번 글에선 암호화 된 애플리케이션 설정 값을 사용할 수 있도록 돕는 라이브러리를 소개한다.
1. Jasypt(Java Simplified Encryption)
자바(java) 어플리케이션에서 설정 파일에 명시된 값들을 암호화, 복호화 할 수 있는 라이브러리다. 스프링 프레임워크도 자바 애플리케이션이기 때문에 Jasypt
를 사용할 수 있다. 평문(plain text)을 암호화 한 값을 아래 형식으로 application.yml 파일에 추가하면 된다. ENC(...)
내부에 지정된 값은 스프링 애플리케이션이 실행될 때 자동으로 복호화된다.
ENC(암호화 된 평문)
2. Practice
데이터베이스 사용자 이름과 비밀번호에 암호화 된 값을 지정하는 예제를 살펴보자.
2.1. Add dependency
스프링 부트(spring boot) 프레임워크를 사용한다면 다음 의존성을 추가한다.
<dependencies>
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
</dependencies>
스프링 MVC 프레임워크를 사용한다면 다음 의존성을 추가한다.
<dependencies>
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot</artifactId>
<version>3.0.4</version>
</dependency>
</dependencies>
스프링 MVC 프레임워크는 아래와 같이 @EnableEncryptableProperties 애너테이션으로 기능을 활성화 해야 한다.
@Configuration
@EnableEncryptableProperties
public class MyApplication {
...
}
2.2. Create Encryptor
이제 암호화기를 만든다. 스프링 빈 객체의 이름은 jasyptStringEncryptor
을 사용해야 한다. 변경하고 싶다면 아래와 같이 설정을 통해 변경한다.
jasypt.encryptor.bean
설정을 통해 빈 객체 이름을 변경한다.
jasypt:
encryptor:
bean: encryptorBean
스프링 빈 객체를 만들 때도 이름을 맞춰줘야 한다.
- @Bean 애너테이션에 이름을 지정한다.
@Configuration
public class JasyptConfig {
@Bean("encryptorBean")
public StringEncryptor stringEncryptor() {
...
}
}
이번 예제는 기본으로 지정된 jasyptStringEncryptor 이름을 사용한다.
2.3. Encryptor Configuration
암호화기를 만들 때 옵션을 지정할 수 있다.
- 비공개 키
- 암호화 알고리즘
- 인코딩(encoding) 타입
- 솔트(salt) 값 생성기
- 기타
비공개 키는 필수로 입력 받아야 한다. 나머지는 별도 설정이 없다면 다음과 같은 기본 값을 사용합니다.
Key | Required | Default Value |
---|---|---|
jasypt.encryptor.password | True | - |
jasypt.encryptor.algorithm | False | PBEWITHHMACSHA512ANDAES_256 |
jasypt.encryptor.key-obtention-iterations | False | 1000 |
jasypt.encryptor.pool-size | False | 1 |
jasypt.encryptor.provider-name | False | SunJCE |
jasypt.encryptor.provider-class-name | False | null |
jasypt.encryptor.salt-generator-classname | False | org.jasypt.salt.RandomSaltGenerator |
jasypt.encryptor.iv-generator-classname | False | org.jasypt.iv.RandomIvGenerator |
jasypt.encryptor.string-output-type | False | base64 |
jasypt.encryptor.proxy-property-sources | False | false |
jasypt.encryptor.skip-property-sources | False | empty list |
2.4. JasyptConfig Class
@Configuration 빈으로 암호화기 객체를 만든다.
- 빈 이름은 기본 값을 사용한다.
- 별도로 지정하지 않으면 메소드 이름이 빈 이름이 된다.
- 메소드 이름을 jasyptStringEncryptor 으로 지정한다.
- 비공개 키는 환경 변수로 주입 받는다.
- PBEWithMD5AndDES 알고리즘을 사용한다.
- 해당 사이트에서 PBEWithMD5AndDES 알고리즘으로 암복호화가 가능하다.
package action.in.blog.config;
import org.jasypt.encryption.StringEncryptor;
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JasyptConfig {
@Bean
public StringEncryptor jasyptStringEncryptor( // 1
@Value(value = "${jasypt.secret-key}") String secretKey // 2
) {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(secretKey);
config.setAlgorithm("PBEWithMD5AndDES");
config.setPoolSize(1);
encryptor.setConfig(config);
return encryptor;
}
}
위에서 언급했듯 웹 사이트에서 암호화 작업을 수행할 수 있다. 이 사이트는 PBEWithMD5AndDES
알고리즘을 사용한다.
- 왼쪽 창에선 암호화, 오른쪽 창에선 복호화를 수행할 수 있다.
- 양방향 암호화 타입을 사용한다.
2.5. applicaiton YAML
위 사이트에서 암호화 된 값을 application.yml 파일에 설정한다. 필자는 비공개 키로 HelloWorld
값을 사용했다.
spring.database.username
,spring.database.password
설정에 암호화 된 값을 사용한다.ENC()
으로 감싼다.
jasypt.secret-key
설정은 환경 변수를 사용한다.- 호스트 머신 혹은 컨테이너의 환경 변수를 사용해서 해당 값을 주입한다.
spring:
datasource:
url: jdbc:mysql://database-host:3306/mysql
driver-class-name: com.mysql.cj.jdbc.Driver
username: ENC(rCnFZG4bfEfOSrYhVUh0pA==)
password: ENC(xvEJVPJcJi8Ja1zeRhbX+w==)
jasypt:
secret-key: ${JASYPT_SECRET_KEY}
3. Run application
이 글의 예시에선 도커 컴포즈(docker compose)를 사용한다. 다음과 같은 도커 컴포즈 파일을 사용한다.
- 데이터베이스 설정
- 컨테이너 이름은
database-host
를 사용한다. - 사용자 이름은
root
, 비밀번호는123
을 사용한다.
- 컨테이너 이름은
- 어플리케이션 설정
- JASYPT_SECRET_KEY 환경 변수로 비공개 키
HelloWorld
를 주입한다.
- JASYPT_SECRET_KEY 환경 변수로 비공개 키
version: '3.8'
services:
mysql:
image: mysql
container_name: database-host
ports:
- '3306:3306'
environment:
- MYSQL_ROOT_PASSWORD=123
healthcheck:
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
timeout: 6s
retries: 10
backend:
build: .
ports:
- '8080:8080'
environment:
- JASYPT_SECRET_KEY=HelloWorld
depends_on:
mysql:
condition: service_healthy
restart: on-failure
도커 컴포즈를 실행해서 정상적으로 데이터베이스에 연결이 되는지 살펴보자. 데이터베이스와 연결이 정상적으로 수행되고 에러 없이 백엔드 애플리케이션이 실행된다.
$ docker-compose up
...
database-host | 2022-10-16T21:10:02.419584Z 0 [Warning] [MY-011810] [Server] Insecure configuration for --pid-file: Location '/var/run/mysqld' in the path is accessible to all OS users. Consider choosing a different directory.
database-host | 2022-10-16T21:10:02.433803Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Bind-address: '::' port: 33060, socket: /var/run/mysqld/mysqlx.sock
database-host | 2022-10-16T21:10:02.433871Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.31' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server - GPL.
action-in-blog-backend-1 |
action-in-blog-backend-1 | . ____ _ __ _ _
action-in-blog-backend-1 | /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
action-in-blog-backend-1 | ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
action-in-blog-backend-1 | \\/ ___)| |_)| | | | | || (_| | ) ) ) )
action-in-blog-backend-1 | ' |____| .__|_| |_|_| |_\__, | / / / /
action-in-blog-backend-1 | =========|_|==============|___/=/_/_/_/
action-in-blog-backend-1 | :: Spring Boot :: (v2.7.4)
action-in-blog-backend-1 |
action-in-blog-backend-1 | 2022-10-16 21:10:25.058 INFO 1 --- [ main] action.in.blog.ActionInBlogApplication : Starting ActionInBlogApplication v0.0.1-SNAPSHOT using Java 11.0.16 on 65972353bc86 with PID 1 (/app/app.jar started by root in /app)
action-in-blog-backend-1 | 2022-10-16 21:10:25.061 INFO 1 --- [ main] action.in.blog.ActionInBlogApplication : No active profile set, falling back to 1 default profile: "default"
...
action-in-blog-backend-1 | 2022-10-16 21:10:26.333 INFO 1 --- [ main] c.u.j.encryptor.DefaultLazyEncryptor : Found Custom Encryptor Bean org.jasypt.encryption.pbe.PooledPBEStringEncryptor@10ded6a9 with name: jasyptStringEncryptor
action-in-blog-backend-1 | 2022-10-16 21:10:26.452 INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
action-in-blog-backend-1 | 2022-10-16 21:10:26.781 INFO 1 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
action-in-blog-backend-1 | 2022-10-16 21:10:26.818 INFO 1 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
action-in-blog-backend-1 | 2022-10-16 21:10:26.858 INFO 1 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.6.11.Final
action-in-blog-backend-1 | 2022-10-16 21:10:27.006 INFO 1 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
action-in-blog-backend-1 | 2022-10-16 21:10:27.117 INFO 1 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
action-in-blog-backend-1 | 2022-10-16 21:10:27.314 INFO 1 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
...
action-in-blog-backend-1 | 2022-10-16 21:10:27.748 INFO 1 --- [ main] c.u.j.EncryptablePropertySourceConverter : Converting PropertySource servletContextInitParams [org.springframework.web.context.support.ServletContextPropertySource] to EncryptableEnumerablePropertySourceWrapper
action-in-blog-backend-1 | 2022-10-16 21:10:27.757 INFO 1 --- [ main] action.in.blog.ActionInBlogApplication : Started ActionInBlogApplication in 3.168 seconds (JVM running for 3.511)
댓글남기기