Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
CarlosRoggan
Product and Topic Expert
Product and Topic Expert

SAP Cloud Integration doesn’t offer an "encryptor" step for encrypting XML content according to the "XML Encryption" standard.
This standard provides some benefits and flexibility for xml content.
The present blog post shows how it can be achieved in a Groovy script.          
This blog post covers
🔸 SAP Cloud Integration 
🔸 Groovy
🔸 org.apache.santuario

Content

Prerequisites
Intro
1. Create Key Pair
2. Create iFlow
2.1.  Create iFlow
2.2. Upload library
2.3. Groovy Script for Encryption
2.4. Groovy Script for Decryption
3. Run
Appendix 1: Sample XML Payload
Appendix 2: Sample Groovy Script for Encryption
Appendix 3: Sample Groovy Script for Decryption
Appendix 4: Java Client Code
Appendix 5: Maven pom file
Appendix 6: Algorithms
Appendix 7: OpenSSL

Prerequisites

🔹 OpenSSL
This tutorial uses OpenSSL for creating a key pair and certificate.
See Appendix for installation help.
Usage is not mandatory, Keys can be generated in any other way.
🔹CPI
To follow this tutorial, access to a Cloud Integration tenant is required, as well as basic knowledge about creating iFlows.
🔹Maven
While not required, it is an advantage to have maven installed.
🔹Understanding the "XML Encryption" standard is not difficult when using the previous blog post
🔹For remaining open questions I recommend the Security Glossary.

Introduction

After going through the previous blog post, we’re well prepared for the hands-on tutorial in this post.

Scenario
In our Cloud Integration scenario, a sales order XML payload must be encrypted because it contains sensitive information (CreditCard number).
To make things easier, we hard-code the sample xml payload in the iFlow.
In a Groovy script, we encrypt only the secret credit card number.
This is possible thanks to the “XML Encryption” standard.
To prove that the new XML payload, which contains encrypted parts, can be decrypted, we add another Groovy script.
This one performs the decryption.

Recap
The “XML Encryption” standard allows to encrypt:
🔹The whole message
🔹The sub-content of a specific node of the XML message
🔹A specific node, means the node itself plus its content

The standard defines an additional XML tree that is inserted in the original document, replacing the sensitive content.
This new XML tree starts with the top-level node <EncryptedData>.

The sample payload
We’re keeping things as simple as possible, so we’re using this simple sample payload:

xml0.jpg

 We want to encrypt the number of the credit card.
The <CreditCard> node itself should not be encrypted.
Note:
It might be more safe to hide even the node name. but I believe it makes the tutorial a little bit more comprehensive, to see this node in the result.
Below screenshot shows the payload before and after the encryption:

xml9.jpg

The encryption process
In the Groovy script, we’re using the Apache Santuario library, which provides an implementation of the “XML Encryption” standard.
This means it is aware of the XML structure that is defined in the standard.
It is also aware of the algorithms that are supported in the specification.

So what we have to  do in the code:
🔸Compose the additional xml tree, using the objects which are offered in the library.
🔸Provide the required  symmetric and asymmetric keys.
🔸Choose the XML node that should be encrypted.
🔸Feed the encryption with that node.

Note:
The spec clearly recommends the below algorithms, so we use them in our sample code.
However, some older legacy algorithms are supported, too, in case they are required.

Recommended Algorithms:
🔹AES symmetric cipher with GCM operation mode.
🔹RSA asymmetric cipher with OAEP.

Note:
In this tutorial, we manually generate RSA key pair with big key size and encrypted private key.
Both settings add more safety and doesn't cause complexity in the Groovy script.

Disclaimer:
This blog post is not an official recommendation, this is not safe and not ready to be pasted into productive environment.
This is just a simplified tutorial to get everybody started.

1. Create Key Pair

There are multiple possibilities for creating a key pair. 
A simple way would be to use CPI:
go to CPI Keystore, press “Create -> Key Pair” and enter some data
That's enough for following this tutorial.

However, let's go through the complicated steps, using OpenSSL 1.1

1.1. Create encrypted private key

Usually, in our tutorials we prefer short commands, for easier understanding.
This would be the simple command to generate a private key with RSA algorithm:
openssl genpkey -algorithm RSA

However, today we create a private key with encryption, for the sake of security:

 

openssl genpkey -algorithm RSA  -pkeyopt rsa_keygen_bits:4096 -out encryPrivKey.pem -aes-256-cbc -pass pass:abcd

 

Note:
Above command uses two safe and one unsafe options:
👍 We create an RSA key with bigger size (4096)
👍The key is encrypted with AES (bigger size 256) and a password.
👎For easier description, we pass the password in plaintext, which is a unsafe.

Note:
The password can be passed interactively, or it can be read from a file(see here).

The command generates the file encryPrivKey.pem.
We can open it with notepad, so we can understand from the header that it is an encrypted private key.
The content is somewhat readable, because it is encoded with base 64.
We can also paste the content into a base64-decoder to see the unreadable content.        

1.2. Create certificate

The second step is to create a certificate with the existing encrypted private key.
In productive environments, a certificate has to be requested from a certification authority (CA), but for us it is enough to use a self-signed certificate which we can generate ourselves.
Actually, for the tutorial we don’t need the certificate, it is required just for uploading to CPI.

 

openssl req -x509 -key encryPrivKey.pem -new -out certSafeSan.pem -subj "/CN=certforsantusafe"

 

Note:
The password is prompted interactively.
Remember our password: abcd

1.3. Create .p12 file

For transporting certificates, there are container formats like p12 which is also called pfx.
It is specified in PKCS #12.
Below command creates a .p12 file which contains the certificate with its private key, recognizable with an alias name.

 

openssl pkcs12 -export -out santusafestore.p12 -inkey encryPrivKey.pem -in certSafeSan.pem -passout pass:abcd -name santusafe

 

Note:
The password abcd is prompted interactively.

1.4. Upload to CPI

In our CPI tenant, we go to the “Manage Keystore” screen (URL path is  /shell/monitoring/Keystore).
We press “Add -> Keystore” and browse for the newly created .p12 file.
We enter our password, in my example it is abcd.
As a result, we see a new entry in the CPI Keystore with alias "santusafe" and type “Key Pair".
The alias name will be used below in the Groovy scripts, in order to load the keys.

2. Create iFlow

In this section, we’re creating a simple iFlow that does nothing but encrypt and decrypt a hard-coded xml-message.

2.1. The iFlow

Our iFlow will look as follows:

iFlow.png

 Let’s quickly go through the configuration.

🔷Timer
We define a start event via "Timer" with default properties, i.e. run once
🔷  Content Modifier
We use a "Content Modifier" in order to hard-code a dummy xml-payload in the "Message Body".
It represents an incoming HTTP request (or similar). 
The dummy xml-payload can be anything of your choice, but it must contain the <CreditCard> node, because it is referred from the Groovy script.
The content is copied from Appendix 1
🔷 Groovy Script1
The script used for encryption.
The content is copied from Appendix 2
🔷 Groovy Script2
The script used for encryption.
The content is copied from Appendix 3

2.2. The security library

In our Groovy code, we’re using the library Apache Santuario.
It is an implementation of the XML Encryption standard, thus perfect for our needs.
The drawback: the library is not available in the CPI Java runtime.
We need to manually get hold of the .jar file and upload it to the iFlow.

2.2.1. Download the "xmlsec" jar file

A common place to find and download jar files is the "mavenrepository".  
We find our library at https://mvnrepository.com/artifact/org.apache.santuario/xmlsec
We choose a version (in my example: 4.0.2)
We click on "bundle" to download the jar file.
in my example the file name is xmlsec-4.0.2.jar.

Alternatively: use maven as described in Appendix 5.

2.2.2. Upload

To make the jar file available for our scripts in the iFlow, we open our iFlow.
To make sure that nothing is selected, we click on the background of the designer.
At the “Integration Flow” properties, we open the tab “References”, then click on “Add -> Archive”.
We browse to our jar file and confirm the dialog.
That’s it.

 

 2.3. The Groovy Script for Encryption

Now let’s go through the code for encrypting xml payload according to the "XML Encryption" standard.

Preparation

A brief look at the required packages:

 

import com.sap.it.api.ITApiFactory
import javax.xml.*
import org.w3c.*

 

We need some basic functionality for dealing with xml (not surprising in case of XML payload).
We cannot use native groovy xml-parsing, we need org.w3c because the apache library is based on it.

For the security related operations, we use some native Java packages:

 

import java.security.*
import javax.crypto.*

 

And finally, the protagonist, the XML Security implementation of Apache Santuario:

 

import org.apache.xml.security.*

 

Note:
I think it is definitely important to rely on a public implementation of the security standard and not to implement the single steps manually.
Reasons are the compatibility, flexibility of the standard, secure implementation, vulnerability-fixes that come with the library.

First thing we have to do is to initialize the apache library:

 

org.apache.xml.security.Init.init();

 

Parse xml string

Next first thing we have to do is to read the xml message and to parse it into a org.w3c.Document instance, with the help of our helper method:

 

Document document = convertToDocument(message.getBody(InputStream.class))

 

We need this document instance later.

The next step is more concrete: we need the concrete element node that should be encrypted.
In our example, it is the <CreditCard> node which contains text content.

 

Element elementToEncrypt = (Element)document.getElementsByTagName("CreditCard").item(0)

 

Note:
Same can be achieved more elegant with xPath.

Note:
As usual, we're skipping all error handling, to make the code sample easier to read.

The DEK

We need a key for encrypting the data (= content).
The DEK ("Data Encryption Key", also known as "Content Encryption Key") is generated on the fly, which makes things more secure. 

 

def SecretKey generateDEK(algo, keySize) throws Exception {
    KeyGenerator keyGenerator = KeyGenerator.getInstance(algo)
    keyGenerator.init(keySize)
    return keyGenerator.generateKey()
}

 

Note:
The "XML Encryption" standard is flexible and allows to point to an existing DEK. 

Note:
The javax.crypto.KeyGenerator supports a variety of algorithms that not necessarily are in sync with the algorithms supported by the apache library.
Basically, we have to stick to what the "XML Encryption" standard recommends
See Appendix for listings.

The KEK

Now we need the KEK ("Key Encryption Key") which is used to encrypt the DEK.
Remember what we did with openssl?
Yes, we created an asymmetric key pair and uploaded it to CPI.
That was the KEK.
Now we only need to fetch it with the CPI runtime API:

 

def PublicKey getPublicKeyFromKeystore(alias){
    KeystoreService keystoreService = ITApiFactory.getService(KeystoreService.class, null) 
    KeyPair keyPair  = keystoreService.getKeyPair(alias) 
    return keyPair.getPublic()    
}

 

Note:
For encryption, the public key is used.
For decryption, the private key is required.
This ensures that anyone can encrypt, because the public key is public.
However only the holder of the private key can read it.

Encrypt the DEK                                             

Now we can use the KEK in order to encrypt the DEK.
With other words:
We use the public key to encrypt the symmetric key.

 

XMLCipher keyEncCipher = XMLCipher.getInstance(XMLCipher.RSA_OAEP)
keyEncCipher.init(XMLCipher.WRAP_MODE, pubKek)
EncryptedKey encryptedKey = keyEncCipher.encryptKey(document, symmetricKey)

 

Note:
The Cipher is configure with XMLCipher.RSA_OAEP.
This is a constant in org.apache.xml.security.encryption.XMLCipher.
It stands for
http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p 
Which is defined in this section of the spec: https://www.w3.org/TR/xmlenc-core1/#sec-RSA-OAEP.
This is noteworthy, because the constants are different than those provided in javax.crypto.Cipher.
It makes sense, because we have to stick rather to the XML-Enc-standard than to the Java-default.

We have to configure the XMLCipher instance with an algorithm that matches our key pair:
RSA_OAEP is the recommendation.

The XMLCipher instance is initialized with the public key. 
Also, there’s the "wrap"-mode which is used for encrypting the key (which will be "wrapped" in the xml).
The symmetric key is passed to the encryptKey method.

Encrypt the content

We've generated the DEK above, now we can use it to encrypt the content (=data).
We need to create and configure another instance of XMLCipher.
This one is used to encrypt the content.
Remember: we’re encrypting 2 things: the content and the key.
The content encryption is done with symmetric key, as such we configure our XMLCipher with "AES".

 

XMLCipher contentEncCipher = XMLCipher.getInstance(XMLCipher.AES_256_GCM)
contentEncCipher.init(XMLCipher.ENCRYPT_MODE, symmetricKey)

 

The constant XMLCipher.AES_256_GCM stands for
http://www.w3.org/2009/xmlenc11#aes256-gcm
In the spec it is described at chapter 5.2.4:
the symmetric algorithm "AES" is used with 256 bit key size and with "GCM" operation mode (see intro).
As you see, for this tutorial I’ve chosen the biggest key size and the recommended algo + operation mode.

The <KeyInfo> node

As we’ve learned above, the "XML Encryption" standard describes how to transmit metadata in an agreed manner.
As we know, we have to send the encrypted DEK to the receiver.
The receiver needs it for decryption.
The standard defines an XML node <KeyInfo> which carries all key-related info.

 

KeyInfo keyInfo = new KeyInfo(document)
keyInfo.add(encryptedKey)

 

The <KeyInfo> is a subnode of the main node <EncryptedData> which is obtained from the content cipher instance.
After composing the KeyInfo instance, we configure it into the EncryptedData instance:

 

EncryptedData encryptedData = contentEncCipher.getEncryptedData()
encryptedData.setKeyInfo(keyInfo)

 

Encrypt the content

The only thing that we haven’t done yet, is what we’re talking about all the time:
encrypt the credit card number.
With other words: Encrypt the content.
Or: use symmetric key (DEK) to encrypt some XML node.
So let's do it.
We use the contentEncCipher instance which we created above.
Remember?
Let's recall the 2 lines:

 

XMLCipher contentEncCipher = XMLCipher.getInstance(XMLCipher.AES_256_GCM)
contentEncCipher.init(XMLCipher.ENCRYPT_MODE, symmetricKey)

 

We can see, the cipher was initialized with the symmetric Key.
Now we can invoke the doFinal method, which does the encryption:

 

Document encryptedDoc = contentEncCipher.doFinal(document, elementToEncrypt, true)

 

The method returns the encrypted payload as an instance of org.w3c.Document.
The method takes the XML node that should be encrypted.
In our example, it is the <CreditCard> node.
The last parameter is important, because it decides about the variant.
Remember?
We can encrypt the content of a node, or the node itself.
So if we set the past param to true then this means that only the content is encrypted.
In our example, only the number of the credit card is encrypted.
The <CreditCard>  node will still be visible in the result after encryption.

Output

That’s all for the encryption.
At the end, we only need to convert the org.w3c.Document instance to a string and set it as message body of the iFlow.

Summary

🔹Prepare document
🔹Generate DEK
🔹Fetch KEK
🔹Compose <EncryptedData> and <KeyInfo>  nodes
🔹Encrypt the DEK
🔹Encrypt the content

2.4. The Groovy Script for Decryption

The decryption script is much shorter because the library takes care of finding the required elements within  the XML.
We only need to take care of properly initializing the lib.

Prepare XML Document

We  need to convert the XML message payload from string to org.w3c.Document:

 

Document document = convertToDocument(message.getBody(InputStream.class))

 

Also, we need to get a hold of the <EncryptedData> node, because this is what has to be decrypted:

 

Element encryptedDataElement = (Element) document.getElementsByTagNameNS(EncryptionConstants.EncryptionSpecNS, EncryptionConstants._TAG_ENCRYPTEDDATA).item(0)

 

Fetch Private Key

Remember?
The encryption is done with the public key.
The secret content can only be decrypted with the private key.
So we fetch the private key from the CPI keystore:

 

def PrivateKey getPrivateKeyFromKeystore(alias){
    KeystoreService keystoreService = ITApiFactory.getService(KeystoreService.class, null) 
    KeyPair keyPair  = keystoreService.getKeyPair(alias) 
    return keyPair.getPrivate()    
}

 

Configure and execute decryption

We shouldn’t forget to initialize the library:

 

org.apache.xml.security.Init.init()

 

The XMLCipher instance that we need, is initialized for decryption mode.
And the private key, which we fetched above, is passed to the XMLCipher, to do the work as KEK:

 

XMLCipher xmlCipher = XMLCipher.getInstance()
xmlCipher.init(XMLCipher.DECRYPT_MODE, null)
xmlCipher.setKEK(privKek)

 

Finally, the doFinal method is executed with the <EncryptedData> node.
The entire org.w3c.Document has always to be passed, as context object.

 

Document decryptedDoc = xmlCipher.doFinal(document, encryptedDataElement)

 

That’s it for the decryption.
The library will automatically read the metadata which we stored in the <EncryptedData> node.
It will use the KEK to automatically unwrap the DEK.
Then use the DEK to decrypt the content, using the algorithm which is stored at the specific path in the <EncryptedData> node.
This is the advantage of a standard…..

Note:
If you get below error, it might be due to wrong key alias
org.apache.xml.security.encryption.XMLEncryptionException: encryption.nokey

Little summary

🔹Find the <EncryptedData> node in the XML payload
🔹Fetch the private key
🔹Configure an XMLCipher with key and node.

Final Notes

In this tutorial, we’ve used the algorithms recommended by the spec:
AES as symmetric key, with big key size and GCM operation mode.
RSA key pair and OAEP, as key transport algorithm.
We’ve chosen to encrypt the private key and use big size, as of our personal choice.

3. Run Scenario

At this point we’ve created a very simple iFlow with hard-coded xml-payload, encryption script and decryption script.
The scripts produce log output, such that we can view the message content before and after each step.

Finally, we can deploy the iFlow, I will be triggered automatically and we can view the results in the log at
Monitor -> Integrations -> Monitor Message Processing -> All Artifacts

Summary

We’ve learned about the “XML Encryption” standard:

🔹It describes 3 variants to encrypt an xml node or its content or the whole message.
🔹 It defines an XML structure where the encrypted content and all required metadata can be found
🔹 It is flexible, but one common way is to use symmetric key to encrypt the content, and in turn, use asymmetric key to encrypt the symmetric key.
🔹 In our tutorial, we’ve generated a strong RSA key pair and used in the iFlow to encrypt and decrypt.
🔹 In our sample groovy code, we’ve learned how to use the apache santuario library for encrypt / decrypt according to the XML Encryption standard.
🔹 We’ve avoided the weak algorithms (DES, TripleDES, RSA 1.5) and used the recommended ones:
     AES GCM (any size)
     RSA OAEP

Links

Specification
W3C recommendation XML Encryption Syntax and Processing V 1.1                                                                  

Java library
Apache Santuario

OpenSSL
Generate Key command
Convert to PKCS8 format
How to secretly passing a password to commands 

Blogs
Intro Blog : Understanding the "XML Encryption" standard.
Understanding the CMS (PKCS7) Standard.
The Security Glossary.

Appendix 1: Sample XML Payload

 

<SalesService>
   <Order>
	<OrderID>11</OrderID>
	<CustomerName>
         The Big Lebowski
      </CustomerName>
	<CreditCard>
         1111.2222.3333.4444
      </CreditCard>
	<ProductID>33</ProductID>
      <moreData>
         Much more data required here
      </moreData>
   </Order>
   <metadata>
      Some more information about the service
   </metadata>
</SalesService>

 

Appendix 2: Sample Groovy Script for Encryption

 

import com.sap.gateway.ip.core.customdev.util.Message
import com.sap.it.api.ITApiFactory
import com.sap.it.api.keystore.KeystoreService

import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.security.KeyPair
import java.security.Key
import java.security.PrivateKey
import java.security.PublicKey
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.Transformer
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult

import org.apache.xml.security.encryption.EncryptedData
import org.apache.xml.security.encryption.EncryptedKey
import org.apache.xml.security.encryption.XMLCipher
import org.apache.xml.security.keys.KeyInfo
import org.apache.xml.security.utils.EncryptionConstants
import org.apache.xml.security.utils.XMLUtils

import org.w3c.dom.Document
import org.w3c.dom.Element
import org.xml.sax.InputSource


def Message processData(Message message) {
    def encryptedDoc = doEncryption(message)
    message.setBody(convertDocumentToString(encryptedDoc))
    return message;
}

def String convertDocumentToString(Document document){
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    XMLUtils.outputDOM(document, outputStream);
    return outputStream.toString()
}

def Document doEncryption(message) throws Exception {
    org.apache.xml.security.Init.init()

    Document document = convertToDocument(message.getBody(InputStream.class))
    writeToLog (document, message, "Before encryption:\n")
    Element elementToEncrypt = (Element)document.getElementsByTagName("CreditCard").item(0)

    Key symmetricKey = generateDEK("AES", 256); // algo, keysize
    PublicKey pubKek = getPublicKeyFromKeystore("santu")   

    // encrypt the symmetric key
	XMLCipher keyEncCipher = XMLCipher.getInstance(XMLCipher.RSA_OAEP)
	keyEncCipher.init(XMLCipher.WRAP_MODE, pubKek);       
    EncryptedKey encryptedKey = keyEncCipher.encryptKey(document, symmetricKey)

    //encrypt the contents of the <CreditCard> node
    XMLCipher contentEncCipher = XMLCipher.getInstance(XMLCipher.AES_256_GCM)
    contentEncCipher.init(XMLCipher.ENCRYPT_MODE, symmetricKey)

    //compose the element <EncryptedData> 
    EncryptedData encryptedData = contentEncCipher.getEncryptedData()
    KeyInfo keyInfo = new KeyInfo(document)
    keyInfo.add(encryptedKey)
    encryptedData.setKeyInfo(keyInfo)
    
    Document encryptedDoc = contentEncCipher.doFinal(document, elementToEncrypt, true)

    writeToLog (encryptedDoc, message, "After encryption:\n")
    return encryptedDoc
}

def SecretKey generateDEK(algo, keySize) throws Exception {
    KeyGenerator keyGenerator = KeyGenerator.getInstance(algo)
    keyGenerator.init(keySize)
    return keyGenerator.generateKey()
}

def PublicKey getPublicKeyFromKeystore(alias){
    KeystoreService keystoreService = ITApiFactory.getService(KeystoreService.class, null) 
    KeyPair keyPair  = keystoreService.getKeyPair(alias) 
    return keyPair.getPublic()    
}

def Document convertToDocument(InputStream stream){
   DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance()
   factory.setNamespaceAware(true)
   DocumentBuilder builder = factory.newDocumentBuilder()
   return builder.parse(new InputSource(stream))
}

def writeToLog(doc, message, text){
    StringWriter stringWriter = new StringWriter()           
    Transformer transformer = TransformerFactory.newInstance().newTransformer()
    transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
    transformer.setOutputProperty(OutputKeys.METHOD, "xml")
    transformer.setOutputProperty(OutputKeys.INDENT, "yes")
    transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8")
    transformer.transform(new DOMSource(doc), new StreamResult(stringWriter))

    def messageLog = messageLogFactory.getMessageLog(message)
    messageLog.addAttachmentAsString("Encryption", text + stringWriter.toString(), "text/plain")

    return stringWriter.toString()
}

 

 

Appendix 3: Sample Groovy Script for Decryption

 

 

import com.sap.gateway.ip.core.customdev.util.Message
import com.sap.it.api.ITApiFactory
import com.sap.it.api.keystore.KeystoreService

import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.security.KeyPair
import java.security.Key
import java.security.PrivateKey
import java.security.PublicKey
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.Transformer
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult

import org.apache.xml.security.encryption.XMLCipher;
import org.apache.xml.security.utils.EncryptionConstants;
import org.apache.xml.security.utils.XMLUtils

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;


def Message processData(Message message) {
    def decryptedDoc = doDecryption(message)
    message.setBody(convertDocumentToString(decryptedDoc))
    return message
}

def Document doDecryption(message) throws Exception {
    org.apache.xml.security.Init.init()

    Document document = convertToDocument(message.getBody(InputStream.class))
    Element encryptedDataElement = (Element) document.getElementsByTagNameNS(EncryptionConstants.EncryptionSpecNS, EncryptionConstants._TAG_ENCRYPTEDDATA).item(0)
    writeToLog (document, message, "Before decryption: \n")

    PrivateKey privKek = getPrivateKeyFromKeystore("santu")   

    // configure and execute decryption
    XMLCipher xmlCipher = XMLCipher.getInstance();
    xmlCipher.init(XMLCipher.DECRYPT_MODE, null);
    xmlCipher.setKEK(privKek);//key to be used for decrypting the symmetric key (DEK)
    Document decryptedDoc = xmlCipher.doFinal(document, encryptedDataElement)

    writeToLog (decryptedDoc, message, "After decryption: \n")
    return decryptedDoc
}

def PrivateKey getPrivateKeyFromKeystore(alias){
    KeystoreService keystoreService = ITApiFactory.getService(KeystoreService.class, null) 
    KeyPair keyPair  = keystoreService.getKeyPair(alias) 
    return keyPair.getPrivate()    
}

def Document convertToDocument(InputStream stream){
   DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance()
   factory.setNamespaceAware(true)
   DocumentBuilder builder = factory.newDocumentBuilder()
   return builder.parse(new InputSource(stream))
}

def String convertDocumentToString(Document document){
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream()
    XMLUtils.outputDOM(document, outputStream)
    return outputStream.toString()
}

def writeToLog(doc, message, text){
    StringWriter stringWriter = new StringWriter()           
    Transformer transformer = TransformerFactory.newInstance().newTransformer()
    transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
    transformer.setOutputProperty(OutputKeys.METHOD, "xml")
    transformer.setOutputProperty(OutputKeys.INDENT, "yes")
    transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8")
    transformer.transform(new DOMSource(doc), new StreamResult(stringWriter))

    def messageLog = messageLogFactory.getMessageLog(message)
    messageLog.addAttachmentAsString("Decryption", text + stringWriter.toString(), "text/plain")

    return stringWriter.toString()
}

 

 

Appendix 4: Java Client Code

If you’re using Java code to import a private key from file, you need to make sure:

  • The file must be DER encoded (not pem)
  • The file must be in PKCS8 format

 

Create key pair
openssl genpkey -algorithm RSA -out privKey.pem
Convert to PKCS8 format with OpenSSL
openssl pkcs8 -topk8 -outform DER -nocrypt -in privKey.pem -out privKey8.der
Extract public key
openssl pkey -pubout -in privKey.pem -out pubKey.der -outform DER

Note:
Most openssl commands are based on the format PEM which is the default, so the optional parameter can be omitted.

Note:
The genpkey command is based on PKCS8 as per default, so you might wonder why we have to use the pkcs8 command.
Reason: maybe it is a bug in openssl, if we choose output format as DER, then result is not PKCS8-compliant.
Thus we have to use the conversion command anyways.

We need DER encoding, because it is required to import the key in Java.

Note:
Should add bigger size for generating private key
openssl genpkey -out privkey.pem -algorithm RSA -pkeyopt rsa_keygen_bits:4096

Using encrypted private key:
openssl genpkey -algorithm RSA -out encryPrivKey.pem -aes-128-cbc -pass pass:abcd
openssl pkcs8 -topk8 -outform DER -in encryPrivKey.pem -out encryPrivKey8.der -passin pass:abcd
openssl pkey -pubout -in encryPrivKey.pem -out encryPubKey.der -outform DER

Java code to import encrypted private key:

 

private static PrivateKey loadPrivateKeyEncrypted(String filePath, char[] password) throws Exception {
    byte[] keyBytes = Files.readAllBytes(Paths.get(filePath));
    PBEKeySpec pbeSpec = new PBEKeySpec(password);
    EncryptedPrivateKeyInfo keyInfo = new EncryptedPrivateKeyInfo(keyBytes);
    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(keyInfo.getAlgName());
    Key secret = secretKeyFactory.generateSecret(pbeSpec);
    PKCS8EncodedKeySpec keySpec = keyInfo.getKeySpec(secret);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");

    return keyFactory.generatePrivate(keySpec);
}

 

Note:
If the password is wrong, you might get this error:
javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
at java.base/com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:859)
at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:939)
at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:735)
at java.base/com.sun.crypto.provider.PBES1Core.doFinal(PBES1Core.java:432)
at java.base/com.sun.crypto.provider.PBEWithMD5AndDESCipher.engineDoFinal(PBEWithMD5AndDESCipher.java:315)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2205)
at java.base/javax.crypto.EncryptedPrivateKeyInfo.getKeySpecImpl(EncryptedPrivateKeyInfo.java:277)            

Appendix 5: Maven pom file

Create a maven project and enter the following dependency in the dependencies section.
Note that you might need to adapt the version.
Today, the current version is 4.0.2.

 

<dependencies>
    <dependency>
        <groupId>org.apache.santuario</groupId>
	<artifactId>xmlsec</artifactId>
	<version>4.0.2</version>
    </dependency>
</dependencies>

 

Run the command
mvn package
Maven will download the dependencies to the local repository at
C:\Users\joe\.m2\repository\org\apache\santuario\xmlsec\4.0.2\xmlsec-4.0.2.jar
From here it can be uploaded to CPI.

Appendix 6: Algorithms

For our convenience, I'm adding the lists of algorithms supported by the different libraries.
In case we need a missing algorithm, it should be possible to add a Security Provider to the Java security framework (e.g. Bouncycastle)

The Algorithms of "XML Encryption" specification

Block Encryption
required TRIPLEDES http://www.w3.org/2001/04/xmlenc#tripledes-cbc 
required AES-128 http://www.w3.org/2001/04/xmlenc#aes128-cbc 
required AES-256 http://www.w3.org/2001/04/xmlenc#aes256-cbc 
required AES128-GCM http://www.w3.org/2009/xmlenc11#aes128-gcm 
optional AES-192 http://www.w3.org/2001/04/xmlenc#aes192-cbc 
optional AES192-GCM http://www.w3.org/2009/xmlenc11#aes192-gcm 
optional AES256-GCM http://www.w3.org/2009/xmlenc11#aes256-gcm  

Key Transport
required RSA-OAEP (including MGF1 with SHA1) http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p 
Optional RSA-OAEP http://www.w3.org/2009/xmlenc11#rsa-oaep 
optional RSA-v1.5 http://www.w3.org/2001/04/xmlenc#rsa-1_5 

The algorithms of Santuario

The org.apache.xml.security.encryption.XMLCipher algorithm constants:

TRIPLEDES
AES_128, AES_256, AES_192
AES_128_GCM, AES_192_GCM, AES_256_GCM
RSA_v1dot5, RSA_OAEP, RSA_OAEP_11
SEED_128
CAMELLIA_128, CAMELLIA_192, CAMELLIA_256

Algorithms for Symmetric Key (DEK)

We generate the symmetric DEK with javax.crypto.KeyGenerator.
It can be initialized with the following algorithms:

AES
ARCFOUR (RC4), RC2
Blowfish
ChaCha20
DES
DESede (triple-des)
HmacMD5
HmacSHA1, HmacSHA224, HmacSHA256, HmacSHA384, HmacSHA512, HmacSHA3-224, HmacSHA3-256, HmacSHA3-384, HmacSHA3-512

Algorithms for Asymmetric Key (KEK)

These algorithms can be specified when generating an instance of KeyFactory.

RSA, RSASSA-PSS
DSA
EC
EdDSA, Ed25519, Ed448
DiffieHellman, XDH, X25519, X448

Openssl:

Generate EC parameters:
openssl genpkey -genparam -algorithm EC -out ecp.pem -pkeyopt ec_paramgen_curve:secp384r1 -pkeyopt ec_param_enc:named_curve

Generate EC key from parameters:
openssl genpkey -paramfile ecp.pem -out eckey.pem

Generate EC key directly:
openssl genpkey -algorithm EC -out eckey.pem -pkeyopt ec_paramgen_curve:P-384 -pkeyopt ec_param_enc:named_curve

Keysize

AES: key size 128, 192, 256
RC4 (ARCFOUR): 40–2048 bits
Blowfish: 32–448 bits
DES:  56       
DESede: 112 or 168 bits

Appendix 7: OpenSSL

Install OpenSSL

OpenSSL is the most commonly used tool (and library) for security-related operations, dealing with certificates, keys, converting, etc etc
Basically, it consists of libraries (for “c”) and a command line tool.

If you have GIT installed, you can just open the git bash which contains openssl.
Otherwise, follow these instructions to install OpenSSL under Windows:

Download OpenSSL starting from here.
Afterwards it might be required to add the openssl.exe to the PATH.
If you get this error:
Unable to load config info from /usr/local/ssl/openssl.cnf
You need to set an environment variable.
e.g. on command line:

set OPENSSL_CONF=c:/tools/openssl/openssl.cfg

Note:
For windows, the config-file extension .cnf has to be adjusted to .cfg
A template .cnf file can be found in GIT installation folder.