Integrating Jasypt with JAXB 2.0 (Mini-Howto)

(Mini-Howto contributed by Éamonn Linehan)

Introduction

The Java Architecture for XML Binding (JAXB) is an API and set of tools in Java for accessing XML through Java. Unlike a SAX or DOM based approach, JAXB does not require any manipulation of XML or knowledge of XML processing libraries. JAXB simply required a developer to bind the schema of an XML document to Java classes (these can be generated). JAXB then look after unmarshalling teh XML document into Java content objects that are available to your program.

Wolfgang Laun provides a comprehensive introduction to JAXB at jaxb.java.net. The JAXB Specification document is available from jcp.org.

Step 1: Create an XmlAdapter

It is possible to extend JAXB to make it capabable of unmarshalling encrypted XML values to decrypted strings in the corresponding Java objects by creating an 'adapter' class, that extends XmlAdapter and uses Jasypt to do the actual encryption/decryption. Your adapter will be converting between one String and another so the class would look like:

package example.cfg;
 
import java.io.File;
import java.io.IOException;
 
import javax.xml.bind.annotation.adapters.XmlAdapter;
 
import org.apache.commons.io.FileUtils;
import org.jasypt.encryption.pbe.PBEStringEncryptor;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.jasypt.properties.PropertyValueEncryptionUtils;
 
public class EncryptedStringXmlAdapter extends XmlAdapter<String,String> {
 
        private final PBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
 
        /**
         * Constructor. Reads the password from a key file on disk
         */
        public EncryptedStringXmlAdapter() {
                String keyFile = Environment.getProperty(PropertyConstants.MASTER_KEY_FILE);
                try {
                        byte[] bytes = FileUtils.readFileToByteArray(new File(keyFile));
                        String key = new String(bytes);
                        encryptor.setPassword(key);
                } catch (IOException e) {
                        System.err.println("Could not load key from " + keyFile);
                }
        }
 
        /**
         * Encrypts the value to be placed back in XML
         */
        @Override
        public String marshal(String plaintext) throws Exception {
                // This encrypts and adds the ENC(...)
                return PropertyValueEncryptionUtils.encrypt(plaintext, encryptor);
        }
 
        /**
         * Decrypts the string value
         */
        @Override
        public String unmarshal(String cyphertext) throws Exception {

                // Perform decryption operations as needed and store the new values
                if (PropertyValueEncryptionUtils.isEncryptedValue(cyphertext))
                        return PropertyValueEncryptionUtils.decrypt(cyphertext, encryptor);

                return cyphertext;
        }
 
}

The constructor of this class is reading the Jasypt password from a file whose location on disk is specified in a property file.

Step 2: Install your adapter using annotations

Here is a simple example of how the adapter might be used to encrypt a configuration file. Imagine your application has an EmailConfiguration class with username and password string fields you want to store encrypted. This class is annotated with JAXB annotations to indicate how it should be mapped to XML. The class will look something like this:

package example.cfg;
 
import java.util.Collections;
import java.util.List;
 
import javax.xml.bind.annotation.XmlAccessOrder;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorOrder;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
 
@XmlRootElement(name = "mailserver-config")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlAccessorOrder(XmlAccessOrder.UNDEFINED)
public class EmailConfiguration {
 
        @XmlAttribute(required = true)
        String host;
 
        @XmlAttribute(required = true)
        String port;
 
        @XmlElement(required = true)
        @XmlJavaTypeAdapter(value = EncryptedStringXmlAdapter.class)
        String username;
 
        @XmlElement(required = true)
        @XmlJavaTypeAdapter(value = EncryptedStringXmlAdapter.class)
        String password;
 
}

Notice that the @XmlTypeAdapter annotation is added only to the two fields we would like to allow encrypted values in.

The above code fragment will map to the following schema:

<xs:complexType name="email">
    <xs:all>
        <xs:element name="host" type="xs:string" />
        <xs:element name="port" type="xs:string" />
        <xs:element name="username" type="xs:string" />
        <xs:element name="password" type="xs:string" />
    </xs:all>
</xs:complexType>

Example: Unmarshalling encrypted configuration from XML using JAXB

Using this class an XML configuration file can be created like the one below:

<?xml version="1.0" encoding="UTF-8"?&gt;
<mailserver-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <host>mail.example.loc</host>
    <port>25</port>
    <username>private@example.loc</username>
    <password>ENC(9Wp+tVgrQSrD71u833wsRxxm96Lq2qoiqkR0jX)</password>
</mailserver-config>

Notice that although both of the username and password fields may contain encrypted values, our adapter implementation will check if the field contains an encrypted value using the PropertyValueEncryptionUtils.isEncryptedValue() method call. In the example above, only the password field contains an encrypted value.

This XML fragment can be unmarshalled to a Java object using JAXB with the following code:

// JAXB load the mail servers configuration
JAXBContext context = JAXBContext.newInstance(EmailConfiguration.class);
Unmarshaller unmarshaller = context.createUnmarshaller();
                        
EmailConfiguration configuration = 
    (EmailConfiguration) unmarshaller.unmarshal(ConfigHelper.getConfigStream(EMAIL_CFG_RESOURCE));

This ConfigHelper.getConfigStream(EMAIL_CFG_RESOURCE) code is simply a static helper function that opens an InputStream to our XML file on disk and creates an StreamSource from which the XML is parsed by JAXB. To test this approach without having to implement a helper function like this one you could store your XML fragment as a String in your class and create a StreamSource from it using:

new StreamSource( new StringReader( xmlStr.toString() ) )

When the password field of the unmarshalled EmailConfiguration object is accessed in code, the decrypted value will be returned.

This example has illustrated how any application-specific configuration file can contain encrypted values. Using JAXB 2.0 to load and convert XML to java objects is fast and easy to program. JAXB's extensibility and ability to convert data in XML format to Java objects has allowed us use Jasypt transform from encrypted XML to decrypted strings. Although this example shows one-way (i.e. decryption only) the transformation works in both drections allowing data in any XML document, messaging protocol or RPC mechanism to be protected with Jasypt.