Encrypting Properties With Jasypt

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.

Encrypting the password

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==)

Configure A Decrypting PropertyPlaceholderConfigurer

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.

Running The Application

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

Issues With Java5

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.

ICU4J With The Command Line Tools

Reviewing the Jasypt scripts, we find that it's possible to customize the classpath:

export JASYPT_CLASSPATH=~/Downloads/icu4j-4_0.jar

ICU4J With Maven

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.

Summary

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.

References

  • http://jasypt.org
  • http://www.springframework.org
  • http://icu-project.org/

你可能感兴趣的:(Encrypting Properties With Jasypt)