Web services! Chances are you've already developed some Web services yourself, or at the very least have heard something about them. While people have put forth numerous Web services definitions, the vision remains the same: interconnect everything from your handheld device and your PC to that big black box in that cold room over there. Indeed, a Web service world could prove more egalitarian in that the distinction between clients and servers blurs. Anyone can be a server or a client—it's just a matter of who possesses something and who wants something.
Call me paranoid, but my first question when I learned about the Web services vision was: "How do they secure this thing?" In a Web services world, everyone communicates with everyone else. Many intermediaries could exist between, say, your supplier, you, and your buyer. What if one of these intermediaries becomes compromised? The communication path then proves only as secure as the least secure hop along the way. End-to-end security becomes vitally important if you want to do something more significant than those five-minute, 10-lines-of-code Hello World services in the tutorials. And the security questions don't stop there: What about authentication? Trust? Authorization?
Microsoft, IBM, and VeriSign have introduced specifications to answer questions ranging from privacy to trust to federation. Underlying these higher-level specifications resides WS-Security (Web Services Security), which describes enhancements to SOAP (Simple Object Access Protocol) messaging to include message integrity and message confidentiality. These two basic features, in turn, stem from the topic of this two-part series: XML Encryption and XML Signature.
Read the whole series on XML security, "Yes, You Can Secure Your Web Services Documents:"
- Part 1: XML Encryption keeps your XML documents safe and secure
- Part 2: XML Signature ensures your XML documents' integrity
Note: You can download this article's source code from Resources.
Why XML Encryption?
Standards like SSL/TLS (Secure Socket Layer/Transport Level Security) for point-to-point security already exist. S/MIME (Secure/Multipurpose Internet Mail Extensions) and PGP (Pretty Good Privacy) ensure that only a message's intended recipient can read that message—thereby achieving end-to-end security. Considering that, what gap does XML Encryption fill?
It turns out that (fortunately) the World Wide Web Consortium (W3C) XML Encryption Working Group did have good reasons for XML Encryption. XML Encryption addresses two requirements that none of the above standards can satisfy together: end-to-end security and selective encryption.
If you've ever worked in the payment domain, you no doubt have noticed that the big players never feel satisfied with SSL/TLS. They keep coming up with these so-called secure payment specifications, from SET (Secure Electronic Transaction), which didn't take off, to the newer ones like Visa's 3D Secure and MasterCard's SPA-UCAF (Secure Payment Application/Universal Cardholder Authentication Field).
Why? Partly because an online credit card payment involves three parties: a cardholder (that's you), an online merchant, and your bank. Ideally, all parties should be authenticated to one another, and the secure communication path should stretch from the cardholder all the way to the bank. But SSL/TLS is simply not designed for such a scenario; SSL/TLS only handles point-to-point security. Thus, if you rely only on SSL, you cannot send your credit card number to the bank through the merchant without the merchant knowing it.
The same goes for Web services, although at a slightly different level. Web service application topologies include all sorts of devices, PCs, proxies, demilitarized zones, gateways, and who knows what else. Consequently, many intermediaries come between two communicating parties. SSL/TLS may secure the path between any two, but not from one end to the other. Something new, therefore, must address this problem.
Standards like S/MIME and PGP do ensure that a message can only be read by its intended recipient. Unfortunately, these standards (as well as SSL/TLS) treat each message as a whole; these standards cannot selectively encrypt part of an XML document. Such selective encryption capabilities will prove important, especially when it comes to a workflow scenario where a document may be processed by several applications, or signed, viewed, or approved by numerous people.
Let's take as an example the aforementioned online payment scenario. In practice, many messages travel to and fro among the three parties before the payment itself happens, but conceptually, what happens proves simple: a message travels from the consumer to the merchant to the bank. Such a message may contain two things: information about the goods bought and instructions for the bank to pay the merchant. The bank doesn't need to know about the former, and the merchant doesn't need to know about the latter. The message, therefore, must be partitioned in such a way that a merchant cannot see the bank's part of the message, and vice versa. This is difficult to achieve; it's impossible with S/MIME or PGP because these standards will encrypt the entire XML document. XML Encryption's selective encryption feature, in contrast, allows an XML-based solution for such problems.
XML Encryption: The core element
In XML Encryption, your plaintext is either an element or that element's content (that's the finest granularity you get—you can't encrypt, say, half an element's content). After encryption, you get an XML element called
EncryptedData, containing the ciphertext in Base64-encoded format. That
EncryptedData element replaces your plaintext. That is, if you encrypt element B in this snippet below:
<A> <B>blah</B> </A>
you'll get back something like this:
<A> <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns=...> <!-- some info, including the ciphertext --> </EncryptedData> </A>
Whereas if you encrypt element B's content, the result will look similar to this:
<A> <B> <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Content" xmlns=...> <!-- some info, including the ciphertext --> <EncryptedData> </B> </A>
Note the difference between the
Type attributes in the two examples. From looking at this attribute, we can tell immediately whether the plaintext is an element of just its content.
EncryptedData's structure proves straightforward. I copied and pasted the structure below directly from XML Encryption's W3C page. In this structure, according the W3C, "
? denotes zero or one occurrence;
+ denotes one or more occurrences;
* denotes zero or more occurrences; and the empty element tag means the element must be empty":
<EncryptedData Id? Type?> <EncryptionMethod/>? <ds:KeyInfo> <EncryptedKey>? <AgreementMethod>? <ds:KeyName>? <ds:RetrievalMethod>? <ds:*>? </ds:KeyInfo>? <CipherData> <CipherValue>? <CipherReference URI?>? </CipherData> <EncryptionProperties>? </EncryptedData>
EncryptedData's most important element,
CipherData, either directly contains the ciphertext in
CipherValue, or, if
CipherReference is used, a reference to it. The other elements are optional because usually the receiving party already has the information they contain. For instance,
EncryptionMethod lets you specify the algorithm and key size, but usually you and the other party will agree on those beforehand. The same goes for
KeyInfo: it gives you the flexibility to give the other party the material to decrypt your message, but you'd probably want to, say, sent it through some out-of-band mechanism.
EncryptionProperties serves as another optional element used for, well, optional information, such as a date/time stamp.
What happens if you want to do super-encryption? That is, what if you want to encrypt your ciphertext with a different key, for instance? The standard mandates that you encrypt the whole
EncryptedData element, nothing more, nothing less. As a result, inside the
CipherData element, you don't get the result of encrypting just your ciphertext, but the whole
EncryptedData element around it as well.
Libraries you can use now
XML Encryption is very new. IBM has submitted a Java Specification Request (JSR 106) that will define its standard API. Currently, however, no standard exists on how the API should look. At the time of this writing, only a handful of libraries provide XML Encryption. I use one of them, the XML Security Suite from IBM (XSS4J), in the rest of this article for illustration purposes. (Don't forget to check the sidebar below, "Give Your Serializable Class a serialVersionUID," to learn about something weird I encountered the first time I tried this library.)
XML Encryption in practice
To see XML Encryption in practice, I'll use it on the document in Listing 1 below. Let's pretend that this example document represents a payment request in the latest payment protocol someone just designed:
<PurchaseOrderRequest> <Order> <Item> <Code>C3PO</Code> <Description>Some almost illegal stuff</Description> </Item> <Quantity>1</Quantity> </Order> <Payment> <CreditCard> <BrandId>Brand X</BrandId> <Number>1111222233334444</Number> <ExpiryDate>20030408</ExpiryDate> </CreditCard> <PurchaseAmount> <Amount>123623</Amount> <Currency>840</Currency> <Exponent>-2</Exponent> </PurchaseAmount> </Payment> </PurchaseOrderRequest>
Suppose you want to encrypt two things with two different keys: you'll encrypt the
Item element's content using the merchant's key, and the entire
CreditCard element using the bank's key. What do you need? You need two keys and the code to encrypt the document, of course. Let's get 'em!
Generate the keys
Let's generate two keys and put them into a JCE (Java Cryptography Extension) keystore. Using
keytool, type the following command:
keytool -genkey -alias merchant -keyalg RSA -keysize 1024 -keystore jwstore -storepass jwpassword -storetype jceks
and answer the questions that ensue to generate a key for the merchant. Let's use
merchantpassword as the password. Do the same for the bank by running the utility again, supplying
bank as the alias and
bankpassword as the password.
Write the code
Now let's look at the encrypting code. XSS4J's API may look a bit complex at first, but you'll find it easy once you figure out how it works. Think about it—if I asked you to encrypt an element, what information would you need? You'd need the following:
- The element to encrypt
- Whether to encrypt the whole element or just its content
- The encryption method to use (that is, key size and algorithm)
- The key to use in encrypting
- A way to get the key
I've written a helper class that, well, helps you to do just those things. With this class, encrypting XML documents is easy:
// Open a document we're going play with. XMLPlainText xmlPlainText = new XMLPlainText( new FileInputStream("payment.xml")); // Let's load the keystore. KeyStore ks = KeyStore.getInstance("jceks"); ks.load(new FileInputStream("jwstore"), "jwpassword".toCharArray()); // Tell the class how to get // the keys. KeyStoreKeyInfoResolver is just // one way to get the keys. KeyStoreKeyInfoResolver kskiResolver = new KeyStoreKeyInfoResolver(ks); kskiResolver.putAliasAndPassword("merchant", "merchantpassword".toCharArray()); kskiResolver.putAliasAndPassword("bank", "bankpassword".toCharArray()); // Prepare the encrypted data, // put CipherValue in it. EncryptedData ed = new EncryptedData(); ed.setCipherData(prepareCipherData()); // Use RSA v1.5 to encrypt, an unusual choice. // Usually you'd use a 3DES key // to encrypt, and the RSA // key to encrypt the 3DES key. EncryptionMethod rsa_1_5 = prepareEncryptionMethod(EncryptionMethod.RSA_1_5); // Prepare the keys. KeyInfo merchant = prepareNameOnlyKeyInfo("merchant"); KeyInfo bank = prepareNameOnlyKeyInfo("bank"); // Encrypt! xmlPlainText.encrypt("//Item", kskiResolver, ed, EncryptedData.CONTENT, rsa_1_5, merchant); xmlPlainText.encrypt("//CreditCard", kskiResolver, ed, EncryptedData.ELEMENT, rsa_1_5, bank); // Dump it! PrintWriter pw = new PrintWriter( new FileWriter("encryptedPayment_rsa_1.xml")); pw.write(xmlPlainText.toString()); pw.close();
encryptedPayment_rsa_1.xml shows the result:
<?xml version="1.0" encoding="UTF-8" ?> <PurchaseOrderRequest> <Order> <Item> <EncryptedData xmlns="http://www.w3.org/2001/04/xmlenc#"> <CipherData> <CipherValue>BNjivf7gTOhHmcfZIX8XI6Vh06UFjwL9HaXMHD+CoAqa4A14UJsh+m9VFNEb5 OezHpAGP1TpZDEo3mHhvo4hcMd2C01z9HkmVLgEdGn77fC/aSmrIb+NjqkvMGxIg/AaUFrv79RsdQBKs cg7pXRRhdgB5h4JSxHJ7dlZudnZBrg=</CipherValue> </CipherData> </EncryptedData> </Item> <Quantity>1</Quantity> </Order> <Payment> <EncryptedData xmlns="http://www.w3.org/2001/04/xmlenc#"> <CipherData> <CipherValue>ci3q/7BqV92eizpxvIszQeo6GYB1NOfePoFiUvJtsYMkhrvR39cqUpdg07J3Q 3GzCymyrXwtZ9iaV4XzzCuvaTYiTecorvLypGcl1NOerWTV/RWmDU04hHHxE0WyvAVML9r/VhMTzHUVx SR2ZkaHe5rzD9NVR0YgApB1IPlOy1m6SxqLeAHC41/apIYaCIq3HUoFqCQuK/1x/OAoPr5jvLjq2ikUg tqtJsdzihPSYmNNzOOxcZRiD6/O5OeN1Pe5FqJ9Q6RR26vDJNwAXf+srf8hXfIvGrx3yTQ88Sp2//oih toH55NZnjns78i4bNGlGoyNK4GTG9DTGEL0+QRTxA==</CipherValue> </CipherData> </EncryptedData> <PurchaseAmount> <Amount>123623</Amount> <Currency>840</Currency> <Exponent>-2</Exponent> </PurchaseAmount> </Payment> </PurchaseOrderRequest>
Easy, isn't it? It gets easier in the next section!
A simpler way to write the code
If you examine
Test.java (found in this article's source code), you'll see that its code mostly comprises prep lines—code that prepares the objects you need to pass to
encrypt(). It worsens for more complex structures like
KeyInfo for RSA and 3DES (Triple Data Encryption Standard) keys. Fortunately, with XSS4J, you can use the actual XML structure itself instead of the corresponding class's instance. Look at the code snippet below. In this example, I want the RSA key to encrypt the 3DES key (which encrypts the
Item element's content), but I'm too lazy to write the code to create the cumbersome
KeyInfo structure required for this operation:
// Open a document we're going play with. XMLPlainText xmlPlainText = new XMLPlainText(new FileInputStream("payment.xml")); // Load the keystore. KeyStore ks = KeyStore.getInstance("jceks"); ks.load(new FileInputStream("jwstore"), "jwpassword".toCharArray()); // Tell the class how to get // the keys. KeyStoreKeyInfoResolver is just // one way of getting the keys. KeyStoreKeyInfoResolver kskiResolver = new KeyStoreKeyInfoResolver(ks); kskiResolver.setAlgorithmFactory(new AlgorithmFactoryExtn()); kskiResolver.putAliasAndPassword("merchant", "merchantpassword".toCharArray()); kskiResolver.putAliasAndPassword("bank", "bankpassword".toCharArray()); // Get EncryptedData. Element edEl = XMLUtil.getDocumentFromFile("encrypted_data.xml").getDocumentElement(); EncryptedData ed = new EncryptedData(edEl); // Encrypt! xmlPlainText.encrypt("//Item", kskiResolver, edEl, EncryptedData.ELEMENT, null, null); // Dump it! PrintWriter pw = new PrintWriter( new FileWriter("encryptedPayment_des_rsa.xml")); pw.write(xmlPlainText.toString()); pw.close();
With the above code, the
Item's content element is encrypted into Listing 5's
<?xml version="1.0" encoding="UTF-8" ?> <PurchaseOrderRequest> <Order> <Item> <enc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns:enc="http://www.w3.org/2001/04/xmlenc#"> <enc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" /> <dsig:KeyInfo xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"> <enc:EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#"> <enc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" /> <dsig:KeyInfo> <dsig:KeyName>merchant</dsig:KeyName> </dsig:KeyInfo> <enc:CipherData> <enc:CipherValue>SbI2DZ+WcYXbGEwcy43hpp9xtULN0TK3PF5+eFh7woEtXrSv2eMjwlMFQ 0x0uxsNHKhHNvlLIltbOUQnTZCItv1PCgxowd3v8Ttu7Wvnbg9wK8vfTJtmIttXunLQhgz/EAa4bALbQ sfs7u6yKzPFB4NQhGgy7ghsSgO1fQSbSBs=</enc:CipherValue> </enc:CipherData> </enc:EncryptedKey> </dsig:KeyInfo> <enc:CipherData> <enc:CipherValue>RfEyBjhg7KbRXWRbVPNGKuwUwLsIo+gRZHJ/kmSaZkWyjl3SjtCmYZ3PW bZGLWYHekjKW113Ryy7gRZhOe7RECv7kR+GYO969+DXz3eWbfKQ2p6yc+CR35q06+Qggy/9</enc: CipherValue> </enc:CipherData> </enc:EncryptedData> </Item> <Quantity>1</Quantity> </Order> <Payment> <CreditCard> <BrandId>Brand X</BrandId> <Number>1111222233334444</Number> <ExpiryDate>20030408</ExpiryDate> </CreditCard> <PurchaseAmount> <Amount>123623</Amount> <Currency>840</Currency> <Exponent>-2</Exponent> </PurchaseAmount> </Payment> </PurchaseOrderRequest>
I used Listing 6's template below to generate Listing 5's encrypted document. Notice how closely the template resembles Listing 5's
<?xml version="1.0" encoding="UTF-8" ?> <EncryptedData Type="...#Element" xmlns="..."> <EncryptionMethod Algorithm="...#tripledes-cbc" /> <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"> <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#"> <EncryptionMethod Algorithm="http://www.w3.org/2001/04/ xmlenc#rsa-1_5" /> <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#"> <KeyName>merchant</KeyName> </KeyInfo> <CipherData> <CipherValue /> </CipherData> </EncryptedKey> </KeyInfo> <CipherData> <CipherValue /> </CipherData> </EncryptedData>
Look at the
EncryptedKey element above. It closely resembles
EncryptedData, doesn't it? Indeed, both derive from an abstract XML Schema type called
EncryptedType. The main difference between the two (no prize for guessing this): one encrypts keys, the other data. In the example above, the
CipherValue's content inside
EncryptedKey results from encrypting the newly generated 3DES key with the merchant's RSA key.
Retrieve your plaintext
Decrypting your encrypted XML is easy. You tell the library which node it should decrypt and how it should obtain the key to encrypt. That information, of course, depends on whether you're just using an RSA key in the keystore or a Triple DES key included in the
EncryptedData. If your (partially) encrypted XML document resembles the one in Listing 2, which contains no information about the encryption algorithm or the key, naturally you must supply them, like this:
EncryptionMethod em = new EncryptionMethod(); em.setAlgorithm(EncryptionMethod.RSA_1_5); KeyInfo ki = new KeyInfo(); KeyName kn = new KeyName(); kn.setName("merchant"); ki.addKeyName(kn); // Decrypt the one encrypted using the merchant's key xmlCipherText.decrypt("//Item/child::*", kskiResolver, null, null, em.createElement(XMLUtil.createNewDocument(), true), ki.createElement(XMLUtil.createNewDocument(), true)); KeyInfo ki2 = new KeyInfo(); KeyName kn2 = new KeyName(); kn2.setName("bank"); ki2.addKeyName(kn2); // Decrypt the one encrypted using the bank's key xmlCipherText.decrypt("//Payment/child::*[position()=1]", kskiResolver, null, null, em.createElement(XMLUtil.createNewDocument(), true), ki2.createElement(XMLUtil.createNewDocument(), true));
On the other hand, if you're using Listing 6's template, which contains the
EncryptedKey, you must supply an
EncryptedKeyRetriever so the library knows how to retrieve your key. Yet you don't need to supply the key information and encryption method, because they're already inside
EncryptedData. The library needs an
EncryptedKeyRetriever able to extract that encrypted key. In this example, because the encrypted key is in the same document, we use
SameDocumentEncryptedKeyRetriever, which handles just that:
EncryptedKeyRetriever ekr = new SameDocumentEncryptedKeyRetriever( xmlCipherText.getCurrentNode().getOwnerDocument()); // Decrypt! xmlCipherText.decrypt("//Item/child::*", kskiResolver, ekr, id, null, null);
Things to look forward to
In this article you've learned why XML Encryption exists, the problems it addresses, and the existing protocols' shortcomings in a Web services environment. You've also seen what your XML documents look like after encryption, with all the variations in the structure, from the simplest structure to the complex ones with encrypted keys inside. You've also seen how you can achieve all this with XSS4J, IBM's XML Security Suite, by looking at the sample code sprinkled throughout the article.
But XML Encryption boasts other interesting features, especially when used with XML Digital Signature, another important standard upon which WS-Security rests. XML Encryption alone won't prove useful if it can't handle scenarios such as workflow, in which both encryption and signature can happen in any order. That's why the W3C's XML Encryption Working Group designed Decryption Transform, which I'll discuss, along with XML Digital Signature, in Part 2 of this series. Till then, have fun with XML Encryption! Try out the sample code; all the files are under an Eclipse project, so you can just open the project directly and try them out after you've set everything up. Enjoy!
Learn more about this topic
- Download this article's source code
- "Yes, You Can Secure Your Web Services Documents," Ray Djajadinata (JavaWorld)
- For the most accurate and up-to-date information on XML Encryption, see the W3C's XML Encryption page
- Download IBM's XSS4J suite
- IBM has submitted XML Encryption to the JCP as JSR 106
- As an XSS4J alternative, consider the C-based XML Security Library
- Also check out this excellent tutorial about XPath
- Ray Djajadinata also wrote "Sir, What Is Your Preference?" (JavaWorld, August 2001)
- For more security-related articles, visit the Security section of JavaWorld's Topical Index
- The Java and XML section of JavaWorld's Topical Index offers numerous articles on using XML with Java
- For more articles on Web services, browse the following JavaWorld resources:
- The Java and Web Services section of our Topical Index
- Frank Sommers's Web Services column
- The Java and Web Services section of our Topical Index
- Talk about XML security in our Java Security discussion
- Sign up for JavaWorld's free weekly Enterprise Java email newsletter
- You'll find a wealth of IT-related articles from our sister publications at IDG.net