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: