Properties are used in many Java applications as a simple way of separating parts that are likely to change, from the parts that are not that likely to change. Consider for example this typical bean definition in a Spring configuration file:
<bean id="traditionalPersonDao" class="org.springframework.ldap.samples.article.dao.TraditionalPersonDaoImpl"> <property name="url" value="ldap://localhost:3901" /> <property name="base" value="dc=jayway,dc=se" /> <property name="userDn" value="uid=admin,ou=system" /> <property name="password" value="secret" /> </bean>
In order to simplify deployment and maintenance, it's quite common to extract properties related to server names, ports, and user credentials from the Spring configuration file into a separate property file, like in thisldap.properties
:
url=ldap://localhost:3901 userDn=uid=admin,ou=system password=secret
In the configuration file, the previously hard-coded values are replaced with "property placeholders", ie variables enclosed with ${}
:
<bean id="traditionalPersonDao" class="org.springframework.ldap.samples.article.dao.TraditionalPersonDaoImpl"> <property name="url" value="${url}" /> <property name="base" value="dc=jayway,dc=se" /> <property name="userDn" value="${userDn}" /> <property name="password" value="${password}" /> </bean>
In order to perform the property value substitution in a transparent way, a PropertyPlaceholderConfigurer
is configured:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:/config/ldap.properties" /> </bean>
Spring will pick up that one has been configured and run it before any beans are instantiated. It will read the property file and replace the placeholders with the actual values. Quite handy and very simple.
But what if your organization dislikes having sensitive information like passwords lying around in property files, in clear text? Let's say that your boss demands that all such passwords must be encrypted. Wouldn't it be great if the password then somehow could be automatically decrypted before being used? This can quite easily be achieved using the Jasypt library.
It's a two-step process. First we need to encrypt the password. Then we configure a differentPropertyPlaceholderConfigurer
that is capable of decrypting the property after it has been read.
The available encryption algorithms are currently limited to Password Based Encryptors (PBE). There are scripts for encrypting and decrypting in the Jasypt distribution. This is the procedure for encrypting a text (assuming the Jasypt library has been unpacked in JASYPT_HOME):
% cd $JASYPT_HOME/bin % chmod +x *.sh % ./encrypt.sh input="This is my message to be encrypted" password=MYPAS_WORD verbose=false p3ZVFhK+aqQCyvSk9uWk7p/eisyPbXp3zt3sqnEZsn1Z5plr4CHNC/HHqlgRQ7I3
Let's verify that it can actually be decrypted:
% ./decrypt.sh input="p3ZVFhK+aqQCyvSk9uWk7p/eisyPbXp3zt3sqnEZsn1Z5plr4CHNC/HHqlgRQ7I3" password=MYPAS_WORD verbose=false This is my message to be encrypted
Good. Note that the encryption is "salted", so you'll never get the same result twice. You'll always be able to decrypt it, though. Want to see? OK, one more time then:
% ./encrypt.sh input="This is my message to be encrypted" password=MYPAS_WORD verbose=false Zi68CfrcLndtKg0npE9OScr+7qNJmWrcO8XI7ZGyucjFiqT9h1FnAIxyezbqNjQq % ./decrypt.sh input="Zi68CfrcLndtKg0npE9OScr+7qNJmWrcO8XI7ZGyucjFiqT9h1FnAIxyezbqNjQq" password=MYPAS_WORD verbose=false This is my message to be encrypted
Now, let's encrypt our password:
% ./encrypt.sh input="secret" password=MYPAS_WORD verbose=false 6mbJVZ6jozGYF1pjjqDQOQ==
We'll replace the password value in the properties file with the string above, surrounded by ENC()
:
url=ldap://localhost:3901 userDn=uid=admin,ou=system password=ENC(6mbJVZ6jozGYF1pjjqDQOQ==)
We'll simply replace our existing PropertyPlaceholderConfigurer
with the JasyptEncryptablePropertyPlaceholderConfigurer
:
<bean class="org.jasypt.spring.properties.EncryptablePropertyPlaceholderConfigurer"> <constructor-arg ref="configurationEncryptor" /> <property name="location" value="classpath:/config/ldap.properties" /> </bean>
It delegates the actual decryption to a StringEncryptor
implementation:
<bean id="configurationEncryptor" class="org.jasypt.encryption.pbe.StandardPBEStringEncryptor"> <property name="config" ref="environmentVariablesConfiguration" /> </bean>
The encryptor in turn needs a configuration that provides information such as the algorithm to use and the encryption password. It delegates that responsibility to a PBEConfig
implementation that expects the password to be available in an environment variable or a system property:
<bean id="environmentVariablesConfiguration" class="org.jasypt.encryption.pbe.config.EnvironmentStringPBEConfig"> <property name="algorithm" value="PBEWithMD5AndDES" /> <property name="passwordEnvName" value="APP_ENCRYPTION_PASSWORD" /> </bean>
Providing the encryption password as a system property is actually a good thing. A system property can be cleared just when the application has started, thereby minimizing considerably the time that the password is exposed.
It won't work with a Maven property:
% mvn test -DAPP_ENCRYPTION_PASSWORD=MYPAS_WORD ... Tests run: 8, Failures: 0, Errors: 8, Skipped: 0 [INFO] ------------------------------------------------------------------------ [ERROR] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] There are test failures. Please refer to target/surefire-reports for the individual test results
We check the Surefire reports and find the cause of the error:
org.jasypt.exceptions.EncryptionInitializationException: Password not set for Password Based Encryptor
It does however work with an environment variable:
% export APP_ENCRYPTION_PASSWORD=MYPAS_WORD % mvn test ... Tests run: 8, Failures: 0, Errors: 0, Skipped: 0 [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ ... % unset APP_ENCRYPTION_PASSWORD
If we're on Java5 (or lower), we'll need ICU4J. Otherwise, we'll run into this:
org.jasypt.exceptions.EncryptionInitializationException: java.lang.NoClassDefFoundError: com/ibm/icu/text/Normalizer
There are two places where we'll need ICU4J: the command line tools and our Maven project.
Reviewing the Jasypt scripts, we find that it's possible to customize the classpath:
export JASYPT_CLASSPATH=~/Downloads/icu4j-4_0.jar
Add this profile to the Maven pom.xml
to get ICU4J in the classpath:
<profiles> <profile> <id>jdk15</id> <activation> <jdk>1.5</jdk> </activation> <dependencies> <dependency> <groupId>com.ibm.icu</groupId> <artifactId>icu4j</artifactId> <version>3.8</version> </dependency> </dependencies> </profile> </profiles>
Currently, the 4.0 version is not available in the central Maven repo, but 3.8 seems to work just fine.
Using Jasypt, it's actually quite easy to use encrypted values in your property files. In a Spring-based application, it's simply a question of replacing the existing PropertyPlaceholderConfigurer
with the Jasypt encrypting equivalent, plus two more beans providing encryption and configuration. Choose how to provide the encryption password, and you're set to go.
If running on Java5 or lower, you'll also need to add ICU4J to your classpath, for the encryption scripts as well as your build and deployment environment.