We are encrypting / decrypting some of our database attributes transparently with prePersist()
(using Morphia) and in the getter of the entity. To keep the entities nice and clean, we're using static methods. It looks something like this:
@Override
@PrePersist
public void prePersist() {
super.prePersist();
if(password != null){
if(passwordEncrypted == null){
passwordEncrypted = new EncryptedString();
}
passwordEncrypted.setEncryptedAttribute(AESEncryptor.encrypt(password, passwordEncrypted.getSalt()));
}
}
Note that we are not decrypting in the postLoad()
method, since the encrypted attributes are not always required and we want to avoid the performance overhead. Unfortunately this rules out @EntityListener
, as described in http://invariantproperties.com/2012/11/25/database-encryption-using-jpa-listeners/.
public String getPassword() {
if((password == null) && (passwordEncrypted != null)){
password = AESEncryptor.decrypt(passwordEncrypted.getEncryptedAttribute(), passwordEncrypted.getSalt());
}
return password;
}
Now we want to keep the encryption password in our properties file and the one from the correct profile (prod, stage, dev) should be loaded.
The encryption code looks something like this - the getPassword
should be loaded via Spring:
public static String encrypt(String input, String salt) {
TextEncryptor encryptor = Encryptors.text(getPassword(), salt);
String cipher = null;
try {
cipher = encryptor.encrypt(input);
} catch(Exception e){
LOG.error("Could not encrypt the input '{}', be sure to check the password for illegal characters", input);
}
return cipher;
}
While it is possible to load static variables with Spring (for example http://www.connorgarvey.com/blog/?p=105), this is pretty hackish and nearly always discouraged. Additionally, we're not sure if this might not open up garbage collection issues.
How could / should this be done correctly?
If you are configuring using XML you can use org.springframework.beans.factory.config.MethodInvokingFactoryBean. For example:
public class AESEncryptor {
private static String password;
static void setPassword(String newPass) {
password = newPass;
}
}
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="sample.AESEncryptor.setPassword"/>
<property name="arguments">
<list>
<value>secret</value>
</list>
</property>
</bean>
To integrate this with profiles you have a number of options. In its simplest for you would simply wrap the MethodInvokingFactoryBean definition with profile definitions. For example:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<beans profile="dev">
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="sample.AESEncryptor.setPassword"/>
<property name="arguments">
<list>
<value>secretForDev</value>
</list>
</property>
</bean>
</beans>
<beans profile="stage">
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="sample.AESEncryptor.setPassword"/>
<property name="arguments">
<list>
<value>secretForStage</value>
</list>
</property>
</bean>
</beans>
<beans profile="prod">
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="sample.AESEncryptor.setPassword"/>
<property name="arguments">
<list>
<value>secretForProd</value>
</list>
</property>
</bean>
</beans>
</beans>
You can then activate the profiles by setting the spring.profiles.active system property.
However, it may be better to use an easier approach. From the Spring blog
Do not use profiles if a simpler approach can get the job done. If the only thing changing between profiles is the value of properties, Spring's existing PropertyPlaceholderConfigurer / may be all you need.
To do this, you could use a configuration similar to the following:
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="sample.AESEncryptor.setPassword"/>
<property name="arguments">
<list>
<value>${security.encrypt.secret}</value>
</list>
</property>
</bean>
<util:properties location="classpath:environment.properties"/>
You would then have a environment.properties file that is included on the classpath for each environment. An example for dev might look like the following:
security.encrypt.secret=secretForDev