Using XML Encryption With CipherReferences, Part 1 - Local Data

Most users of encrypted XML will encrypt their data and embed the resulting cipher value directly into the EncryptedData element, using a CipherValue tag. However, XML encryption also supports the use of CipherReferences, which allow you to place the encrypted XML outside of the EncryptedData element, or even outside of the current document. Getting this to work is a little tricky, so I'll provide an example here of referring to encrypted data that is stored elsewhere in the current document. Once again, I'll be using my order XML to work with.

Encrypting the data

The first step is to encrypt the data into an array of bytes. This is easily accomplished in the same way as I showed in my previous discussion of XML Encryption.

// load the data
XmlDocument doc = new XmlDocument();
doc.Load("order.xml");
XmlElement paymentElem = doc.SelectSingleNode("/order/payment") as XmlElement;

// encrypt
SymmetricAlgorithm key = new RijndaelManaged();
byte[] cryptData = new EncryptedXml().EncryptData(Encoding.UTF8.GetBytes(paymentElem.OuterXml), key);

Now that the data is encrypted, it must be inserted into the document. To do this, I will create a new XML node (called encrypted), with a specific ID (payment), that I will use in the cipher reference to poin the XML decryption engine to the correct location. In order to store the binary data in XML, I must first transform it into a base64 string.

// setup the element pointed to by the ciper reference
XmlElement cryptElem = doc.CreateElement("encrypted");
cryptElem.InnerText = Convert.ToBase64String(cryptData);
XmlAttribute idAttr = doc.CreateAttribute("id");
idAttr.Value = "payment";
cryptElem.Attributes.Append(idAttr);
doc.DocumentElement.AppendChild(cryptElem);

Creating the CipherReference

Unfortunately, there is no easy way to create a cipher reference programatically. In order to create the cipher reference, you must first create the XML that represents the reference, and then load that XML into a CipherReference object. There are a couple of things to look out for when creating this XML. You must include a transform chain that takes as input the node that the reference points to, and returns as output the byte array of encrypted data. In the above example, I transformed the byte array into a base 64 string, then placed it into the body of an XML element. In order to convert this back into a byte array, I must first use an XPath expression to get the value out of the XML element, and then use a base64 transform to convert the string into raw bytes. This transform chain is shown in the given XML. The other interesting part of the CipherReference XML is that the URI must be set to "#id", where id is the ID given to the XML element holding the encrypted data (in this case "payment").

// Create a CipherReference to refer to the data in the /order/encrypted element
CipherReference cr = new CipherReference();
XmlDocument reference = new XmlDocument();

// note: make sure the appropriate transforms are added to convert the encrypted data back to bytes
reference.LoadXml(
    @"<CipherReference URI='#payment'>
  <Transforms xmlns='https://www.w3.org/2001/04/xmlenc#'>
   <Transform xmlns='https://www.w3.org/2000/09/xmldsig#'
     Algorithm='https://www.w3.org/TR/1999/REC-xpath-19991116'>
    <XPath>self::text()</XPath>
   </Transform>
   <Transform xmlns='https://www.w3.org/2000/09/xmldsig#'
     Algorithm='https://www.w3.org/2000/09/xmldsig#base64'/>
  </Transforms>
    </CipherReference>");
cr.LoadXml(reference.DocumentElement);

Creating an EncryptedData object from the CipherReference

Next, an EncryptedData object must be created from the CipherReference. This step is very similar to the standard XML encryption. The only notable difference is that CipherData is created with a CipherReference instead of a CipherValue. Note that the encryption key and method must still be setup properly. Once I've constructed the EncryptedXml object, I replace the payment element with it.

// Create the EncryptedData object from the cipher reference
EncryptedData encrypted = new EncryptedData();
encrypted.CipherData = new CipherData(cr);
encrypted.KeyInfo.AddClause(new KeyInfoName("key"));
encrypted.EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncAES256Url);
EncryptedXml.ReplaceElement(paymentElem, encrypted, false);

This creates XML that looks like:

<order>
  <purchase>
    <item quantity="1">Def Leppard: Pyromania</item>
    <item quantity="1">Ozzy Osbourne: Goodbye to Romance</item>
  </purchase>
  <shipping>
    <to>Shawn Farkas</to>
    <street>5 21st Street</street>
    <city>Seattle</city>
    <state>WA</state>
    <zip>98000</zip>
  </shipping>
  <EncryptedData xmlns="https://www.w3.org/2001/04/xmlenc#">
    <EncryptionMethod Algorithm="https://www.w3.org/2001/04/xmlenc#aes256-cbc" />
    <KeyInfo xmlns="https://www.w3.org/2000/09/xmldsig#">
      <KeyName>key</KeyName>
    </KeyInfo>
    <CipherData>
      <CipherReference URI="#payment">
        <Transforms>
          <Transform
              Algorithm="https://www.w3.org/TR/1999/REC-xpath-19991116"
              xmlns="https://www.w3.org/2000/09/xmldsig#">
            <XPath>self::text()</XPath>
          </Transform>
          <Transform
              Algorithm="https://www.w3.org/2000/09/xmldsig#base64"
              xmlns="https://www.w3.org/2000/09/xmldsig#" />
        </Transforms>
      </CipherReference>
    </CipherData>
  </EncryptedData>
  <encrypted id="payment">65ZxnnE3  ...  wRtm+A==</encrypted>
</order>

Decrypting

Decrypting a document with a cipher reference works exactly the same as decrypting the document with the cipher value embedded in the encrypted data. All that has to be done is create an EncryptedXml object for the document, setup the key name, and call DecryptDocument. I also remove the cipher data from the resulting document, since DecryptDocument will only remove the EncryptedData.

EncryptedXml decryptXml = new EncryptedXml(doc);
decryptXml.AddKeyNameMapping("key", key);
decryptXml.DecryptDocument();
doc.DocumentElement.RemoveChild(doc.SelectSingleNode("/order/encrypted[@id='payment']"));

After running the decryption code over the encrypted XML, the resulting XML is exactly the same as the original, unencrypted order.xml. In my next post, I'll show how to use CipherReferences to refer to encrypted data not even in the same document as the EncryptedData tag.