개발 메모장

[보안] Jasypt를 이용한 민감정보 암호화 본문

보안

[보안] Jasypt를 이용한 민감정보 암호화

yyyyMMdd 2024. 3. 12. 11:41
728x90
  • 개인정보가 포함된 서비스의 소스에 DB접근 정보가 평문화 되어있어 암호화를 해야 했습니다.

  • 검색하여 찾아본 결과 모든 내용이 Jasypt라는 라이브러리를 이용해 처리하고 있음을 확인하고 적용했습니다.

  • Spring-boot로 새로 만들어 처리할 때에는 아주 쉽게 처리가 가능했으나 해당 서비스는 기존에 설정된 것들이 복잡하게 얽혀있어 처리에 애를 먹었습니다.

  • 적용하는 과정에서의 방법에 대해 공유하고자 합니다.

#. Jasypt의 특징

 

  1. 암호화 및 복호화
    - 비밀번호, DB 연결 정보 및 기타 기밀 정보와 같은 민감한 데이터를 암호화하고 복호화가 가능합니다.

  2. 다양한 암호화 알고리즘 지원
    - AES, DES, Triple DES, PBE 등을 포함한 다양한 암호화 알고리즘을 지원합니다. 
    - 개발자는 보안 요구 사항에 따라 가장 적합한 암호화 알고리즘을 선택할 수 있습니다.

  3. Java 애플리케이션과의 통합
    - 웹 애플리케이션, 데스크톱 애플리케이션 및 백엔드 시스템을 포함한 Java 애플리케이션에 쉽게 통합될 수 있습니다.

  4. 사용 용이성
    - 암호화 및 복호화 프로세스의 복잡성을 추상화하여 개발자가 낮은 수준의 암호화 세부 사항을 처리하지 않고도 보안 기능 구현에 집중할 수 있도록 합니다.

  5. 구성 기반 암호화
    - 개발자는 간단한 구성 설정을 사용하여 데이터베이스 비밀번호와 같은 민감한 구성 속성을 암호화할 수 있습니다. 
    - 코드를 크게 변경하지 않고도 구성 파일에 저장된 중요한 정보를 보호하는 데 도움이 됩니다.

  6. 명령줄 인터페이스
    - 사용자가 콘솔에서 데이터를 암호화하고 해독할 수 있는 CLI 도구를 제공합니다. 

#. Jasypt가 제공하는 암호화 알고리즘

 

  • PBEWithMD5AndDES
    - MD5 다이제스트 및 DES 대칭 암호화를 사용합니다.

  • PBEWithMD5AndTripleDES
    - PBEWithMD5AndDES과 비슷하지만 더 강력한 암호화를 위해 Triple DES(3DES)를 사용합니다.

  • PBEWithSHA1AndDESede
    - SHA-1 다이제스트와 Triple DES를 결합합니다.

  • PBEWithSHA1AndRC2_40
    - 40바이트 키와 함께 SHA-1 다이제스트 및 RC2를 사용합니다.

  • PBEWithSHA1AndRC4_128
    - SHA-1 다이제스트와 RC4를 128바이트 키와 결합합니다.

  • PBEWithSHAAnd128BitAES-CBC-BC/PKCS5Padding
    - CBC 모드에서 128바이트 키와 함께 SHA 다이제스트 및 AES를 사용합니다.

  • PBEWithSHAAnd256BitAES-CBC-BC/PKCS5Padding
    - PBEWithSHAAnd128BitAES-CBC-BC/PKCS5Padding와 비슷하지만 256바이트 AES 키를 사용합니다.

#. 암호화 및 복호화 CLI 사용방법

 

  • http://www.jasypt.org/download.html  클릭합니다.

  • 라이브러리 버전에 맞게 다운로드 후 압축해제하기

  • cmd > 압축 푼 경로 및 bin폴더까지 접속

  • 전체 경로 중 띄어쓰기가 포함된 폴더 등의 경로가 있다면
    "오류: 기본 클래스 org.jasypt.intf.cli.JasyptPBEStringEncryptionCLI을(를) 찾거나 로드할 수 없습니다."가 발생합니다.

  • encrypt inpuit="암호화할내용" password="소금값" algorithm="PBEWITHMD5ANDDES"으로 작성
    복호화의 경우 encrypt -> decrypt로 변경하여 처리

  • 암호화 처리


  • 복호화 처리

 


#. 적용방법

 

  • xml에 DB정보를 저장해 놓은 상태였으나 xml에 jasypt를 bean으로 설정하고 그 경로를 xml파일로 잡게 되면 제대로 처리가 되지 않았습니다.

  • xml 파일의 DOCTYPE이 NULL이고 jasypt의 configurationEncryptor bean의 프로퍼티 name은 config라 맞지 않음 -> DOCTYPE을 config로 맞추면 .metadata에 dtd 파일이 없다 -> dtd파일을 강제로 넣어주면 값을 읽을 수 없다.
    같은 내용의 오류가 발생하였습니다.

  • 그리하여 xml에 직접 연결하여 처리하는 방법에 한계를 느껴 xml에 있는 DB정보만 properties 파일 옮겨 처리하는 방법을 택하였습니다.

  • 기존 설정 자체가 xml 파일 기준으로 되어있어 삽질도 많이 했습니다.

1. 라이브러리 추가(maven)

  • spring 3.1 이후 버전의 호환 및 지원 가능합니다.
<dependency>
    <groupId>org.jasypt</groupId>
    <artifactId>jasypt-spring31</artifactId>
    <version>1.9.3</version>
</dependency>

 


2. context.xml 내 bean 설정

  • 우선 기존의 xml 내용을 간략히 살펴보면 placeholder의 경로는 이미 잡혀있는 상태입니다.

  • context.xml의 내용입니다.
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
	<property name="properties">
		<bean class="org.springmodules.commons.configuration.CommonsConfigurationFactoryBean">
			<property name="configurations">
				<list>
					<ref bean="configuration" />
				</list>
			</property>
		</bean>
	</property>
</bean>

<bean id="configuration" class="org.apache.commons.configuration.CompositeConfiguration">
	<constructor-arg>
		<list>
			<bean class="org.apache.commons.configuration.XMLConfiguration">
				<constructor-arg type="java.lang.String">
					<value>config/config.xml</value>
				</constructor-arg>
			</bean>
		</list>
	</constructor-arg>
</bean>

<bean id="dataSourceSpied" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
	<property name="driverClassName" value="${jdbc.driverName}"/>
	<property name="url" value="${jdbc.url}"/>
	<property name="username" value="${jdbc.username}"/>
	<property name="password" value="${jdbc.password}"/>
</bean>

 

  • DB정보가 담긴 config.xml 입니다.
<?xml version="1.0" encoding="UTF-8"?>
<config>
	<jdbc>
		<driverName>com.microsoft.sqlserver.jdbc.SQLServerDriver</driverName>
		<url>DB URL</url>
		<id>접근 계정</id>
		<pw>접근 암호</pw>
	</jdbc>
</config>

 

  • jasypt 사용 시에도 placeholder를 지정하게끔 되어있습니다.

  • placeholder가 2개가 되면 오류가 발생했고, 또한 기존과 같이 configUtil을 이용해 값은 뽑아낼 수 있어야 했으며 dataSource bean에 변수로 들어가는 값이 db.properties를 봐야 했기에 기존 bean인 PropertyPlaceholderConfigurer 삭제하였습니다.

  • placeholder는 중복으로 사용이 불가한 것 같습니다.
<bean id="propertyConfigurer" class="org.jasypt.spring31.properties.EncryptablePropertyPlaceholderConfigurer">
	<constructor-arg ref="configurationEncryptor" />
	<property name="location" value="classpath:config/db.properties" />
</bean>

<bean id="environmentVariablesConfiguration" class="org.jasypt.encryption.pbe.config.EnvironmentStringPBEConfig">
	<property name="algorithm" value="PBEWithMD5AndDES"/>
	<!-- <property name="password" value="test"/> -->
	<property name="passwordEnvName" value="JASYPT_PASSWORD"/>
</bean>

<bean id="configurationEncryptor" class="org.jasypt.encryption.pbe.StandardPBEStringEncryptor">
	<property name="config" ref="environmentVariablesConfiguration"/>
</bean>
  • 여기서 한 가지 주목하실 점은 password입니다.

  • 테스트하실 때에는 password에 작성하셔도 되지만 솔트값이 노출되면 복호화가 가능하기에 운영서버 적용 시에는 OS 환경변수에서 지정하고 그 환경변수를 호출해 처리해줘야 합니다.

  • 환경변수는 property name을 passwordEnvName으로 설정해야 합니다.(password로 설정하고 값을 넣으면 읽어오지 못합니다.)

3. 환경변수 설정

  • 환경변수 설정은 간단하며 아래와 같이 설정하면 됩니다.

 


4. properties에 적용하기

  • properties를 생성하여 아래와 같이 property를 작성하고, 그 값은 상단에 명시한 방법을 통해 암호화한 값을 넣어줍니다.

  • 값을 넣을 땐 ENC() 내에 넣어야 복호화가 정상적으로 처리되어 DB와 연결됩니다.

  • Jasypt의 placeholder에서 새로 지정한 경로의 db.properties입니다.
database.driverName=com.microsoft.sqlserver.jdbc.SQLServerDriver

database.url=ENC(CtVN5KhL0CFmGrx/T9YHD/n29uOe1eXQIv5JXQRR+UIL3amV7I9xX25G5SPoTkv+OuzWFihD6Gtsaccl8L0KN3iCfPbwMTR+)
database.username=ENC(4/F4Xv+HsFtXoFjZRgZevacKsjpMn0E6)
database.password=ENC(bof7zWWSWW1fwFNUVkK/J/WtmXoZ6/Hc)

jasypt.encryptor.password=${JASYPT_PASSWORD}

 

  • 또한 properties에서 database를 사용해 url, username, password를 처리했으므로 context.xml의 dataSourceSpied bean의 파라미터가 값도 {jdbc.url}이 아닌 {database.url}과 같이 수정해줘야 합니다.

  • 이렇게 적용하면 DB에 접근이 가능합니다.

5. 로직 내 사용방법

  • 로직 내에서 운영 / 개발서버에 따라 로직이 달라지게 되는 경우 서버 값을 가져와 사용해야 합니다.

  • 그러나 암호화가 되어있기에 복호화 처리를 해줘야 하므로 복호화 로직을 구현합니다.

  • 대략적인 호출방법은 아래와 같습니다.
import org.springframework.core.env.Environment;

@Controller
@PropertySource({"classpath:/config/dbConfig.properties"})
public class TestController {
	
	@Autowired
	private Environment env;
	
	@RequestMapping("/test.do")
	public void test123() {
		String server = decryptDbProperty(env.getProperty("data.url"), env.getProperty("jasypt.encryptor.password"))
	}
    
	public static String decryptDbProperty(String key, String pw) {
		StandardPBEStringEncryptor enc = new StandardPBEStringEncryptor();
		enc.setAlgorithm("PBEWithMD5AndDES");
		enc.setPassword(pw);

		if(key.startsWith("ENC(") && key.endsWith(")")) {
			key = key.substring(4, key.length() -1);
		}
		return enc.decrypt(key);
	}
}

 

  • @PropertySource를 이용해 외부 프로퍼티를 호출합니다.

  • 호출한 데이터를 변수에 담는 방법은 @Value를 사용해도 되고 위처럼 Environment의 getProperty를 사용해도 됩니다.

  • 복호화 시엔 알고리즘과 솔트값인 password 등 지정한 세팅값과 동일하게 세팅해줘야 하며 ENC()를 그대로 호출해 오기 때문에 ENC()를 제거하는 작업이 필요하니 참고해 주시길 바랍니다.

#. Spring Boot에서는 아래와 같이 처리했고 위 내용과 중첩되는 내용이 많아 간략하게만 설명하도록 하겠습니다.

 

1. 라이브러리 추가

implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5'

 

2. Config 파일 생성

@Configuration
@EnableEncryptableProperties
public class JasyptAESConfig {
	
	@Bean("jasyptEncryptorAES")
	public StringEncryptor stringEncryptor() {
		PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
		SimpleStringPBEConfig config = new SimpleStringPBEConfig();
		
		config.setPassword("tester"); // 소금값
		config.setAlgorithm("PBEWITHMD5ANDDES"); // 알고리즘
		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;
	}
}

 

3. 테스트 파일 생성하여 암호화(CLI -> 소스 내 코드로 대체)

class JasyptAESConfigTest {

	@Test
	void testStringEncryptor() {
		String url = "암호화 할 url";
		String username = "암호화 할 유저명";
		String password = "암호화 할 비밀번호";
		
		System.out.println("===========> " + jasyptEncoding(url));
		System.out.println("===========> " + jasyptEncoding(username));
		System.out.println("===========> " + jasyptEncoding(password));
	}
	
	public String jasyptEncoding(String value) {
		String key = "tester"; // 소금값
		StandardPBEStringEncryptor pbeEnc = new StandardPBEStringEncryptor();
		pbeEnc.setAlgorithm("PBEWITHMD5ANDDES");
		pbeEnc.setPassword(key);
		pbeEnc.setIvGenerator(new RandomIvGenerator());
		return pbeEnc.encrypt(value);
	}
}

 

4. properties 적용

spring.datasource.url=ENC(7JINp6zuKJ3I4S2hsUaPgaqwx5BCwmwjE88x1bQxCnqQLaD5NbZXWHDeUVUfeQp8IbzjJHIGIhR2xtS5BZNuSbOj5JQUFcfn)
spring.datasource.username=ENC(VTIl4ruh6p37XNcLTa2qAUpV/2pD5LcK)
spring.datasource.password=ENC(GibMit23TKHr80Aw/vPUa/roiXkgO8pq)
spring.datasource.driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver

jasypt.encryptor.password=tester // 소금값
jasypt.encryptor.bean=jasyptEncryptorAES // bean 값

 

 

 

===========================================================
틀린 내용이 있거나 이견 있으시면 언제든 가감 없이 말씀 부탁드립니다!

===========================================================

 

 

728x90