본문 바로가기
오픈소스/Tomcat

[Tomcat 8.5] 데이터소스 DB 정보 암호화 (id/pw)

by sangyeon 2022. 11. 22.
728x90

1.   배경 및 목적

Tomcat 서버의 DB정보에 관한 설정은 server.xml 혹은 context.xml 파일에 존재한다. 해당 정보에는 DB 접속에 필요한 id/pw가 있기 때문에 암호화되지 않는다면 계정 정보가 탈취될 가능성이 존재하여 보안에 취약하기 때문에 반드시 계정 정보를 암호화해야 한다.
이 글에서 테스트 환경은 아래와 같다.

Server version: Apache Tomcat/8.5.81
Server built:   Jun 8 2022 21:30:15 UTC
Server number:  8.5.81.0
OS Name:        Linux
OS Version:     5.10.118-111.515.amzn2.x86_64
Architecture:   amd64
JVM Version:    1.8.0_312-b07
JVM Vendor:     Red Hat, Inc.

 

2.   구현

먼저 암호화 코드를 패키징 해주어 라이브러리 형태로 제공한다. Eclipse를 사용하여 소스 패키징을 하였다.
소스 패키징에 필요한 코드는
EncryptedDataSourceFactor.java, Encryptor.java이다.

2.1.  Eclipse 소스 패키징 작업

2.1.1.     소스 패키징을 위한 프로젝트 생성

  EncryptModule이라는 프로젝트를 생성하여 ‘secured’ 패키지 안에 소스를 생성하였다.
- JRE System Library
추가 방법 : https://lee-mandu.tistory.com/289
- Server Runtime
추가 방법 :
https://heoseongh.github.io/jsp/tip-eclipse-setting-RuntimeServerEnvironment/

 

2.1.2.     EncryptedDataSourceFactor.java

3.     package secured;
4.      
5.     import java.io.UnsupportedEncodingException;
6.     import java.security.InvalidKeyException;
7.     import java.security.NoSuchAlgorithmException;
8.     import java.sql.SQLException;
9.     import java.util.Properties;
10.   
11.  import javax.crypto.BadPaddingException;
12.  import javax.crypto.IllegalBlockSizeException;
13.  import javax.crypto.NoSuchPaddingException;
14.  import javax.naming.Context;
15.  import javax.sql.DataSource;
16.   
17.  import org.apache.tomcat.jdbc.pool.DataSourceFactory;
18.  import org.apache.tomcat.jdbc.pool.PoolConfiguration;
19.  import org.apache.tomcat.jdbc.pool.XADataSource;
20.   
21.   
22.  public class EncryptedDataSourceFactory extends DataSourceFactory {
23.   
24.      private Encryptor encryptor = null;
25.   
26.      public EncryptedDataSourceFactory() {
27.          try {
28.              encryptor = new Encryptor(); // If you've used your own secret key, pass it in...
29.          } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedEncodingException e) {
30.            System.out.println("Error instantiating decryption class." + e);
31.              throw new RuntimeException(e);
32.          }
33.      }
34.   
35.      @Override
36.      public DataSource createDataSource(Properties properties, Context context, boolean XA) throws InvalidKeyException,
37.              IllegalBlockSizeException, BadPaddingException, SQLException, NoSuchAlgorithmException,
38.              NoSuchPaddingException {
39.          // Here we decrypt our password.
40.          PoolConfiguration poolProperties = EncryptedDataSourceFactory.parsePoolProperties(properties);
41.          poolProperties.setUsername(encryptor.decrypt(poolProperties.getUsername()));
42.          poolProperties.setPassword(encryptor.decrypt(poolProperties.getPassword()));
43.   
44.          // The rest of the code is copied from Tomcat's DataSourceFactory.
45.          if (poolProperties.getDataSourceJNDI() != null && poolProperties.getDataSource() == null) {
46.              performJNDILookup(context, poolProperties);
47.          }
48.          org.apache.tomcat.jdbc.pool.DataSource dataSource = XA ? new XADataSource(poolProperties)
49.                  : new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);
50.          dataSource.createPool();
51.   
52.          return dataSource;
53.      }
54.  }

 

2.1.3  Encryptor.java

해당 소스에서 main 메소드 안에 testid, testpassword 변수에 실제 DB 접속에 필요한 ID/PW 정보를 입력한 뒤, 소스를 실행시켜 암호화된 값을 잘 보관한다.
※ 추후에 암호화된 값을 tomcat 데이터소스 설정 값에 넣어줄 예정

2.       package secured;
3.        
4.       import java.io.UnsupportedEncodingException;
5.       import java.security.InvalidKeyException;
6.       import java.security.Key;
7.       import java.security.MessageDigest;
8.       import java.security.NoSuchAlgorithmException;
9.       import java.util.Arrays;
10.     
11.    import javax.crypto.BadPaddingException;
12.    import javax.crypto.Cipher;
13.    import javax.crypto.IllegalBlockSizeException;
14.    import javax.crypto.KeyGenerator;
15.    import javax.crypto.NoSuchPaddingException;
16.    import javax.crypto.spec.SecretKeySpec;
17.     
18.    public class Encryptor {
19.          private static final String ALGORITHM = "AES";
20.     
21.          private static final String defaultSecretKey = "asdfo23jda3sds";
22.     
23.          private Key secretKeySpec;
24.     
25.          public Encryptor()
26.                                     throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException {
27.                       this(null);
28.          }
29.     
30.          public Encryptor(String secretKey)
31.                                     throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException {
32.                       this.secretKeySpec = generateKey(secretKey);
33.          }
34.     
35.          public String encrypt(String plainText) throws InvalidKeyException, NoSuchAlgorithmException,
36.                                     NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
37.                       Cipher cipher = Cipher.getInstance(ALGORITHM);
38.                       cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
39.                       byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
40.                       return asHexString(encrypted);
41.          }
42.     
43.          public String decrypt(String encryptedString) throws InvalidKeyException, IllegalBlockSizeException,
44.                                     BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
45.                       Cipher cipher = Cipher.getInstance(ALGORITHM);
46.                       cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
47.                       byte[] original = cipher.doFinal(toByteArray(encryptedString));
48.                       return new String(original);
49.          }
50.     
51.          private Key generateKey(String secretKey) throws UnsupportedEncodingException, NoSuchAlgorithmException {
52.                       if (secretKey == null) {
53.                                     secretKey = defaultSecretKey;
54.                       }
55.                       byte[] key = (secretKey).getBytes("UTF-8");
56.                       MessageDigest sha = MessageDigest.getInstance("SHA-1");
57.                       key = sha.digest(key);
58.                       key = Arrays.copyOf(key, 16); // use only the first 128 bit
59.     
60.                       KeyGenerator kgen = KeyGenerator.getInstance("AES");
61.                       kgen.init(128); // 192 and 256 bits may not be available
62.     
63.                       return new SecretKeySpec(key, ALGORITHM);
64.          }
65.     
66.          private final String asHexString(byte buf[]) {
67.                       StringBuffer strbuf = new StringBuffer(buf.length * 2);
68.                       int i;
69.                       for (i = 0; i < buf.length; i++) {
70.                                     if (((int) buf[i] & 0xff) < 0x10) {
71.                                                  strbuf.append("0");
72.                                     }
73.                                     strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
74.                       }
75.                       return strbuf.toString();
76.          }
77.     
78.          private final byte[] toByteArray(String hexString) {
79.                       int arrLength = hexString.length() >> 1;
80.                       byte buf[] = new byte[arrLength];
81.                       for (int ii = 0; ii < arrLength; ii++) {
82.                                     int index = ii << 1;
83.                                     String l_digit = hexString.substring(index, index + 2);
84.                                     buf[ii] = (byte) Integer.parseInt(l_digit, 16);
85.                       }
86.                        return buf;
87.          }
88.     
89.          public static void main(String[] args) throws Exception {
90.                       Encryptor aes = new Encryptor(defaultSecretKey);
91.     
92.                       String testid = "DB 접속 계정";
93.                       System.out.println("Id:" + aes.encrypt(testid));
94.     
95.                       String testpassword = "DB 접속 패스워드";
96.                       System.out.println("Pw:" + aes.encrypt(testpassword));
97.          }
98.    }

 

# 소스 수행 결과, 암호화된 ID/PW 정보

 

2.1.4. 프로젝트 Export

   마지막으로 생성한 프로젝트를 jarExport 해주어야 한다.
프로젝트 우클릭 > Export > jar 입력 > 추출할 jar 파일명 입력 후 Finish 선택한다.
이렇게 생성한 EncryptModule.jar 파일은 ${TOMCAT_HOME}/lib에 위치 시켜준다.
- EncryptModule.jar
소스 파일 : https://github.com/Hwang-sangyeon/tomcat.git


 

 

2.2.  Tomcat 설정

  2.2.1. context.xml

   Context.xml 파일에 데이터소스 설정을 추가해준다. 파란 음영 부분은 반드시 주의를 기울여 작성해야 한다. Factory의 경우에는 EncryptModule.jar 안의 패키지 클래스이므로 ${TOMCAT_HOME}/lib 아래에 해당 jar 파일이 반드시 존재해야 한다.
또한 위에서 Encryptor.java를 수행하여 얻은 암호화된 DB 계정정보(ID/PW)를 아래에 입력해준다.

<Context>
<Resource name="jdbc/testDB"
       auth="Container"
       factory="secured.EncryptedDataSourceFactory"
       type="javax.sql.DataSource"
       username="e36f8d62b9e99bcdb14e224d5787433d"
       password="e1ce32d849b75a52813759a63d2358aa"
       driverClassName="com.mysql.jdbc.Driver"
       url="jdbc:mysql://localhost:3306/employees"
       maxActive="15"
       maxIdle="3"/>
</Context>

 

  2.2.2. web.xml

   ${APP_HOME}/WEB-INF/web.xml에 알맞게 설정한다. (jdbc/testDB)

<web-app>
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">
  <description>MySQL Test App</description>
  <resource-ref>
      <description>DB Connection</description>
      <res-ref-name>jdbc/testDB</res-ref-name>
      <res-type>javax.sql.DataSource</res-type>
      <res-auth>Container</res-auth>
  </resource-ref>
</web-app>

 

  2.2.3. testDB.jsp

   마지막으로 DB접속을 해볼 테스트 jsp페이지를 생성해본다. 데이터소스 룩업(Lookup)하는 부분에 알맞은 데이터소스명을 기입한다.

<%@ page import="java.sql.*"%>
<%@ page import="javax.sql.*"%>
<%@ page import="javax.naming.*"%>
<%@ page contentType="text/html;charset=utf-8"%>
<%
         Context ctx = null;
         Connection conn = null;
         Statement stmt = null;
         ResultSet rs = null;
 
         try {
                ctx = new InitialContext();
                DataSource ds = (DataSource) ctx.lookup("java:/comp/env/jdbc/testDB");
                conn = ds.getConnection();
                stmt = conn.createStatement();
 
                out.println("MySQL Connection Success!");
                out.println("<br />");
                out.println("DB SQL Start");
 
                out.println("<br />");
                out.println("<br />");
 
                rs = stmt.executeQuery("select * from employees LIMIT 10000");
                out.println("<table border=1>");
                out.println("<tr>");
                out.println("<th>사번</th>");
                out.println("<th>생일</th>");
                out.println("<th>이름</th>");
                out.println("<th></th>");
                out.println("<th>성별</th>");
                out.println("<th>입사일</th>");
                out.println("</tr>");
 
                while(rs.next()) {
                    out.println("<tr>");
                    out.println("<td>" + rs.getString("emp_no") + "</td>");
                    out.println("<td>" + rs.getString("birth_date") + "</td>");
                    out.println("<td>" + rs.getString("first_name") + "</td>");
                    out.println("<td>" + rs.getString("last_name") + "</td>");
                    out.println("<td>" + rs.getString("gender") + "</td>");
                    out.println("<td>" + rs.getString("hire_date") + "</td>");
                    out.println("</tr>");
                }
                out.println("</table>");
 
                conn.close();
         }
         catch(Exception e){
              out.println(e);
         }
%>

 

3. 실행 예시

   ip:port/testDB.jsp 호출 후, 정상적으로 DB 접근하여 정보를 조회해오는 모습

 

 

728x90