JNDI 加密憑據

在 JNDI 宣告中,你可能希望加密使用者名稱和密碼。

你必須實現自定義資料來源工廠才能解密憑據。

server.xml 中用 factory="cypher.MyCustomDataSourceFactory" 替換 factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"

然後定義你的自定義工廠:

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());
    }
}

當然,你需要一個實用程式來加密使用者名稱和密碼;

    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();
        }

    }
}

如果你有使用者名稱和密碼的加密值,請替換 server.xml 中的清除值。

請注意,加密器應位於模糊的 jar 中以隱藏私鑰(或者你也可以將金鑰作為程式的引數傳遞)。