This topic is probably not relevant for many people, but I figured it warranted a post anyways since it's taken so much of my time. The basic problem: Encrypting and decrypting XML using the OpenSAML APIs for XML Encryption. OpenSAML is a SAML api supporting SAML 2.0, where some elements can be encrypted. One example is the Assertion element in a Response. Instead of transmitting a regular Assertion element, it is possible to transmit an EncryptedAssertion element instead. This element then contains the encrypted assertion.
The API contains two classes of particular interest in this case: org.opensaml.saml2.encryption.Encrypter and org.opensaml.saml2.encryption.Decrypter for encrypting and decrypting. Before using them, it's probably necessary to explain how XML encryption works with asymmetric keys (public key encryption).
The usual way of doing public key encryption is that the sender encrypts data with the receiver's public key. The receiver can then decrypt it with the private key, thus ensuring privacy. However, encrypting an entire data stream using asymmetric keys is very expensive, so instead a symmetric (shared) key is generated by the sender. This key is then encrypted with the receiver's public key, and the data stream is then encrypted with the symmetric key.
The asymmetric keys are usually created with the RSA algorithm while a popular choice for symmetric keys is 128 bit AES. In an encrypted XML structure, the data looks like this:
-
<xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Element">
-
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
-
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
-
<xenc:EncryptedKey>
-
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
-
<xenc:CipherData>
-
<xenc:CipherValue>
-
W6U2DRN11Y/dbIMCEP....
-
</xenc:CipherValue>
-
</xenc:CipherData>
-
</xenc:EncryptedKey>
-
</ds:KeyInfo>
-
<xenc:CipherData>
-
<xenc:CipherValue>
-
uK3fL7fFC/Y6GbXLwmFmLZcla8...
-
</xenc:CipherValue>
-
</xenc:CipherData>
-
</xenc:EncryptedData>
In other words, the EncryptedData element (which is a standard XML encryption element) contains a KeyInfo which holds the encrypted symmetric key and a CipherData element which contains data encrypted with the symmetric key. The first element tells us that the symmetric data is encrypted with 128 bit AES, and EncryptedData/KeyInfo/EncryptedKey/EncryptionMethod says that the key itself is encrypted with RSA.
All this means that when encrypting an XML element, two keys must be used: A randomly generated, and the receiver's public key. In OpenSAML, it looks like this:
-
package test;
-
-
-
import org.opensaml.saml2.core.EncryptedAssertion;
-
import org.opensaml.saml2.core.Response;
-
import org.opensaml.saml2.encryption.Encrypter;
-
import org.opensaml.saml2.encryption.Encrypter.KeyPlacement;
-
import org.opensaml.xml.encryption.EncryptionConstants;
-
import org.opensaml.xml.encryption.EncryptionParameters;
-
import org.opensaml.xml.encryption.KeyEncryptionParameters;
-
import org.opensaml.xml.security.SecurityHelper;
-
import org.opensaml.xml.security.credential.Credential;
-
-
-
public class EncryptTest throws Exception {
-
public void encrypt(Response response, Credential receiverCredential) {
-
Credential symmetricCredential = SecurityHelper.getSimpleCredential(
-
SecurityHelper.generateSymmetricKey(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128));
-
-
EncryptionParameters encParams = new EncryptionParameters();
-
encParams.setAlgorithm(EncryptionConstants.ALGO_ID_BLOCKCIPHER_AES128);
-
encParams.setEncryptionCredential(symmetricCredential);
-
-
KeyEncryptionParameters kek = new KeyEncryptionParameters();
-
kek.setAlgorithm(EncryptionConstants.ALGO_ID_KEYTRANSPORT_RSA15);
-
kek.setEncryptionCredential(receiverCredential);
-
-
Encrypter encrypter = new Encrypter(encParams, kek);
-
encrypter.setKeyPlacement(KeyPlacement.INLINE);
-
-
EncryptedAssertion encrypted = encrypter.encrypt(response.getAssertions().get(0));
-
response.getEncryptedAssertions().add(encrypted);
-
-
response.getAssertions().clear();
-
}
-
}
This code replaces an unencrypted Assertion with an encrypted. The Encrypter is instantiated with two arguments: The EncryptionParameters and the KeyEncryptionParameters. The EncryptionParameters contain a reference to the shared key and the algorithm to use, and the KeyEncryptionParameters contain the receiver's public key.
Decrypting an encrypted element is then a question of first decrypting the key and then decrypting the data. In code it can look something like this:
-
public class DecryptTest throws Exception {
-
public Assertion decrypt(EncryptedAssertion enc, Credential credential) {
-
KeyInfoCredentialResolver keyResolver = new StaticKeyInfoCredentialResolver(credential);
-
EncryptedKey key = enc.getEncryptedData().getKeyInfo().getEncryptedKeys().get(0);
-
-
Decrypter decrypter = new Decrypter(null, keyResolver, null);
-
SecretKey dkey = (SecretKey) decrypter.decryptKey(
-
key, enc.getEncryptedData().getEncryptionMethod().getAlgorithm());
-
-
Credential shared = SecurityHelper.getSimpleCredential(dkey);
-
decrypter = new Decrypter(new StaticKeyInfoCredentialResolver(scred), null, null);
-
return decrypter.decrypt(enc);
-
}
-
}
The credentials in the arguments list must contain the receiver's private key, and is used to decrypt the shard key. When the shared key has been decrypted, the rest of the message can be decrypted.
All of this looks pretty simple, but the APIs are not very forgiving. If you try to encrypt or decrypt with the wrong algorithm or with the wrong keys (or key types), strange things will happen. Most likely, you'll get a stacktrace teling you that the padding is wrong, which is not exactly helpful. However, the examples above show one way of doing encryption and decryption which works, and I hope it can help a little if you're also unable to escape from XML/web services.



