Jasypt(Java Simplified Encryption)

4 분 소요


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 빈으로 암호화기 객체를 만든다.

  1. 빈 이름은 기본 값을 사용한다.
    • 별도로 지정하지 않으면 메소드 이름이 빈 이름이 된다.
    • 메소드 이름을 jasyptStringEncryptor 으로 지정한다.
  2. 비공개 키는 환경 변수로 주입 받는다.
  3. 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 알고리즘을 사용한다.

  • 왼쪽 창에선 암호화, 오른쪽 창에선 복호화를 수행할 수 있다.
  • 양방향 암호화 타입을 사용한다.

https://www.devglan.com/online-tools/jasypt-online-encryption-decryption

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를 주입한다.
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)

TEST CODE REPOSITORY

REFERENCE

댓글남기기