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
Parameter
element of Listing 1 has been replaced by anEncryptedData
element in Listing 2. TheEncryptedData
element contains the encrypted form of theParameter
element. The encryption algorithm used for this XML encryption is triple DES as specified by theEncryptionMethod
child of theEncryptedData
element. - The
EncryptedData
element in Listing 2 does not contain any key information. The corresponding key information resides inside thewsse:Security
element in the SOAP header, which contains anEncryptedKey
element. TheEncryptedKey
element wraps a symmetric triple DES secret key in encrypted form. Notice from Listing 2 that theEncryptionMethod
used to produce theEncryptedKey
is 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
EncryptedData
element with its corresponding key used for encryption? Look at theReferenceList
element inside theEncryptedKey
element of Listing 2. TheReferenceList
element has a child element namedDataReference
, whoseURI
attribute points to theEncryptedData
element 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
EncryptedData
element in Listing 4 contains aKeyName
element, 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
EncryptedKey
element inside thewsse:Security
header. Instead we just have aReferenceList
element that refers to theEncryptedData
element inside the SOAP body. This type ofReferenceList
element does not specify the key used for producing theEncryptedData
element. ThisReferenceList
only 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:Signature
element will appear before theReferenceList
element. 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
Token
interface we presented in Listing 3 of the first column of this series. TheToken
interface contains several methods and we'll need to implement each of them in a Java class namedEncryptedKeyToken
. - Implement the constructor of the
WSSMessage
class we introduced in Listing 2 of the first column of this series. - Implement the
encryptElement()
andencryptElementWithXPath()
methods of theWSSMessage
class.
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:
keyStoreFileName
is 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.keyStorePassword
is the string representation of the password that we need in order to access the key store.KeyName
is the string representing the name of the recipient's public key that is stored in the key store.wsuKeyID
is thewsu:Id
of the encrypted key token. The token will author thisID
in the token's XML format.wssMessage
is the parentWSSMessage
object 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:Id
of theEncryptedData
structure 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 thetoken
object. 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 theSecurity
header, 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 aWSSMessage
object, passing a plain text SOAP message string to theWSSMessage
constructor. - It then instantiates an
EncryptedKeyToken
object 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 theWSSMessage
object, passing the token as well as thewsu:Id
of 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 theEncryptedKeyToken
class. We have shown theEncryptedKeyTokenWithCrossReference
class in Listing 14. - The
EncryptedKeyTokenWithCrossReference
constructor takes the same parameters as theEncryptedKeyToken
constructor. Since we don't need any additional functionality in theEncryptedKeyTokenWithCrossReference
constructor, theEncryptedKeyTokenWithCrossReference
constructor simply calls the super's constructor. - The
encrypt()
method in theEncryptedKeyTokenWithCrossReference
class needs just one extra step as compared to theencrypt()
method in theEncryptedKeyToken
class. The extra step is to add theSecurityTokenReference
structure (that we described in Listing 3) wrapped inside aKeyInfo
element. Therefore, in theencrypt()
method of theEncryptedKeyTokenWithCrossReference
class, we simply call the super'sencrypt()
method and then add aSecurityTokenReference
element.
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. |