jdbc properties 암호화(jasypt를 안쓰고)

22_gas·2024년 6월 21일

초안 2022. 12. 6 작성

개요

spring에서 DB접속 정보를 properties로 관리하게 되는데, 서버 보안 점검툴로 검출하게되면
항상 이 부분에서 보안취약점이 걸리게 된다. 그래서 접속정보를 암호화 할 방법을 찾게 되고 가장 일반적으로 널리 쓰는 방식으로는 jasypt 라이브러리를 활용한 방법이 존재한다.

jasypt

첨엔 쉽게 적용할수 있어서 좋아했다.

jdbc.properties

jdbc.driverClassName=net.sf.log4jdbc.DriverSpy
jdbc.url=jdbc:log4jdbc:mysql://xx.x.x.x:3306/database?....

jdbc.username=root
jdbc.password=ENC(YzFSlZP38ibqrWQ==)
jdbc.validationQuery=select 1

스프링 설정(xml)도 간단하다.

<...생략>
<bean id="propertyConfigurer" class="org.jasypt.spring4.properties.EncryptablePropertyPlaceholderConfigurer">
	<constructor-arg ref="configurationEncryptor"/>
	<property name="location" value="classpath:config/jdbc.properties"/>
</bean>
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
	<property name="driverClassName" value="${jdbc.driverClassName}" />
	<property name="url" value="${jdbc.url}" />
	<property name="username" value="${jdbc.username}" />
	<property name="password" value="${jdbc.password}" />
	<property name="initialSize" value="5"/>
	<property name="maxTotal" value="20"/>
	<property name="maxIdle" value="20"/>
	<property name="minIdle" value="5"/>
	<property name="validationQuery" value="${jdbc.validationQuery}" />
</bean>

익이 봐왔던 코드일것이다.

여기서 문제가 발생하게 되는데 

암호화 방식 통일의 필요성

엔지니어의 경우 실제 서버에 패치를 해야하고 암호문을 만들어내야 하는데, 암호화를 적용해야하는 부분이 늘어남에 따라 각각 다른 암호화 방식을 채택하고 있어 불편함이 있었다.

[DB테이블 안에 계정정보, 서버 접속정보config 파일,  웹 jdbc 설정 정보]의 암호화 방식을 한가지로 통일하자는 의견이 나왔고 해당 부분의 개발을 맡게 되었다.

암호화 방식 통일

Mysql의 AES_ENCRYPT()

납품하는 프로그램의 기본 DB인 Mysql에서 기본 제공하는 AES_ENCRYPT()라는 함수를 활용하자는 의견이 나왔다.

암호화 적용기

jasypt의 'PBEWITHSHA256AND128BITAES-CBC-BC' 알고리즘

찾아보니 jasypt의 알고리즘리스트에  'PBEWITHSHA256AND128BITAES-CBC-BC'가 있다고 해서 테스트해보았다 

1. jdk1.8.0_101/jer/lib/ext 에 bcprov-jdk18on-172.jar를 넣고

2. jdk1.8.0_101/jer/lib/security/java.security 제일 마지막에

security.provider.2=org.bouncycastle.jce.provider.BouncyCastleProvider 도 넣고 테스트코드 실행

PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
encryptor.setProvider(new BouncyCastleProvider());
encryptor.setPassword("somePassword");
encryptor.setAlgorithm("PBEWITHSHA256AND128BITAES-CBC-BC");
encryptor.setSaltGenerator(new StringFixedSaltGenerator(""));

new BouncyCastleProvider(); 에서 계속 실패 .... 결국 실패 했다. 

EncryptablePropertyPlaceholderConfigurer 수정

그럼 EncryptablePropertyPlaceholderConfigurer 를 뜯어보자

EncryptablePropertyPlaceholderConfigurer 는 propertyPlaceholderConfigurer를 상속받아 사용하는데

propertyPlaceholderConfigurer에 setLocation하기 전에 조작해볼수 있지 않을까?

@Bean
public static PropertySourcesPlaceholderConfigurer loadProperties(){
    PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
    Resource[] resourceLocations = new Resource[] {
            new ClassPathResource("jdbc.properties")           
    };
    pspc.setLocations(resourceLocations);
    pspc.setIgnoreUnresolvablePlaceholders(true);
    return pspc;    

}

여기까지 하고 멈칫... resource 안에 값을 어떻게 바꿔야될지 감이 안잡혔다.

DataSource 생성부분 분리

그래서 DataSource를 만드는 부분을 class로 빼서 
@Value({jdbc.url}) 요런거 사용하는 부분을 바꿔보려 했는데 

xml 하단에서 dataSource를 못읽어서 실패... (내가 뭔가 잘못한듯)

DbConfig class 활용

결국 다 실패하고 DBConfig 라는 클래스로 활용

public class DbConfig {

    /* maria 세팅 */
    private static String jdbcDriverClassName;
    private static String jdbcUrl;
    private static String jdbcUserName;
    private static String jdbcPassword;
    private static String jdbcValidationQuery;


    /**
     * @Method setJdbcProperties
     * @Date 2022. 12. 02
     * @Writter 22gas
     * @Return void
     * @Discript jdbc_maria.properties 셋팅
     */
    public void setJdbcProperties(Properties props) {
        DbConfig.jdbcDriverClassName = getPropertiesValue(StringUtils.trim(props.getProperty("jdbc.driverClassName")));
        DbConfig.jdbcUrl = getPropertiesValue(StringUtils.trim(props.getProperty("jdbc.url")));
        DbConfig.jdbcUserName = getPropertiesValue(StringUtils.trim(props.getProperty("jdbc.username")));
        DbConfig.jdbcPassword = getPropertiesValue(StringUtils.trim(props.getProperty("jdbc.password")));
        DbConfig.jdbcValidationQuery = getPropertiesValue(StringUtils.trim(props.getProperty("jdbc.validationQuery")));

    }

    /**
     * 프로퍼티 값을 읽어서 복호화 해야하는지 아닌지 판단한다.
     * ENC() 로 되어있으면 복호화 하고 아니면 그냥 쓴다.
     * @param _value
     * @return String
     */
    private String getPropertiesValue(String _value){
        if(_value.startsWith("ENC(") && _value.endsWith(")")){
            try{
                String _encStr = _value.replace("ENC(", "").replace(")", "");
                Cipher decryptCipher = Cipher.getInstance("AES");
                decryptCipher.init(Cipher.DECRYPT_MODE, generateMySQLAESKey(AppGlobal.dbKey));
                String _decryptStr = new String(decryptCipher.doFinal(Base64.getDecoder().decode(_encStr)));
                return _decryptStr;
            } catch (Exception e){
                return _value;
            }
        } else {
            return _value;
        }
    }

    /**
     * mysql암호화를 위한 필요작업
     * @param key
     * @return
     */
    private SecretKeySpec generateMySQLAESKey(final String key) {
        try {
            final byte[] finalKey = new byte[16];
            int i = 0;
            for(byte b : key.getBytes("UTF-8"))
                finalKey[i++%16] ^= b;
            return new SecretKeySpec(finalKey, "AES");
        } catch(UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
 
    //.... getter, setter
 }

xml 에서는  dbConfig['변수명'] 으로 사용...

<!-- jdbc_maria.properties를 읽어서 dataBase로 사용 -->
<bean id="dataBase" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
	<property name="location" value="classpath:config/jdbc.properties" />
	<property name="fileEncoding" value="UTF-8" />
</bean>

<!-- 위에서 만든 dataBase를 DbConfig 클래스에 매핑 -->
<bean id="dbConfig" class="path.to.DbConfig">
	<property name="jdbcProperties" ref="dataBase" />
</bean>

<!-- 위에서 만든 dbConfig로 내부 값 접근-->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
	<property name="driverClassName" value="#{dbConfig['jdbcDriverClassName']}" />
	<property name="url" value="#{dbConfig['jdbcUrl']}" />
	<property name="username" value="#{dbConfig['jdbcUserName']}" />
	<property name="password" value="#{dbConfig['jdbcPassword']}" />
	<property name="initialSize" value="5"/>
	<property name="maxTotal" value="20"/>
	<property name="maxIdle" value="20"/>
	<property name="minIdle" value="5"/>
	<property name="validationQuery" value="#{dbConfig['jdbcValidationQuery']}" />
</bean>

※ xml 에서 dbConfig['변수명'] 으로 접근하게 되는데 getter가 없다면 에러가 뜬다... 참고!

profile
전 아직 모르는게 많아요

0개의 댓글