How to avoid storing passwords in the clear for tomcat's server.xml Resource definition of a DataSource?
As said before encrypting passwords is just moving the problem somewhere else.
Anyway, it's quite simple.
Just write a class with static fields for your secret key and so on, and static methods to encrypt, decrypt your passwords.
Encrypt your password in Tomcat's configuration file (server.xml
or yourapp.xml
...) using this class.
And to decrypt the password "on the fly" in Tomcat, extend the DBCP's BasicDataSourceFactory
and use this factory in your resource.
It will look like:
<Resource
name="jdbc/myDataSource"
auth="Container"
type="javax.sql.DataSource"
username="user"
password="encryptedpassword"
driverClassName="driverClass"
factory="mypackage.MyCustomBasicDataSourceFactory"
url="jdbc:blabla://..."/>
And for the custom factory:
package mypackage;
....
public class MyCustomBasicDataSourceFactory extends org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception {
Object o = super.getObjectInstance(obj, name, nameCtx, environment);
if (o != null) {
BasicDataSource ds = (BasicDataSource) o;
if (ds.getPassword() != null && ds.getPassword().length() > 0) {
String pwd = MyPasswordUtilClass.unscramblePassword(ds.getPassword());
ds.setPassword(pwd);
}
return ds;
} else {
return null;
}
}
Hope this helps.
Tomcat has a Password FAQ that specifically addresses your question. In short: Keep the password in the clear and properly lock-down your server.
That page also offers some suggestions of how security-by-obscurity might be used to pass an auditor's checklist.
As @Ryan mentioned, please read Tomcat's Tomcat Password FAQ before implementing this solution. You are only adding obscurity not security.
@Jerome Delattre's answer will work for simple JDBC data sources, but not for more complicated ones that connect as part of the datasource construction (e.g. oracle.jdbc.xa.client.OracleXADataSource).
This is alternative approach that modifies the password prior to calling the existing factory. Below is an example of a factory for a basic datasource and one for an Atomikos JTA compatible XA datasource.
Basic Example:
public class MyEncryptedPasswordFactory extends BasicDataSourceFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context context, Hashtable<?, ?> environment)
throws Exception {
if (obj instanceof Reference) {
Reference ref = (Reference) obj;
DecryptPasswordUtil.replacePasswordWithDecrypted(ref, "password");
return super.getObjectInstance(obj, name, context, environment);
} else {
throw new IllegalArgumentException(
"Expecting javax.naming.Reference as object type not " + obj.getClass().getName());
}
}
}
Atomikos Example:
public class MyEncryptedAtomikosPasswordFactory extends EnhancedTomcatAtomikosBeanFactory {
@Override
public Object getObjectInstance(Object obj, Name name, Context context, Hashtable<?, ?> environment)
throws NamingException {
if (obj instanceof Reference) {
Reference ref = (Reference) obj;
DecryptPasswordUtil.replacePasswordWithDecrypted(ref, "xaProperties.password");
return super.getObjectInstance(obj, name, context, environment);
} else {
throw new IllegalArgumentException(
"Expecting javax.naming.Reference as object type not " + obj.getClass().getName());
}
}
}
Updating password value in Reference:
public class DecryptPasswordUtil {
public static void replacePasswordWithDecrypted(Reference reference, String passwordKey) {
if(reference == null) {
throw new IllegalArgumentException("Reference object must not be null");
}
// Search for password addr and replace with decrypted
for (int i = 0; i < reference.size(); i++) {
RefAddr addr = reference.get(i);
if (passwordKey.equals(addr.getType())) {
if (addr.getContent() == null) {
throw new IllegalArgumentException("Password must not be null for key " + passwordKey);
}
String decrypted = yourDecryptionMethod(addr.getContent().toString());
reference.remove(i);
reference.add(i, new StringRefAddr(passwordKey, decrypted));
break;
}
}
}
}
Once the .jar file containing these classes are in Tomcat's classpath you can update your server.xml to use them.
<Resource factory="com.mycompany.MyEncryptedPasswordFactory" username="user" password="encryptedPassword" ...other options... />
<Resource factory="com.mycompany.MyEncryptedAtomikosPasswordFactory" type="com.atomikos.jdbc.AtomikosDataSourceBean" xaProperties.user="user" xaProperties.password="encryptedPassword" ...other options... />