본문으로 건너뛰기

Tomcat DB 계정 정보 암호화


이전 챕터를 지나오면서 JDBC 를 이용해 DB를 연결했을 것입니다. 문제는 연결 정보인 context.xml 가 Github와 같이 웹 상에 공유되면 보안적으로 문제가 발생하므로 DB 계정 정보를 암호화하는 방법에 대해 소개하도록 하겠습니다.

info

실습이 진행된 코드는 parkgang/helloworld-tomcat-jndi-encryption 에서 확인하실 수 있습니다.

메커니즘

  1. 암호화 계정 추출
  2. 복호화 코드 jar 추출
  3. server에 복호화 jar 적용

암호화 & 복호화 코드 생성

프로젝트 생성

아래와 같이 프로젝트, 모듈, 패키지를 선언합니다.

note

반드시 package를 필요로 하지는 않지만 추후, 파일을 불러올때 편리합니다.

EncryptedDataSourceFactoryEncryptor 을 파일을 생성합니다.

EncryptedDataSourceFactory

아래의 코드를 작성합니다.

EncryptedDataSourceFactory.java
package secured;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.util.Properties;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.naming.Context;
import javax.sql.DataSource;

import org.apache.tomcat.jdbc.pool.DataSourceFactory;
import org.apache.tomcat.jdbc.pool.PoolConfiguration;
import org.apache.tomcat.jdbc.pool.XADataSource;

public class EncryptedDataSourceFactory extends DataSourceFactory {
private Encryptor encryptor = null;

public EncryptedDataSourceFactory() {
try {
encryptor = new Encryptor(); // If you've used your own secret key, pass it in...
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedEncodingException e) {
System.out.println("Error instantiating decryption class." + e);
throw new RuntimeException(e);
}
}

@Override
public DataSource createDataSource(Properties properties, Context context, boolean XA) throws InvalidKeyException,
IllegalBlockSizeException, BadPaddingException, SQLException, NoSuchAlgorithmException,
NoSuchPaddingException {
// Here we decrypt our password.
PoolConfiguration poolProperties = EncryptedDataSourceFactory.parsePoolProperties(properties);
poolProperties.setUsername(encryptor.decrypt(poolProperties.getUsername()));
poolProperties.setPassword(encryptor.decrypt(poolProperties.getPassword()));
// The rest of the code is copied from Tomcat's DataSourceFactory.
if (poolProperties.getDataSourceJNDI() != null && poolProperties.getDataSource() == null) {
performJNDILookup(context, poolProperties);
}
org.apache.tomcat.jdbc.pool.DataSource dataSource = XA ? new XADataSource(poolProperties)
: new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);
dataSource.createPool();
return dataSource;
}
}

Encryptor

아래의 코드를 작성합니다.

Encryptor.java
package secured;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

public class Encryptor {
private static final String ALGORITHM = "AES";
private static final String defaultSecretKey = "asdfo23jda3sds";
private Key secretKeySpec;

public Encryptor()
throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException {
this(null);
}

public Encryptor(String secretKey)
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, UnsupportedEncodingException {
this.secretKeySpec = generateKey(secretKey);
}

public String encrypt(String plainText) throws InvalidKeyException, NoSuchAlgorithmException,
NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
return asHexString(encrypted);
}

public String decrypt(String encryptedString) throws InvalidKeyException, IllegalBlockSizeException,
BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] original = cipher.doFinal(toByteArray(encryptedString));
return new String(original);
}

private Key generateKey(String secretKey) throws UnsupportedEncodingException, NoSuchAlgorithmException {
if (secretKey == null) {
secretKey = defaultSecretKey;
}
byte[] key = (secretKey).getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16); // use only the first 128 bit
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128); // 192 and 256 bits may not be available
return new SecretKeySpec(key, ALGORITHM);
}

private final String asHexString(byte buf[]) {
StringBuffer strbuf = new StringBuffer(buf.length * 2);
int i;
for (i = 0; i < buf.length; i++) {
if (((int) buf[i] & 0xff) < 0x10) {
strbuf.append("0");
}
strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
}
return strbuf.toString();
}

private final byte[] toByteArray(String hexString) {
int arrLength = hexString.length() >> 1;
byte buf[] = new byte[arrLength];
for (int ii = 0; ii < arrLength; ii++) {
int index = ii << 1;
String l_digit = hexString.substring(index, index + 2);
buf[ii] = (byte) Integer.parseInt(l_digit, 16);
}
return buf;
}

public static void main(String[] args) throws Exception {
Encryptor aes = new Encryptor(defaultSecretKey);
String testid = "testid";
System.out.println("Id:" + aes.encrypt(testid));
String testpassword = "testpassword";
System.out.println("Pw:" + aes.encrypt(testpassword));
}
}

lib 추가

아마 위의 파일을 작성하면 많은 에러가 발생할 것입니다. 이유는 tomcat-jdbc 이 없기 떄문입니다.

PC에 설치된 tomcat의 아래 사진의 경로에서 해당 파일을 추가해줍니다.

오류가 없어진 것을 확인할 수 있습니다.

암호화 하기

사용할 계정 암호화를 진행합니다. 아래의 값에 따라서 암호화된 결과값이 생성됩니다.

저 값을 알게되면 같은 알고리즘을 통해서 복호화가 가능하므로 절때, 공유되면 안됩니다.

빨간색 네모값의 암호화된 결과 값입니다. 출력된 값을 context.xml 에 넣어주면 됩니다.

복호화 jar 추출

빌드된 결과물을 확인할 수 있습니다.

jar을 tomcat에 추가

복호화 파일을 tomcat에 추가하여 복호화가 가능하도록 합니다.

note

위 사진을 보면 유추할 수 있지만 복호화가 필요로 하는 server pc의 tomcat에도 넣어야합니다.
해당 파일은 git으로 관리하지 않고 서버에 직접 넣도록 합니다.

context.xml 선언

아래와 같이 암호화된 계정 정보와 복호화가 가능하도록 아래처럼 명시해줍니다.

note

factory는 복호화 되는 class 파일의 경로입니다.

마무리

이제 안전하게 DB 접속 정보를 Github와 같이 협업하는 장소에 올릴 수 있게 되었습니다.

이러한 방법을 모를 때는 context.xmlattribute 만 정의해놓고 필요에 따라 Local Machine에서 Value를 넣고 Public 하게 공개된 장소에 Push 하지 않는 방법을 사용할 수 있었는데 일단 귀찮고 더 좋은 방법은 없나? 라고 생각해게 됩니다.

이럴 때 해당 방법을 사용해서 손쉽게 DB 접속 정보를 관리할 수 있을 것 입니다.


parkgang