In the JNDI declaration you may want to encrypt the username and password.
You have to implement a custom datasource factory in order to be able to decrypt the credentials.
In server.xml
replace factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
by factory="cypher.MyCustomDataSourceFactory"
Then define your custom factory :
package cypher;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory;
public class MyCustomDataSourceFactory extends BasicDataSourceFactory {
//This must be the same key used while encrypting the data
private static final String ENC_KEY = "aad54a5d4a5dad2ad1a2";
public MyCustomDataSourceFactory() {}
@Override
public Object getObjectInstance(final Object obj, final Name name, final Context nameCtx, final Hashtable environment) throws Exception {
if (obj instanceof Reference) {
setUsername((Reference) obj);
setPassword((Reference) obj);
}
return super.getObjectInstance(obj, name, nameCtx, environment);
}
private void setUsername(final Reference ref) throws Exception {
findDecryptAndReplace("username", ref);
}
private void setPassword(final Reference ref) throws Exception {
findDecryptAndReplace("password", ref);
}
private void findDecryptAndReplace(final String refType, final Reference ref) throws Exception {
final int idx = find(refType, ref);
final String decrypted = decrypt(idx, ref);
replace(idx, refType, decrypted, ref);
}
private void replace(final int idx, final String refType, final String newValue, final Reference ref) throws Exception {
ref.remove(idx);
ref.add(idx, new StringRefAddr(refType, newValue));
}
private String decrypt(final int idx, final Reference ref) throws Exception {
return new CipherEncrypter(ENC_KEY).decrypt(ref.get(idx).getContent().toString());
}
private int find(final String addrType, final Reference ref) throws Exception {
final Enumeration enu = ref.getAll();
for (int i = 0; enu.hasMoreElements(); i++) {
final RefAddr addr = (RefAddr) enu.nextElement();
if (addr.getType().compareTo(addrType) == 0) {
return i;
}
}
throw new Exception("The \"" + addrType + "\" name/value pair was not found" + " in the Reference object. The reference Object is" + " "
+ ref.toString());
}
}
Of course you need an utility to encrypt the username and password ;
package cypher;
import java.io.UnsupportedEncodingException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
public class CipherEncrypter {
Cipher ecipher;
Cipher dcipher;
byte[] salt = {
(byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x35, (byte) 0xE3, (byte) 0x03
};
int iterationCount = 19;
/**
* A java.security.InvalidKeyException with the message "Illegal key size or default parameters" means that the cryptography strength is limited; the unlimited strength juridiction policy files are not in the correct location. In a JDK,
* they should be placed under ${jdk}/jre/lib/security
*
* @param passPhrase
*/
public CipherEncrypter(final String passPhrase) {
try {
// Create the key
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
KeySpec spec = new PBEKeySpec(passPhrase.toCharArray(), salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
// SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
// Create the ciphers
ecipher = Cipher.getInstance(tmp.getAlgorithm());
dcipher = Cipher.getInstance(tmp.getAlgorithm());
final AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);
ecipher.init(Cipher.ENCRYPT_MODE, tmp, paramSpec);
dcipher.init(Cipher.DECRYPT_MODE, tmp, paramSpec);
}
catch (Exception e) {
e.printStackTrace();
}
}
public String encrypt(final String str) {
try {
final byte[] utf8 = str.getBytes("UTF8");
byte[] ciphertext = ecipher.doFinal(utf8);
return new sun.misc.BASE64Encoder().encode(ciphertext);
}
catch (final javax.crypto.BadPaddingException e) {
//
}
catch (final IllegalBlockSizeException e) {
//
}
catch (final UnsupportedEncodingException e) {
//
}
catch (Exception e) {
//
}
return null;
}
public String decrypt(final String str) {
try {
final byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(str);
return new String(dcipher.doFinal(dec), "UTF-8");
}
catch (final javax.crypto.BadPaddingException e) {
//TODO
}
catch (final IllegalBlockSizeException e) {
//TODO
}
catch (final UnsupportedEncodingException e) {
//TODO
}
catch (final java.io.IOException e) {
//TODO
}
return null;
}
public static void main(final String[] args) {
if (args.length != 1) {
System.out.println("Error : you have to pass exactly one argument.");
System.exit(0);
}
try {
//This key is used while decrypting.
final CipherEncrypter encrypter = new CipherEncrypter("aad54a5d4a5dad2ad1a2");
final String encrypted = encrypter.encrypt(args[0]);
System.out.println("Encrypted :" + encrypted);
final String decrypted = encrypter.decrypt(encrypted);
System.out.println("decrypted :" + decrypted);
}
catch (final Exception e) {
e.printStackTrace();
}
}
}
When you have encrypted values for username and password, replace the clear ones in server.xml
.
Note that the encrypter should be in an obfuscated jar to keep the private key hidden (or you can also pass the key as an argument of the programm).