AES 암호화 방식을 통한 DBCP 암호화
암호화 처리는 어떤 데이터를 암호화 하느냐에 따라
일방향 암호화 처리를 할 것인지
양방향 암호화 처리를 할 것인지 결정하게 됩니다.
일방향암호화 : 사용자비밀번호와 같이 시스템관리자도 복호화가 필요없이 비교만 하면 되는 경우
양방향암호화 : 사용자이름, 휴대폰번호, 계좌번호 등 복호화하여 시스템운영상 확인이 필요한 경우
복호화가 필요한 경우는 암호화 했던 비밀키를 입력하여 풀어냅니다.
이번 포스팅에서는 tomcat 을 was 로 사용하는 경우
server.xml 에 평문으로 입력된 DB접속정보가 노출되어
2차 피해는 막자는 취지에서 샘플 소스를 공유합니다.
준비
AES 암호화를 위한 필요라이브러리
https://commons.apache.org/proper/commons-codec/download_codec.cgi
이클립스를 사용하는 경우
commons-codec-1.11-bin.zip 파일 다운로드 받은 후 commons-codec-1.11.jar 를 java build path 에 등록합니다.
아래는 server.xml 에 등록된 DB 접속정보입니다.
username , password , url 정보가 평문으로 입력되어 있어
인증되지 않은 사용자가 해당 파일을 오픈 했을 때 서버정보를 쉽게 알 수 있게 됩니다.
목표
username , password , url 와 같은 평문 저장을 원치 않는 속성을 암호화 처리
암호화 처리 방식 : AES-256
( SEED 방식도 많이 쓰니 다음에 정리하겠습니다..)
STEP 1.
라이브러리 등록 : commons-codec-1.11.jar
파일 다운로드 경로 : https://commons.apache.org/proper/commons-codec/download_codec.cgi
commons-codec-1.11-bin.zip 파일을 받아 압축을 풀면
해당 jar 라이브러리 파일이 존재하는데
Java Build Path에 등록합니다.
STEP 2
평문 데이터를 암호화 및 복호화 function 제공 ( AES256Util.java )
package com.encrypt; import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException; import java.security.Key; import java.security.NoSuchAlgorithmException; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.apache.commons.codec.binary.Base64; public class AES256Util { private String iv; private Key keySpec; public AES256Util(String key) throws UnsupportedEncodingException { this.iv = key.substring(0, 16); byte[] keyBytes = new byte[16]; byte[] b = key.getBytes("UTF-8"); int len = b.length; if(len>keyBytes.length){ len = keyBytes.length; } System.arraycopy(b, 0, keyBytes, 0 , len); SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); this.keySpec = keySpec; } public String encrypt(String str) throws NoSuchAlgorithmException, GeneralSecurityException, UnsupportedEncodingException { Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); c.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv.getBytes())); byte[] encrypted = c.doFinal(str.getBytes("UTF-8")); String enStr = new String(Base64.encodeBase64(encrypted)); return enStr; } public String decrypt(String str) throws NoSuchAlgorithmException, GeneralSecurityException, UnsupportedEncodingException { Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); c.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv.getBytes())); byte[] byteStr = Base64.decodeBase64(str.getBytes()); return new String(c.doFinal(byteStr), "UTF-8"); } }
작업 내용 : 암호화 / 복호화 처리 함수 제공
STEP 3
서버정보 암호화 내용 확인 ( TestMain.java )
package com.encrypt; import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; public class TestMain { public static void main(String[] args) throws UnsupportedEncodingException, NoSuchAlgorithmException, GeneralSecurityException { AES256Util aes = new AES256Util("testestestestses"); // 암호화 키 16자리 // 암호화 된 내용 System.out.println("scott : " + aes.encrypt("scott")); System.out.println("tiger : " + aes.encrypt("tiger")); } }
작업내용 : STEP 2 에서 작성한 함수를 main 을 통해서 테스트할 수 있습니다.
TestMain.java 실행결과
scott : BxoN1jurrKiXrlSSvf0/ng== tiger : JcHanmccsCwU4Z4NpnYQKg==
복호화 했을 때 입력했던 값을 받을 수 있다면 성공!
System.out.println(aes.decrypt("BxoN1jurrKiXrlSSvf0/ng==")); System.out.println(aes.decrypt("JcHanmccsCwU4Z4NpnYQKg=="));
STEP 4
암호화 된 정보를 복호화 하는 factory 를 생성
DBCP 의 기본은 org.apache.commons.dbcp.BasicDataSourceFacroty 를 사용하고 있는데
해당 class 파일을 decompile 후 아래와 같이 customizing 했습니다.
package com.encrypt; import java.io.ByteArrayInputStream; import java.io.UnsupportedEncodingException; import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; import java.util.Collections; import java.util.Enumeration; import java.util.Hashtable; import java.util.Properties; import java.util.StringTokenizer; import javax.naming.Context; import javax.naming.Name; import javax.naming.RefAddr; import javax.naming.Reference; import javax.naming.spi.ObjectFactory; import javax.sql.DataSource; import org.apache.tomcat.dbcp.dbcp.BasicDataSource; public class EncryptDataSourceFactory implements ObjectFactory { private static final String PROP_DEFAULTAUTOCOMMIT = "defaultAutoCommit"; private static final String PROP_DEFAULTREADONLY = "defaultReadOnly"; private static final String PROP_DEFAULTTRANSACTIONISOLATION = "defaultTransactionIsolation"; private static final String PROP_DEFAULTCATALOG = "defaultCatalog"; private static final String PROP_DRIVERCLASSNAME = "driverClassName"; private static final String PROP_MAXACTIVE = "maxActive"; private static final String PROP_MAXIDLE = "maxIdle"; private static final String PROP_MINIDLE = "minIdle"; private static final String PROP_INITIALSIZE = "initialSize"; private static final String PROP_MAXWAIT = "maxWait"; private static final String PROP_TESTONBORROW = "testOnBorrow"; private static final String PROP_TESTONRETURN = "testOnReturn"; private static final String PROP_TIMEBETWEENEVICTIONRUNSMILLIS = "timeBetweenEvictionRunsMillis"; private static final String PROP_NUMTESTSPEREVICTIONRUN = "numTestsPerEvictionRun"; private static final String PROP_MINEVICTABLEIDLETIMEMILLIS = "minEvictableIdleTimeMillis"; private static final String PROP_TESTWHILEIDLE = "testWhileIdle"; private static final String PROP_PASSWORD = "password"; private static final String PROP_URL = "url"; private static final String PROP_USERNAME = "username"; private static final String PROP_VALIDATIONQUERY = "validationQuery"; private static final String PROP_VALIDATIONQUERY_TIMEOUT = "validationQueryTimeout"; private static final String PROP_INITCONNECTIONSQLS = "initConnectionSqls"; private static final String PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED = "accessToUnderlyingConnectionAllowed"; private static final String PROP_REMOVEABANDONED = "removeAbandoned"; private static final String PROP_REMOVEABANDONEDTIMEOUT = "removeAbandonedTimeout"; private static final String PROP_LOGABANDONED = "logAbandoned"; private static final String PROP_POOLPREPAREDSTATEMENTS = "poolPreparedStatements"; private static final String PROP_MAXOPENPREPAREDSTATEMENTS = "maxOpenPreparedStatements"; private static final String PROP_CONNECTIONPROPERTIES = "connectionProperties"; private static final String[] ALL_PROPERTIES = { "defaultAutoCommit", "defaultReadOnly", "defaultTransactionIsolation", "defaultCatalog", "driverClassName", "maxActive", "maxIdle", "minIdle", "initialSize", "maxWait", "testOnBorrow", "testOnReturn", "timeBetweenEvictionRunsMillis", "numTestsPerEvictionRun", "minEvictableIdleTimeMillis", "testWhileIdle", "password", "url", "username", "validationQuery", "validationQueryTimeout", "initConnectionSqls", "accessToUnderlyingConnectionAllowed", "removeAbandoned", "removeAbandonedTimeout", "logAbandoned", "poolPreparedStatements", "maxOpenPreparedStatements", "connectionProperties" }; public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception { if ((obj == null) || (!(obj instanceof Reference))) { return null; } Reference ref = (Reference)obj; if (!"javax.sql.DataSource".equals(ref.getClassName())) { return null; } Properties properties = new Properties(); for (int i = 0; i < ALL_PROPERTIES.length; i++) { String propertyName = ALL_PROPERTIES[i]; RefAddr ra = ref.get(propertyName); if (ra != null) { String propertyValue = ra.getContent().toString(); properties.setProperty(propertyName, propertyValue); } } return createDataSource(properties); } public static DataSource createDataSource(Properties properties) throws Exception { BasicDataSource dataSource = new BasicDataSource(); String value = null; value = properties.getProperty("defaultAutoCommit"); if (value != null) { dataSource.setDefaultAutoCommit(Boolean.valueOf(value).booleanValue()); } value = properties.getProperty("defaultReadOnly"); if (value != null) { dataSource.setDefaultReadOnly(Boolean.valueOf(value).booleanValue()); } value = properties.getProperty("defaultTransactionIsolation"); if (value != null) { int level = -1; if ("NONE".equalsIgnoreCase(value)) { level = 0; } else if ("READ_COMMITTED".equalsIgnoreCase(value)) { level = 2; } else if ("READ_UNCOMMITTED".equalsIgnoreCase(value)) { level = 1; } else if ("REPEATABLE_READ".equalsIgnoreCase(value)) { level = 4; } else if ("SERIALIZABLE".equalsIgnoreCase(value)) { level = 8; } else { try { level = Integer.parseInt(value); } catch (NumberFormatException e) { System.err.println("Could not parse defaultTransactionIsolation: " + value); System.err.println("WARNING: defaultTransactionIsolation not set"); System.err.println("using default value of database driver"); level = -1; } } dataSource.setDefaultTransactionIsolation(level); } value = properties.getProperty("defaultCatalog"); if (value != null) { dataSource.setDefaultCatalog(value); } value = properties.getProperty("driverClassName"); if (value != null) { dataSource.setDriverClassName(value); } value = properties.getProperty("maxActive"); if (value != null) { dataSource.setMaxActive(Integer.parseInt(value)); } value = properties.getProperty("maxIdle"); if (value != null) { dataSource.setMaxIdle(Integer.parseInt(value)); } value = properties.getProperty("minIdle"); if (value != null) { dataSource.setMinIdle(Integer.parseInt(value)); } value = properties.getProperty("initialSize"); if (value != null) { dataSource.setInitialSize(Integer.parseInt(value)); } value = properties.getProperty("maxWait"); if (value != null) { dataSource.setMaxWait(Long.parseLong(value)); } value = properties.getProperty("testOnBorrow"); if (value != null) { dataSource.setTestOnBorrow(Boolean.valueOf(value).booleanValue()); } value = properties.getProperty("testOnReturn"); if (value != null) { dataSource.setTestOnReturn(Boolean.valueOf(value).booleanValue()); } value = properties.getProperty("timeBetweenEvictionRunsMillis"); if (value != null) { dataSource.setTimeBetweenEvictionRunsMillis(Long.parseLong(value)); } value = properties.getProperty("numTestsPerEvictionRun"); if (value != null) { dataSource.setNumTestsPerEvictionRun(Integer.parseInt(value)); } value = properties.getProperty("minEvictableIdleTimeMillis"); if (value != null) { dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(value)); } value = properties.getProperty("testWhileIdle"); if (value != null) { dataSource.setTestWhileIdle(Boolean.valueOf(value).booleanValue()); } value = properties.getProperty("password"); if (value != null) { dataSource.setPassword(decryptDBCPProperty(value)); } value = properties.getProperty("url"); if (value != null) { dataSource.setUrl(decryptDBCPProperty(value)); } value = properties.getProperty("username"); if (value != null) { dataSource.setUsername(decryptDBCPProperty(value)); } value = properties.getProperty("validationQuery"); if (value != null) { dataSource.setValidationQuery(value); } value = properties.getProperty("validationQueryTimeout"); if (value != null) { dataSource.setValidationQueryTimeout(Integer.parseInt(value)); } value = properties.getProperty("accessToUnderlyingConnectionAllowed"); if (value != null) { dataSource.setAccessToUnderlyingConnectionAllowed(Boolean.valueOf(value).booleanValue()); } value = properties.getProperty("removeAbandoned"); if (value != null) { dataSource.setRemoveAbandoned(Boolean.valueOf(value).booleanValue()); } value = properties.getProperty("removeAbandonedTimeout"); if (value != null) { dataSource.setRemoveAbandonedTimeout(Integer.parseInt(value)); } value = properties.getProperty("logAbandoned"); if (value != null) { dataSource.setLogAbandoned(Boolean.valueOf(value).booleanValue()); } value = properties.getProperty("poolPreparedStatements"); if (value != null) { dataSource.setPoolPreparedStatements(Boolean.valueOf(value).booleanValue()); } value = properties.getProperty("maxOpenPreparedStatements"); if (value != null) { dataSource.setMaxOpenPreparedStatements(Integer.parseInt(value)); } value = properties.getProperty("initConnectionSqls"); if (value != null) { StringTokenizer tokenizer = new StringTokenizer(value, ";"); dataSource.setConnectionInitSqls(Collections.list(tokenizer)); } value = properties.getProperty("connectionProperties"); if (value != null) { Properties p = getProperties(value); Enumeration e = p.propertyNames(); while (e.hasMoreElements()) { String propertyName = (String)e.nextElement(); dataSource.addConnectionProperty(propertyName, p.getProperty(propertyName)); } } if (dataSource.getInitialSize() > 0) { dataSource.getLogWriter(); } return dataSource; } private static Properties getProperties(String propText) throws Exception { Properties p = new Properties(); if (propText != null) { p.load(new ByteArrayInputStream(propText.replace(';', '\n').getBytes())); } return p; } private static String decryptDBCPProperty(String encryptStr) throws UnsupportedEncodingException, NoSuchAlgorithmException, GeneralSecurityException { AES256Util aes = new AES256Util("testestestestses"); return aes.decrypt(encryptStr); } }
위 소스에서 중요내용
username, password, url 에 해당하는 부분을 복호화하도록 변경
변경전 : dataSource.setPassword(value); 변경후 : dataSource.setPassword(decryptDBCPProperty(value));
눈치빠른 분들은 아시겠지만
server.xml 에서 입력된 username, password , url 을 암호화 하는 부분이었습니다.
( 위 3가지 정보 중 필요한 부분만 암호화 처리해도 됩니다. )
STEP 5
server.xml 에 암호화 된 내용으로 개인정보 변경
여기서 중요한 부분은 STEP 4 에서 만든 factory 를 지정하여
DBCP 할 때 사용할 수 있도록 합니다.
default 값은 BasicDataSourceFactory 로 별도로 지정해주지 않으면
tomcat-dbcp.jar 에 있는 내용을 사용하게 되니
반드시 우리가 만든 factory 를 설정해주시기 바랍니다.
여기까지 작업하면 해당 내용이 암호화 처리가 됩니다!
궁금한점이나 안되는 부분있으면 댓글 달아주세요^^
'IT > JAVA JSP' 카테고리의 다른 글
spring mybatis mysql 과 oracle 설정 방법 (0) | 2022.04.07 |
---|---|
jmeter webdriver selenium 내 iframe 인식/조작하는 방법 (0) | 2021.03.08 |
jMeter plugin webDriver 설치 및 사용방법 (0) | 2021.03.05 |
Cannot load JDBC driver class 'oracle.jdbc.driver.OracleDriver' 오류 해결 (0) | 2020.03.30 |
jquery 로 iframe form submit ( sample 포함 ) (0) | 2018.07.04 |