Encrypt username and password for JNDI in Tomcat Server.xml

Murali MurugesanFull stack Java developer
CERTIFIED EXPERT
codestore.com.au
Published:
Updated:
Most of the developers using Tomcat find it easy to configure the datasource in Server.xml and use the JNDI name in the code to get the connection.  So the default connection pool using DBCP (or any other framework) is made available and the life goes easy using the connection pool.

But problems pop up when you use Tomcat as your production server. The most vulnerable part is the exposure of database username and password in clear text format. There are lots of chances for the data being corrupted using the login credentials. So the need of the hour is to safeguard the exposed username and password through some means.

And here goes the way to do it....

Credit: Full credit to Michael Remijan for this wonderful article on how to secure the password in Tomcat.  Please read it at:
     http://websphere.sys-con.com/node/393364

What I am discussing here is the other way of implementing it using the Cipher encryption based on a encryption key, just to make the life of some intruders a little bit harder.  Ok, let's get to the code straight away,

Here is the normal Datasource configuration in Server.xml,.  A you can see, username and password are exposed, which is vulnerable and totally unacceptable.

<Context path="/myProject" docBase="myProject" debug="1" reloadable="true">
                       <Resource name="jdbc/test" auth="Container" type="javax.sql.DataSource"  driverClassName="oracle.jdbc.driver.OracleDriver" url="jdbc:oracle:thin:@server:1523:TEST"
                       username="muralim" password="helloworld"  maxActive="4" maxIdle="2" maxWait="-1"
                       logAbandoned="true"  removeAbandoned="true"  removeAbandonedTimeout="60"/>
                       </Context>

Open in new window

So I use the  Cipher encryption to encrypt my username and password and replace the clear text of username and password as below:

<Context path="/myProject" docBase="myProject" debug="1" reloadable="true">
                       <Resource name="jdbc/test" auth="Container" type="javax.sql.DataSource"  driverClassName="oracle.jdbc.driver.OracleDriver" url="jdbc:oracle:thin:@server:1523:TEST"
                      username="Ql6u3CAl988=" password="CgrtRRVz6A3aaGAfDpRRuQ=="  maxActive="4" maxIdle="2" maxWait="-1"
                       logAbandoned="true"  removeAbandoned="true"  removeAbandonedTimeout="60"/>
                       </Context>

Open in new window

I used a common key to encrypt my username and password and it's all encrypted now.  But how does Tomcat recognise that these credentials are encrypted and know to decrypted then when making the actual connection???...

So here we have the server.xml entry changed again:
<Context path="/myProject" docBase="myProject" debug="1" reloadable="true">
                      
                      <Resource name="jdbc/test" auth="Container" type="javax.sql.DataSource"
                      
                      factory="com.mycomp.util.CompEncryptedDataSourceFactory"
                      
                      driverClassName="oracle.jdbc.driver.OracleDriver" url="jdbc:oracle:thin:@server:1523:TEST"
                       username="Ql6u3CAl988=" password="CgrtRRVz6A3aaGAfDpRRuQ=="  maxActive="4" maxIdle="2" maxWait="-1"
                       logAbandoned="true"  removeAbandoned="true"  removeAbandonedTimeout="60"/>
                       </Context>

Open in new window

In the above code snippet, you might notice the attribute factory with some value of a class name.  By default DBCP factory is used to handle the connection resources and hence the attribute value is implicit. To read more on custom factories read on...

So what exactly is there in the CompEncryptedDataSourceFactory ???

Here goes the code, i have tweaked the sample from the REMIJAN article to achieve this,

import java.util.Enumeration;
                      import java.util.Hashtable;
                      import javax.naming.*;
                      import org.apache.commons.dbcp.BasicDataSourceFactory;
                      
                      public class  CompEncryptedDataSourceFactory extends BasicDataSourceFactory{
                      
                      // common key for encryption and decryption
                      private static final String ENC_KEY ="CompEncryptedDataSourceFactory";
                      
                      public CompEncryptedDataSourceFactory()
                      {}
                      
                      public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment)
                      throws Exception
                      {
                      if(obj instanceof Reference)
                      {
                      setUsername((Reference)obj);
                      setPassword((Reference)obj);
                      }
                      return super.getObjectInstance(obj, name, nameCtx, environment);
                      }
                      
                      private void setUsername(Reference ref)
                      throws Exception
                      {
                      findDecryptAndReplace("username", ref);
                      }
                      
                      private void setPassword(Reference ref)
                      throws Exception
                      {
                      findDecryptAndReplace("password", ref);
                      }
                      
                      private void findDecryptAndReplace(String refType, Reference ref)
                      throws Exception
                      {
                      int idx = find(refType, ref);
                      String decrypted = decrypt(idx, ref);
                      replace(idx, refType, decrypted, ref);
                      }
                      
                      private void replace(int idx, String refType, String newValue, Reference ref)
                      throws Exception
                      {
                      ref.remove(idx);
                      ref.add(idx, new StringRefAddr(refType, newValue));
                      }
                      
                      private String decrypt(int idx, Reference ref)throws Exception
                      {
                      return new CipherEncrypter(ENC_KEY).decrypt(ref.get(idx).getContent().toString());
                      }
                      
                      private int find(String addrType, Reference ref)
                      throws Exception
                      {
                      Enumeration enu = ref.getAll();
                      for(int i = 0; enu.hasMoreElements(); i++)
                      {
                      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());
                      }
                      }

Open in new window

For now don't worry about the working of this class as it's not so important unless you are too keen :-) . As you might see, the class extends BasicDataSourceFactory from Commons/DBCP.

And here is the CipherEncrypter, -- I have no idea on the working of this class and i got this by googling.

[Editor's Note:  This, and similar source code is found, usually without attribution, at several Java coding sites, including: http://www.exampledepot.com/egs/javax.crypto/desstring.html ]
import java.io.UnsupportedEncodingException;
                      import java.security.spec.AlgorithmParameterSpec;
                      
                      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;
                      
                      CipherEncrypter(String passPhrase) {
                      try {
                      // Create the key
                      PBEKeySpec keySpec = new PBEKeySpec(passPhrase.toCharArray());
                      SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec);
                      ecipher = Cipher.getInstance(key.getAlgorithm());
                      dcipher = Cipher.getInstance(key.getAlgorithm());
                      
                      // Prepare the parameter to the ciphers
                      AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);
                      
                      // Create the ciphers
                      ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
                      dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
                      } catch (java.security.InvalidAlgorithmParameterException e) {
                      } catch (java.security.spec.InvalidKeySpecException e) {
                      } catch (javax.crypto.NoSuchPaddingException e) {
                      } catch (java.security.NoSuchAlgorithmException e) {
                      } catch (java.security.InvalidKeyException e) {
                      }
                      }
                      
                      public String encrypt(String str) {
                      try {
                      byte[] utf8 = str.getBytes("UTF8");
                      byte[] enc = ecipher.doFinal(utf8);
                      return new sun.misc.BASE64Encoder().encode(enc);
                      } catch (javax.crypto.BadPaddingException e) {
                      } catch (IllegalBlockSizeException e) {
                      } catch (UnsupportedEncodingException e) {
                      } catch (Exception e) {
                      }
                      return null;
                      }
                      
                      public String decrypt(String str) {
                      try {
                      // Decode base64 to get bytes
                      byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(str);
                      
                      // Decrypt
                      byte[] utf8 = dcipher.doFinal(dec);
                      
                      // Decode using utf-8
                      return new String(utf8, "UTF8");
                      } catch (javax.crypto.BadPaddingException e) {
                      } catch (IllegalBlockSizeException e) {
                      } catch (UnsupportedEncodingException e) {
                      } catch (java.io.IOException e) {
                      }
                      return null;
                      }
                      
                      public static void main(String[] args)
                      {
                      
                      try {
                      DesEncrypter encrypter = new DesEncrypter("CompEncryptedDataSourceFactory"); // key for encryption
                      String encrypted = encrypter.encrypt("helloworld");
                      System.out.println("encrypted :"+encrypted);
                      
                      String decrypted = encrypter.decrypt(encrypted);
                      System.out.println("decrypted :"+decrypted);
                      } catch (Exception e) {
                      
                      e.printStackTrace();
                      }
                      
                      }
                      }

Open in new window

The above code includes a main method to get the encrypted text of the user name and password.

To set up configuration of jars to enable this in Tomcat 5.5 (it might vary with Tomcat versions):

1. Place commons-dbcp-1.2.1.jar,commons-pool-1.3.jar (use latest versions of these jars)  in
    common/lib.
2. Make a jar file of both classes CipherEncrypter.class and  CompEncryptedDataSourceFactory.class
    and place it in the common/lib.   Something like  >>
           jar cf CompEncryptedDataSourceFactory.jar *.class

So now we are ready to see the custom factory for encrypting the username and password.  Now write a normal JNDI lookup code and fetch your connection.

Here is the test code, and you have a connection at last with the encrypted username and password.
try{
                      
                      InitialContext initialContext = new InitialContext();
                      DataSource dataSource = (DataSource)initialContext.lookup("java:comp/env/jdbc/test");
                      
                      return dataSource.getConnection();
                      }catch(Exception e){
                      log.error("Exception in getInstance",e);
                      e.printStackTrace();
                      }

Open in new window

Hope this article makes a developer life easy in configuring a secured datasource.

Happy Coding...

-Murali*
3
30,491 Views
Murali MurugesanFull stack Java developer
CERTIFIED EXPERT
codestore.com.au

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.