본문으로 건너뛰기

Tomcat DB 계정 정보 암호화


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

info

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

메커니즘​

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

암호화 & 복호화 코드 생성​

프로젝트 생성​

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

note

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

EncryptedDataSourceFactory 와 Encryptor 을 파일을 생성합니다.

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.xml 에 attribute 만 정의해놓고 필요에 따라 Local Machine에서 Value를 넣고 Public 하게 공개된 장소에 Push 하지 않는 방법을 사용할 수 있었는데 일단 귀찮고 더 좋은 방법은 없나? 라고 생각해게 됩니다.

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


parkgang