devxlogo

Managing XML Encryption with Java

Managing XML Encryption with Java

ML Encryption provides end-to-end security for applications that require secure exchange of structured data. XML itself is the most popular technology for structuring data, and therefore XML-based encryption is the natural way to handle complex requirements for security in data interchange applications.

This article discusses how to manage the complexities of XML encryption using tools and technologies available in the Java programming language and the Apache XML Security framework.

First, however, I will introduce the security threats facing XML.

XML Security Threats
XML-based data transfer has emerged as the standard for organizations to exchange business data. As with all communications over the public Internet, XML-based transfers have their own set of vulnerabilities to confront. The following list illustrates some specific XML security threats:

  • Schema Poisoning: Manipulating the WS schema to alter the data processed by the application
  • XML Parameter Tampering: Injection of malicious scripts or content into XML parameters
  • Coercive Parsing: Injection of malicious content into the XML
  • Oversized Payload: Sending oversized files to create an XDoS attack
  • Recursive Payload: Sending mass amounts of nested data to create an XDoS attack against an XML parser
  • XML Routing Detours: Redirecting sensitive data within the XML path
  • External Entity Attack: An attack on an application that parses XML input from suspicious sources using an incorrectly configured XML parser

These threats and others pose potentially serious problems to developers creating applications, components, and systems that depend on XML data. Clearly, a secure solution is needed. For this task, you can rely on the science and art of cryptography as the foundation for a Java-based solution.

Introduction to Cryptography
Cryptography can be defined as the science of and techniques for securing data by encrypting or transforming it into an unrecognizable format and then decrypting it back into the original format. Encryption is further defined as the process of taking data (known as cleartext) and altering it using a cryptographic key to produce ciphertext, which is unrecognizable to unauthorized entities/principals. Decryption?the reverse of encryption?can therefore be defined as the process of altering ciphertext using a cryptographic key to reproduce the original cleartext.

Some of the common elements involved in cryptographic processes are:

  • Public keys: Numbers associated with a particular entity such as an individual or an organization. Public keys are always part of a public/private key-pair and are intended to be publicly available to anyone intending to distribute confidential data back-and-forth with the key owner.
  • Private keys: Numbers associated with a particular entity such as an individual or an organization. Private keys are always part of a public/private key-pair and are intended to be known only by the key owner. Private keys are used to encrypt data that will be decrypted using the corresponding public key and vice versa.
  • Key-pair generators: Used to generate a pair of public and private keys that conform to a cryptographic algorithm.
  • Key factories: Used to convert opaque cryptographic keys into transparent key specifications (representations of the underlying key data), and vice versa.
  • Keystores: Databases used to manage a repository of keys.
  • Cryptographic algorithms: Sets of instructions and procedures, such as RSA or DES that define the processes for transforming the cleartext data.

Cryptography systems can be broadly classified as single-key or symmetric-key systems and two-key or public-key systems.

Symmetric Cryptography
When one party wishes to communicate secured data with another and they both share the same key for encrypting and decrypting the data, the process is known as symmetric cryptography. The shared key is referred to as a symmetric key. Because the same key is used to encrypt and decrypt the data with symmetric cryptography, the decryption process is essentially a reversal of the encryption process.

The main problem with symmetric cryptography is that the keys must be shared between parties involved in the encryption or decryption processes. The question then becomes; how do the parties securely exchange the keys? Passing the keys physically or within a corporate intranet is often considered secure; however, in the world of the public Internet, this is usually not an option.

Asymmetric Cryptography
In an effort to solve the key exchange problem of symmetric cryptography, asymmetric cryptography was introduced. Asymmetric cryptography replaces the single, shared key with a pair of mathematically related keys, known as the public key and private key or asymmetric keys. The public key and private key are generated together and can only be used for encryption and decryption when they are used together.

 
Figure 1. Asymmetric Data Exchange: The figure shows the steps involved in exchanging asymmetrically encrypted data.

With asymmetric cryptography, the public key is made available for use by anyone who wishes to communicate securely with the owner of the private key, while the owner keeps the private key confidential.

These steps are typical when exchanging secured data using asymmetric cryptography:

  1. The sender retrieves the recipient’s public key, usually from a trusted third-party such as a certificate authority.
  2. The sender then encrypts the data using the public key and sends the encrypted form of the data.
  3. The recipient uses a private key to decrypt the data.
 
Figure 2. Session-Key Data Exchange: People typically use asymmetric encryption only to exchange a shared symmetric key, called a session key, which both parties then use to encrypt and decrypt data for the duration of the exchange process.

Figure 1 illustrates a typical exchange using public keys and private keys:

Asymmetric cryptography is more expensive than symmetric encryption in terms of computational resources; therefore, developers ordinarily use asymmetric encryption only to exchange a shared symmetric key, which is then used by the sender and receiver for the duration of the data exchange in a symmetric-based conversation. The symmetric key is often referred to as a session key in this situation.

Figure 2 illustrates a typical exchange using public keys, private keys, and session keys:

Java Cryptography Architecture
JDK 1.1 introduced the Security API along with the Java Cryptography Architecture (JCA), a framework of classes, interfaces, and packages for developing cryptographic applications and components for the Java platform. The JCA’s provider-based architecture allows for heterogeneous cryptography implementations.

JDK 1.2 added additional services such as:

  • Keystore creation and management
  • Algorithm parameter management
  • Algorithm parameter generation
  • Key factory support to convert between different key representations.

Sun’s version of the JRE includes a default JCA provider package that contains implementations of the MD5 and SHA-1 algorithms, a certificate factory for X.509 certificates and certificate revocation lists, a pseudo-random-number generation algorithm, and a keystore implementation.

The Java Cryptography Extension (JCE) extends the JDK to include APIs for encryption, key exchange, and message authentication code. Together the JCE and the JCA provide a complete, platform-independent cryptography API; however, because the JCE provider included with JDK 1.4.x doesn’t support a number of algorithms such as RSA, it’s advisable to install a JCE provider that supports a more robust set of algorithms. The sample code provided with this article uses the BouncyCastle provider.

If you are running version 1.4.x of the Java SDK, follow these steps to add a JCE provider to your JDK/JRE environment:

  1. Download and install a JCE provider JAR file.
  2. Copy the JCE provider JAR file to your /jre/lib/ext/ directory.
  3. Edit the /jre/lib/security/java.security properties file using a text editor. Add the path to the JCE provider you’ve just downloaded to this file. You need to add a line complying with the following format, where is the order of precedence to be used by the JRE when evaluating security providers. Make sure that the Sun security provider remains at the highest precedence, with a value of 1.:
  4.    security.provider.=      org.bouncycastle.jce.provider.BouncyCastleProvider
  5. Save and close the file.

Java Cryptography and Asymmetric Keys
The JCE supports symmetric keys via the API classes and interfaces found in the java.security package. With this package, you can generate a public/private key pair as follows:

  1. Create an instance of a KeyPairGenerator for the desired algorithm.
  2. Initialize the KeyPairGenerator instance with the desired number of bits for the key-size.
  3. Call the generateKeyPair() method to generate the key pair.

In code, the process looks like this:

   KeyPairGenerator generator =      KeyPairGenerator.getInstance("RSA");   generator.initialize(1024);   KeyPair keyPair = generator.generateKeyPair();

Java Cryptography and Symmetric Keys
The JCE also supports symmetric keys through the API classes and interfaces in the javax.crypto.* packages. You create a symmetric key in much the same manner as you create a key pair, as follows:

   SecretKey key =      KeyGenerator.getInstance("DES").generateKey();

Next, you use a factory method of the Cipher class to create a Cipher instance to encrypt and decrypt data.

   Cipher cipher = Cipher.getInstance("DES");

At this point, you must call the init() method to specify which encrypt/decrypt function to perform. Finally, call the doFinal() method to perform the desired cryptographic function. The following code illustrates initializing a Cipher instance to perform encryption on a byte array of data using a previously generated symmetric key.

   byte[] data = "A simple string".getBytes();   cipher.init(Cipher.ENCRYPT_MODE, key);   byte[] encryptedBytes =         cipher.doFinal(data);

In the preceding example, both the encryption and the decryption were performed with the same Key object. But because encryption and decryption ordinarily occur on different VMs at different times, you need a method to transport the key securely.

XML Encryption
The W3C’s XML Encryption specification deals with data confidentiality using encryption techniques. As defined by the specification, with XML encryption, XML tags contain the encrypted data.

Some features of XML encryption are:

  • The ability to encrypt a complete XML file
  • The ability to encrypt a single element of an XML file
  • The ability to encrypt only the contents of an XML element
  • The ability to encrypt binary data within an XML file

The following sections discuss each of these features in more detail.

Encrypting a Complete XML File
Here’s a short sample XML file that can serve to demonstrate XML encryption:

           John Doe     125.00     1234-5678-8765-4321     July 6, 2005   

When you encrypt an entire XML file, the process simply replaces the root element ( in the sample) with an element wherein the encryption details, including the encrypted content, are contained. Now, look at Listing 1, which contains an encrypted version of the preceding sample XML file.

Toward the bottom of Listing 1, you’ll find the encrypted contents of the root element within the child element named .

Encrypting a Single Element
To encrypt a single element of an XML file, you specify the desired child element, rather than the root element of the input file as the element to encrypt. Look at Listing 2, which shows the results of encrypting only the element of the sample file.

Notice that the encryption process replaced the tag and its contents with an tag, while leaving the siblings of the element untouched.

Encrypting the Contents of an Element
Sometimes, you want to encrypt just the contents of an XML element rather than the entire element or document, leaving the element’s tag untouched. Listing 3 shows the results of encrypting just the contents of the element.

Notice that in Listing 3, the tag itself remains intact, but the contents are now contained within the element.

Introducing the Apache XML Security Framework
The cryptography tools that created the encrypted XML examples for this article depend on JCA and JCE for low-level cryptographic support. However, the high-level XML encryption and decryption functionality is provided by the Apache XML Security project, which you can download here. The classes and interfaces in this project make use of the JCA and JCE to provide implementations for W3C XML security standards such as XML Encryption and XML Signature.

The tools in the next sections demonstrate how to use the Apache XML Security project to generate symmetric keys for XML encryption and decryption.

Performing XML Encryption using Apache XML Security
The EncryptTool class reads input from a file, encrypts the contents of the file, and then stores the encrypted file to disk. The tool uses the Apache XML framework to create two symmetric keys for the following purposes:

  • to encrypt the actual XML file data
  • to encrypt the key used to encrypt the XML file data

The tool writes both the encrypted data and the encrypted encryption key to disk. You need the following imports statements in your code:

   package com.jeffhanson.security.encryption;      import java.io.File;   import java.io.FileOutputStream;   import java.io.IOException;      import java.security.Key;      import javax.crypto.SecretKey;   import javax.crypto.KeyGenerator;      import org.apache.xml.security.keys.KeyInfo;   import org.apache.xml.security.encryption.XMLCipher;   import org.apache.xml.security.encryption.EncryptedData;   import org.apache.xml.security.encryption.EncryptedKey;      import org.w3c.dom.Document;   import org.w3c.dom.Element;      import javax.xml.transform.TransformerFactory;   import javax.xml.transform.Transformer;   import javax.xml.transform.dom.DOMSource;   import javax.xml.transform.stream.StreamResult;   import javax.xml.transform.OutputKeys;      public class EncryptTool   {...}

First, initialize the Apache XML framework?in this case, to the default values.

      static      {         org.apache.xml.security.Init.init();      }

Next, use the following method to read and parse the XML file to be encrypted. The method creates and returns a DOM document.

      private static Document parseFile(String fileName)         throws Exception      {         javax.xml.parsers.DocumentBuilderFactory dbf =            javax.xml.parsers.DocumentBuilderFactory.newInstance();         dbf.setNamespaceAware(true);         javax.xml.parsers.DocumentBuilder db =            dbf.newDocumentBuilder();         Document document = db.parse(fileName);            return document;      }

You need to generate the key used to encrypt the data-encryption tool. The generated key is necessary to store and/or transport the data-encryption tool securely.

      private static SecretKey GenerateKeyEncryptionKey()         throws Exception      {         String jceAlgorithmName = "DESede";         KeyGenerator keyGenerator =            KeyGenerator.getInstance(jceAlgorithmName);         SecretKey keyEncryptKey = keyGenerator.generateKey();            return keyEncryptKey;      }

To store the key-encryption key, use the following method, which writes the key-encryption key to a file. The decryption tool can then retrieve it later and use it to decrypt the encrypted data.

      private static void storeKeyFile(Key keyEncryptKey)         throws IOException      {         byte[] keyBytes = keyEncryptKey.getEncoded();         File keyEncryptKeyFile = new File("keyEncryptKey");         FileOutputStream outStream =            new FileOutputStream(keyEncryptKeyFile);         outStream.write(keyBytes);         outStream.close();            System.out.println("Key encryption key stored in: "            + keyEncryptKeyFile.toURL().toString());      }

Next, you generate the symmetric data-encryption key using the following method.

      private static SecretKey GenerateSymmetricKey()         throws Exception      {         String jceAlgorithmName = "AES";         KeyGenerator keyGenerator =            KeyGenerator.getInstance(jceAlgorithmName);         keyGenerator.init(128);         return keyGenerator.generateKey();      }

And finally, you write the encrypted document to a file as follows:

      private static void writeEncryptedDocToFile(         Document doc, String fileName)         throws Exception      {         File encryptionFile = new File(fileName);         FileOutputStream outStream =            new FileOutputStream(encryptionFile);            TransformerFactory factory =            TransformerFactory.newInstance();         Transformer transformer = factory.newTransformer();         transformer.setOutputProperty(            OutputKeys.OMIT_XML_DECLARATION, "no");         DOMSource source = new DOMSource(doc);         StreamResult result = new StreamResult(outStream);         transformer.transform(source, result);            outStream.close();            System.out.println(            "Encrypted XML document written to: "            + encryptionFile.toURL().toString());      }

Controlling the Encryption
In the sample code, the main method performs the necessary steps to encrypt an XML file and store the encrypted form of the file to disk, along with the encrypted key needed to decrypt the file.

      public static void main(String args[])         throws Exception      {         Document document = parseFile(args[0]);         Key symmetricKey = GenerateSymmetricKey();         Key keyEncryptKey = GenerateKeyEncryptionKey();

The code follows the steps shown earlier. It retrieves and parses the XML file to be encrypted. Then, it generates the symmetric key that will be used to encrypt the data. Next, it generates the symmetric key that will be used to encrypt the data-encryption key, and stores it to a file.

The following block creates and initializes a cipher with the key-encryption key. Depending on the value of the first parameter, the init method initializes the cipher for one of the following four operations:

  • Encryption
  • Decryption
  • Key wrapping
  • Key unwrapping

For the key-encryption key, set the mode to WRAP_MODE, which will convert the key to bytes, allowing it to be transferred securely from one place to another?in this case, the file system.

         XMLCipher keyCipher = XMLCipher.getInstance(            XMLCipher.TRIPLEDES_KeyWrap);         keyCipher.init(XMLCipher.WRAP_MODE, keyEncryptKey);         EncryptedKey encryptedKey =            keyCipher.encryptKey(document, symmetricKey);

Now, you specify the element to be encrypted. Remember, to encrypt the entire file, specify the root element; otherwise, retrieve and specify the specific child element you want to encrypt:

         Element rootElement = document.getDocumentElement();         Element elementToEncrypt = rootElement;         if (args.length > 2)         {            elementToEncrypt =                (Element) rootElement.getElementsByTagName(               args[2]).item(0);            if (elementToEncrypt == null)            {               System.err.println("Unable to find element: "                  + args[2]);               System.exit(1);            }         }

The following block creates and initializes a cipher with the data-encryption key. In this case, the mode is set to ENCRYPT_MODE, because it will be used to encrypt the data.

         XMLCipher xmlCipher =            XMLCipher.getInstance(XMLCipher.AES_128);         xmlCipher.init(XMLCipher.ENCRYPT_MODE, symmetricKey);

The following code adds the encrypted key’s key-info to the encrypted data element being built.

         EncryptedData encryptedDataElement =            xmlCipher.getEncryptedData();         KeyInfo keyInfo = new KeyInfo(document);         keyInfo.add(encryptedKey);         encryptedDataElement.setKeyInfo(keyInfo);

At this point, you can call the doFinal method to do the actual encryption.

         boolean encryptContentsOnly = true;         xmlCipher.doFinal(document,            elementToEncrypt, encryptContentsOnly);

And finally, write the encrypted document to a file.

         writeEncryptedDocToFile(document, args[1]);      }   }

Performing XML Decryption using Apache XML Security
The DecryptTool class performs the steps in reverse: It reads an encrypted file from disk, decrypts the contents of the file using a previously stored key, and then stores the decrypted file to disk. The imports statements are similar to those used in the EncryptTool, and you can download the source code so I won’t show them here.

You initialize the Apache XML Security Framework to the default values using the same code as was used to encrypt the data earlier.

      static      {         org.apache.xml.security.Init.init();      }

Next, you load the encrypted document from a file. The following method reads and parses the XML file to be decrypted, creating and returning a DOM document.

      private static Document loadEncryptedFile(         String fileName) throws Exception      {         File encryptedFile = new File(fileName);         javax.xml.parsers.DocumentBuilderFactory dbf =            javax.xml.parsers.            DocumentBuilderFactory.newInstance();         dbf.setNamespaceAware(true);         javax.xml.parsers.DocumentBuilder builder =            dbf.newDocumentBuilder();         Document document = builder.parse(encryptedFile);            System.out.println(            "Encryption document loaded from: " +            encryptedFile.toURL().toString());         return document;      }

The following method reads a previously stored key from a file. You’ll use it to decrypt the encrypted data-decryption key.

      private static SecretKey loadKeyEncryptionKey()         throws Exception      {         String fileName = "keyEncryptKey";         String jceAlgorithmName = "DESede";            File kekFile = new File(fileName);            DESedeKeySpec keySpec =            new DESedeKeySpec(            JavaUtils.getBytesFromFile(fileName));         SecretKeyFactory skf =            SecretKeyFactory.getInstance(jceAlgorithmName);         SecretKey key = skf.generateSecret(keySpec);            System.out.println("Key encryption key loaded from: "            + kekFile.toURL().toString());         return key;      }

To write the decrypted document to a file, use the following method.

      private static void writeDecryptedDocToFile(         Document doc, String fileName) throws Exception      {         File encryptionFile = new File(fileName);         FileOutputStream outStream =            new FileOutputStream(encryptionFile);            TransformerFactory factory =            TransformerFactory.newInstance();         Transformer transformer = factory.newTransformer();         transformer.setOutputProperty(            OutputKeys.OMIT_XML_DECLARATION, "no");         DOMSource source = new DOMSource(doc);         StreamResult result = new StreamResult(outStream);         transformer.transform(source, result);            outStream.close();            System.out.println("Decrypted data written to: " +            encryptionFile.toURL().toString());      }

Again, a main() method performs the necessary steps to decrypt an already encrypted XML file using a previously stored and encrypted key.

      public static void main(String args[])         throws Exception      {

First, it retrieves the XML file to be decrypted and parsed into a DOM document.

         Document document = loadEncryptedFile(args[0]);   

Next, it retrieves the encrypted data element from the DOM document.

         String namespaceURI =            EncryptionConstants.EncryptionSpecNS;         String localName = EncryptionConstants._TAG_ENCRYPTEDDATA;         Element encryptedDataElement =            (Element)document.getElementsByTagNameNS(            namespaceURI, localName).item(0);   

Then, it retrieves the key that will be used to decrypt the data-decryption key from disk.

         Key keyEncryptKey = loadKeyEncryptionKey();   

The following block creates and initializes a cipher with the data-decryption key. The mode in this case is set to DECRYPT_MODE, because it will be used to decrypt the data.

         XMLCipher xmlCipher = XMLCipher.getInstance();         xmlCipher.init(XMLCipher.DECRYPT_MODE, null);         xmlCipher.setKEK(keyEncryptKey);   

It calls the doFinal() method to perform the actual decryption.

         xmlCipher.doFinal(document, encryptedDataElement);

And finally, it writes the decrypted document to a file.

         writeDecryptedDocToFile(document, args[1]);      }   }

XML Encryption provides end-to-end security for applications that require secure exchange of structured data. XML itself is the most popular technology for structuring data, and therefore XML-based encryption is the natural way to handle complex requirements for security in data interchange applications.

The Java cryptography architecture (JCA) is a framework of classes, interfaces, and packages for developing cryptographic applications and components for the Java platform. The JCA is built around a provider-based architecture in order to allow for heterogeneous cryptography implementations.

The Apache XML Security project makes use of the JCA to provide implementations of W3C XML security standards such as XML-Signature and XML-Encryption.

This article discussed how to create tools and applications to manage the complexities of XML encryption using the Java programming language and the Apache XML Security framework.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist