Implementing XML Encryption in Java
April 21, 2004
In the first column of this series, we presented a high-level WSS4J API, which we wish to implement in this series of columns. In the second column, we discussed the various Java components we are going to use in implementing the WSS4J API. The most important component is XML Security Suite for Java (XSS4J) from IBM alphaWorks. The second column also demonstrated how to use XSS4J for XML encryption.
In this column, we will use the XSS4J concepts discussed in the previous column to implement the XML encryption features in our WSS4J API.
Before we start the discussion, we would like to point out a name clash issue. There's an open source Web Services Security project at SourceForge.net (recently moved to Apache's web site) that shares the same WSS4J name. The WSS4J API we are developing in this series of columns is purely for educational purposes and aims at demonstrating the use of WSS in Java applications. The WSS4J open source project at SourceForge (now at Apache) is an independent project and is not part of our effort.
A WSS Message Before and After XML Encryption
We are going to add support for both symmetric and asymmetric keys in our WSS4J API. Listing 1 shows a simple SOAP message we will use as sample input to our XML encryption process. We will encrypt a portion of Listing 1 to demonstrate the working of our WSS4J API.
We are going to perform three types of XML encryption. Listings 2, 3, and 4 are three WSS messages that show the outcome of our encryption process. Listings 2, 3, and 4 are slightly different from each other. As we have already covered the details of WSS format in our Web Services Security series (especially in Part 4), we will not go into the details of WSS format. Here we will only highlight the differences between Listings 2, 3, and 4:
Listing 2 shows the following:
- If you look at the contents of the SOAP body in Listings 1 and 2, you will see that the first
Parameterelement of Listing 1 has been replaced by anEncryptedDataelement in Listing 2. TheEncryptedDataelement contains the encrypted form of theParameterelement. The encryption algorithm used for this XML encryption is triple DES as specified by theEncryptionMethodchild of theEncryptedDataelement. - The
EncryptedDataelement in Listing 2 does not contain any key information. The corresponding key information resides inside thewsse:Securityelement in the SOAP header, which contains anEncryptedKeyelement. TheEncryptedKeyelement wraps a symmetric triple DES secret key in encrypted form. Notice from Listing 2 that theEncryptionMethodused to produce theEncryptedKeyis RSA and the key used for this RSA encryption is the tour operator's public key. We covered the details of such encrypted keys in Step 2 of the Using XSS4J for XML Encryption section in the second column. - How do we relate an
EncryptedDataelement with its corresponding key used for encryption? Look at theReferenceListelement inside theEncryptedKeyelement of Listing 2. TheReferenceListelement has a child element namedDataReference, whoseURIattribute points to theEncryptedDataelement we produced using the encrypted key.
The EncryptedKey element is a security token in WSS terms. Although the XML
encryption specification simply calls it an encryption key, the WSS specification
talks
about abstract (or more general) tokens. This means we can treat cryptographic keys
as a
type of security token. Therefore, while implementing WSS, we will treat an
EncryptedKey structure as a type of token and call it an encrypted key
token.
Now look at Listing 3, which is a very
slightly modified form of Listing 2. The
only difference between Listings 2 and 3 is that the EncryptedData element in
Listing 3 contains a KeyInfo
element, which wraps a SecurityTokenReference element. The
ecurityTokenReference element has a Reference child element,
whose URI attribute refers to the wsu:Id of the
EncryptedKey that was used to produce the EncryptedData.
If there are more than one EncryptedData and EncryptedKey
elements in a WSS message, then it is computationally more expensive to find the
EncryptedKey corresponding to a particular EncryptedData
structure in Listing 2 as compared to Listing 3. That's because in Listing 2 you
have to look into the ReferenceList elements of several
EncryptedKey structures until you find the required EncryptedKey
structure. On the other hand, if you have to find the EncryptedData structure
corresponding to a particular EncryptedKey Listing 3, you can read the
URI attribute value of the Reference element and jump directly
to the required EncryptedKey structure.
We will call Listing 3 as an encrypted key token with cross reference.
Now look at Listing 4, which shows the following:
- The
EncryptedDataelement in Listing 4 contains aKeyNameelement, which specifies the name of the key used for encryption. Here we are assuming that the recipient of this message can locate the actual key corresponding to this name. Therefore, we are not concerned about how an application will map a key name to the actual key. - There's no
EncryptedKeyelement inside thewsse:Securityheader. Instead we just have aReferenceListelement that refers to theEncryptedDataelement inside the SOAP body. This type ofReferenceListelement does not specify the key used for producing theEncryptedDataelement. ThisReferenceListonly specifies the order in which different actions take place in the header (e.g. if you encrypt and then sign a portion of a message, theds:Signatureelement will appear before theReferenceListelement. For details please refer to the discussion accompanying Listings 7 and 8 in the fourth article of my Web Services Security series).
We will call this ReferenceList element a reference list only
token.
We will implement the first two types of tokens (encrypted key token and encrypted key token with cross reference) in this column and the third type (reference list only token) in the next column of this series.
Implementing the Encrypted Key Token
Now let's see how to implement and use an encrypted key token. In order to implement and use an encrypted key token, we'll need to do the following:
- Implement the
Tokeninterface we presented in Listing 3 of the first column of this series. TheTokeninterface contains several methods and we'll need to implement each of them in a Java class namedEncryptedKeyToken. - Implement the constructor of the
WSSMessageclass we introduced in Listing 2 of the first column of this series. - Implement the
encryptElement()andencryptElementWithXPath()methods of theWSSMessageclass.
Let's do these steps one by one.
Implementing the Token Interface
Have a look at the EncryptedKeyToken class shown in Listing 5. This class implements the
Token interface and is responsible for performing the second, third, and
fourth steps of using XSS4J for XML encryption that we described in the second column of this
series. The final result of using an encrypted key token is what we have already seen
in Listing 2.
Have a look at the EncryptedKeyToken constructor, which takes the following
parameters:
keyStoreFileNameis the full path name of a Java key store file. The key store file contains the public key of the recipient of the WSS message.keyStorePasswordis the string representation of the password that we need in order to access the key store.KeyNameis the string representing the name of the recipient's public key that is stored in the key store.wsuKeyIDis thewsu:Idof the encrypted key token. The token will author thisIDin the token's XML format.wssMessageis the parentWSSMessageobject that wraps this token.
The constructor first stores the parent WSSMessage object, the key token ID,
and the key name in their corresponding fields. It then loads the encryption template
in a
DOM object. We demonstrated the use of an encryption template in Listing 3 of the second
column. Listing 6 shows the
encryption template that our EncryptedKeyToken will use. Listing 6 is slightly
different from the encryption template we introduced in Listing 3 of the second
column. We will explain the difference between the two templates later.
The EncryptedKeyToken constructor then opens the key store file, loads it into
an input stream, and loads the input stream into a Java KeyStore object. It
then instantiates a KeyStoreKeyInfoResolver object and calls its
setAlgorithmFactory() method to set the algorithm factory to be used during
XML encryption. Recall that we have covered the details of these steps in the Using XSS4J for XML
Encryption section of the second column of this series.
Notice that the EncryptedKeyToken constructor cannot access the key in the key
store. That's because we have not supplied the password required to access the key
residing
inside the Java KeyStore object. The application that instantiates the
EncryptedKeyToken object will also call the
EncryptedKeyToken.setSecret() method to specify the password to access the
key.
The setSecret() method (Listing
5) takes the key access password in byte array form, converts the byte array to a
character array, and supplies the character array to the
KeyStoreKeyInfoResolver. The KeyStoreKeyInfoResolver will
internally fetch the required key.
Now have a look at the encrypt() method in Listing 5. This method authors the encrypted
form (an EncryptedData structure) of the plain text element that we want to
encrypt. The encrypt() method takes three parameters:
wsuEncryptedElementID:Thewsu:Idof theEncryptedDatastructure that theencrypt()method will author.encryptionAlgo:The algorithm to be sued for encryption.elementWantToEncrypt:The DOM element representation of the XML element that we want to encrypt.
The encrypt() method performs the following eight steps (marked with comments
in Listing 5):
Step 1:
KeyStoreKeyInfoResolver can do both encryption and decryption. So the first
step is to set the mode of operation of the key resolver to "encryption."
Step 2: If you look at the encryption template we are using (Listing 6), you will find that there is a
ReferenceList child of the EncryptedKey element. This
ReferenceList element was not present in the encryption template we
introduced in Listing 3
of the second column. We need this ReferenceList element in the
EncryptedKey because the final objective of implementing the
EncryptedKeyToken class is to author the structure shown in Listing 2 (which contains a
ReferenceList element).
Notice from Listing 2 that the
DataReference child of the ReferenceList element refers to the
wsu:Id of the EncryptedKey that was produced using the
EncryptedKey. Therefore, our second step is to add a URI
attribute to the DataReference element. The URI attribute refers
to the wsu:Id of the EncryptedData that we are about to
author.
Step 3: The third step is to author the wsu:Id of the
EncryptedData that we will shortly author. At this moment the encryption
template looks like as shown in Listing
7.
Step 4: We also need to tell that it is the encryption algorithm that we want to
use. For this purpose, our fourth step is to set the value of the Algorithm attribute
of the
EncryptionMethod element in Listing 7.
Step 5: The EncryptedKey element has its own wsu:Id. So
the fifth step is to author the wsu:Id of the EncryptedKey
element.
Now our template is all set and looks like what's shown in Listing 8.
Step 6: The next step is to XML encrypt the input DOM element using the
procedure we described while discussing step 4 of KeyInfo and encrypt it using
the Using XSS4J for XML
Encryption section of the second article of this series.
Step 7: Assuming we encrypted the first Parameter element of Listing 1, it will look like Listing 9 after encryption. If you compare
Listing 2 and Listing 9, you will find that the
EncryptedData element in Listing 9 is not the required form that we were
trying to author (see EncryptedData in Listing 2).
The main difference between the EncryptedData elements in Listing 2 and
Listing 9 is that the EncryptedData element in Listing 9 contains a KeyInfo
element, which in turn contains the EncryptedKey element. In Listing 2, the same
EncryptedKey element resides in the SOAP header.
Therefore, the seventh step is to copy the EncryptedKey element and store it
as a DOM element object named tokenElement.
Step 8: Now we remove the entire KeyInfo child of the
EncryptedData element, so that the EncryptedData element in Listing 10 looks exactly like the
EncryptedData in Listing
2.
The EncryptedKeyToken class contains another public method named
getXMLString(), which returns the string form of the encrypted key token.
Notice that in step 7 above we stored the EncryptedKey in an object named
tokenElement. The getXMLString() method simply returns the
string form of the tokenElement object.
Now let's see how the WSSMessage class will use the
EncryptedKeyToken class to author a WSS message containing an encrypted key
token.
Implementing the WSSMessage Constructor
Listing 11 shows the
WSSMessage implementation. Have a look at the WSSMessage
constructor. The constructor is very simple. It just takes a plain text SOAP message
and
loads it into a DOM object. For the moment, our WSSMessage class
constructor is very humble. It will become more complex in later columns as our
WSSMessage class grows in functionality.
Implementing Encryption Methods in the WSSMessage Class
Note that the WSSMessage class, as described in the first column of this
series, represents the whole WSS message (as opposed to the
EncryptedKeyToken class, which represents only a single token). Therefore,
the encrypt() method of the EncryptedKeyToken class only handles
one type of token, while the encryption methods in the WSSMessage class should
be able to handle different types of tokens.
Look at the encryptElement() and encryptElementWithXPath()
methods in Listing 11. Each method takes
four parameters as already described in the first column. Each of
the two methods first locates the element to be encrypted and then calls a method
named
encrypt(). The encrypt() method is private and performs the
following tasks:
- It checks what type of token is being used. In our case, we are using an encrypted key token.
- If the token being used is an encrypted key token, the
encrypt()element simply calls thetoken.encrypt()method. - The
encrypt()method works according to the eight-step procedure described above and encrypts the plain text element. - Next, the
WSSMessage.encrypt()method calls thegetXMLString()method of thetokenobject. ThegetXMLString()method returns the string representation of the encrypted key token. - After fetching the string representation of the token, the
WSSMessage.encrypt()method places the token inside theSecurityheader, which resides inside the SOAP header.
Listing 12 shows the final result when
the first Parameter element of Listing 1 is encrypted using an encrypted key token.
In a similar manner the WSSMessage.encrypt() method will handle different
types of tokens.
Using our WSSMessage Class in an XML Encryption Application
The final question is how an application will use our WSSMessage and
EncryptedKeyToken classes. Have a look at Listing 13, which shows the
main() method of a WSSEncryptionSample class. You can see the
following steps in the main() method of Listing 13, which demonstrates how easy and
simple it is to use our WSS4J API for encryption:
- The
main()method first instantiates aWSSMessageobject, passing a plain text SOAP message string to theWSSMessageconstructor. - It then instantiates an
EncryptedKeyTokenobject and then calls itssetSecret()method passing the key access password along with the method invocation call. - The third step is to call the
encryptElement()method of theWSSMessageobject, passing the token as well as thewsu:Idof the plain text element to be encrypted.
Implementing the Encrypted Key Token with a Cross Reference
This section demonstrates how we can easily enhance our EncryptedKeyToken to
implement support for the encrypted key token with cross reference that we introduced
in Listing 3. This needs the following
steps:
- Write a Java class named
EncryptedKeyTokenWithCrossReference, which extends theEncryptedKeyTokenclass. We have shown theEncryptedKeyTokenWithCrossReferenceclass in Listing 14. - The
EncryptedKeyTokenWithCrossReferenceconstructor takes the same parameters as theEncryptedKeyTokenconstructor. Since we don't need any additional functionality in theEncryptedKeyTokenWithCrossReferenceconstructor, theEncryptedKeyTokenWithCrossReferenceconstructor simply calls the super's constructor. - The
encrypt()method in theEncryptedKeyTokenWithCrossReferenceclass needs just one extra step as compared to theencrypt()method in theEncryptedKeyTokenclass. The extra step is to add theSecurityTokenReferencestructure (that we described in Listing 3) wrapped inside aKeyInfoelement. Therefore, in theencrypt()method of theEncryptedKeyTokenWithCrossReferenceclass, we simply call the super'sencrypt()method and then add aSecurityTokenReferenceelement.
We have demonstrated the use of EncryptedKey structure in WSS messages. Next
time, we will start by implementing the reference list token that we introduced in
Listing 3. We will also discuss the use of
XSS4J for XML digital signatures in the next column of this series.
ResourcesDownload the source code zip of this article. The zip contains the source as well as the compiled form of the code. The zip also contains a readme.txt file that will help you compile and run the code of this article. Read the first and second column of this series, which introduced the WSS4J API. Also check out the first, second, third, and fourth parts of the series of article on Web Services Security from the same author. Check out the official JAXP and JCA/JCE pages at Java.Sun.com. Visit BouncyCastle.org to download the latest version of their
provider. We tested the code of this article with the
Download your copy of XSS4J from IBM alphaWorks web site. Download ICU4J from here. This tutorial at IBM developerWorks will help you learn the basics of cryptography in Java. Read this article at OnJava.com that introduces JAXP. Also check out this page about JAXP. Check out these articles on XML Encryption (Part 1 and Part 2) by the same author. |