tomcat JNDI Encrypted credentials


Example

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).