Thursday 18 June 2015

Deltaspike configuration properties encryption with Jasypt

The following article shows how to support transparent encryption of properties files entries using Jasypt lite and the Deltaspike configuration mechanism.  My solution uses Deltaspike 1.2.1 and Jasypt 1.9.1, but it should work for later versions as well. Properties files entries are decrypted at runtime using a password stored in a system property which can be passed on the command line at application startup.

Deltaspike is a set of extensions built on top of CDI (Context and dependency injection), available at: https://deltaspike.apache.org/

Jasypt is tool written in Java to add transparent encryption to different parts of an application (e.g. database fields, properties files entries). The project website is located at: http://www.jasypt.org/ 

As first step, set up the application, using Deltaspike configuration, to read properties file and inject properties values.


package com.github.lbitonti.deltaspikeconfig;

import org.apache.deltaspike.core.api.config.ConfigResolver;
import org.apache.deltaspike.core.api.config.PropertyFileConfig;

public class ConfigurationFile implements PropertyFileConfig {

  static {
    ConfigResolver.addConfigFilter(new ConfigurationDecryptingFilter());
  }

  @Override
  public String getPropertyFileName() {
    return "config.properties";
  }

  @Override
  public boolean isOptional() {
    return false;
  }

}

This is loaded at startup using the ServiceLoader mechanism. For this to happen, add a file named 'org.apache.deltaspike.core.api.config.PropertyFileConfig' to the project 'META-INF/services' directory. The file should contain a single line declaring the class to be loaded. In my case:
com.github.lbitonti.deltaspikeconfig.ConfigurationFile

After this is done properties read from config.properties can be injected into (cdi) beans. For instance, to inject the value of a property with key a.config.value, you can use the following syntax:


@Inject @ConfigProperty(name = "a.config.value")
private String aConfigValue;
Check the Deltaspike documentation for more details.

As you can see in the static block at the top of the ConfigurationFile class, this triggers the load of another class, ConfigurationDecryptingFilter, which takes care of initializing Jasypt and providing the necessary code to decrypt the properties file entries that are actually encrypted. The content of this second class are as follows:


package com.github.lbitonti.deltaspikeconfig;

import org.apache.deltaspike.core.spi.config.ConfigFilter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;

import java.security.Security;

public class ConfigurationDecryptingFilter implements ConfigFilter {

  public static final String PROVIDER = BouncyCastleProvider.PROVIDER_NAME;

  static {
    Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
  }

  private StandardPBEStringEncryptor encryptor;

  public ConfigurationDecryptingFilter() {
    String encPwd = System.getProperty("enc.pwd");
    encryptor = new StandardPBEStringEncryptor();
    encryptor.setPassword(encPwd);
    encryptor.setProviderName(PROVIDER);
    encryptor.setAlgorithm("PBEWITHSHA256AND256BITAES-CBC-BC");
    encryptor.initialize();
  }

  @Override
  public String filterValue(String key, String value) {
    if (isEncrypted(value)) {
      return encryptor.decrypt(value.substring(4, value.length() - 1));
    }
    return value;
  }

  @Override
  public String filterValueForLog(String key, String value) {
    if (isEncrypted(value)) {
      return "<" + key + " value>";
    }
    return value;
  }

  protected boolean isEncrypted(String value) {
    if (value != null && value.startsWith("ENC(") && value.endsWith(")")) {
      return true;
    }
    return false;
  }

}

With this configuration, all values that are enclosed between the 2 following character sequences: 'ENC(' and ')' are decrypted using the password passed as system property with name 'enc.pwd'. All values that do not start and terminate with these tokens are simply returned without any processing.
ConfigurationDecryptingFilter also initializes Bouncycastle, as this allows us to use a stronger encryption algorithm (i.e. AES-256). For this to work, the JVM used should have the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files installed. For the Oracle JVM these can be downloaded from the Oracle website.

For all this to work the following dependencies are needed in your pom file (if you use maven that is):


<dependency>
    <groupId>org.jasypt</groupId>
    <artifactId>jasypt</artifactId>
    <version>1.9.1</version>
    <classifier>lite</classifier>
</dependency>
<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.51</version>
</dependency>

<dependency>
    <groupId>org.apache.deltaspike.core</groupId>
    <artifactId>deltaspike-core-api</artifactId>
    <version>1.2.1</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.apache.deltaspike.core</groupId>
    <artifactId>deltaspike-core-impl</artifactId>
    <version>1.2.1</version>
    <scope>runtime</scope>
</dependency>

A CDI provider (e.g. Weld, OpenWebBeans) has to be included as a dependency as well.