반응형



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 관련 프로그래밍 초급 & 고급 정보를 공유하는 블로그

,