App Connect

 View Only

Encrypting part of a payload in IIB

By Tony Hays posted Mon May 24, 2021 04:41 PM


Encrypting part of a payload in IIB

NOTE: I am not a security expert.  Anything presented here may technically work, but may not be advisable for actual security requirements. 

My client recently made a decision that two data elements being stored in a database table would need to be encrypted at rest.  While we ultimately were able to do this with Oracle tablespace encryption, I also created a POC to show that we could do it in IIB if required as well.

In IIB, I accomplished this using a Java Compute Node approach, creating a fairly generic Encrypt/Decrypt node, using Environment.Variables as the way to pass parameters and receive results back in.  Doing this, any non-Java transformation can then access these functions.

I also broke to code into the Java Compute Node code, to deal with Environment.Variables, and a Singleton POJO to actually do the operations, which allows Java to call it directly, either within IIB or outside of IIB if needed.  Both are described below.

This uses a symmetric encryption/decryption algorithm meaning a single key is used to both encrypt and decrypt the data.  This is not suitable for all applications.

Java Compute Node

The JCN code first looks for the last Environment.Variables.Crypto element; if it isn’t found, an exception will be thrown.  It then looks for either Encrypt or Decrypt as a child node; if neither are found, or both are found, exceptions will be thrown.

For Encrypt, the child nodes under Encrypt are:

  • Seed, an initial seed value in CHAR format
  • ClearText, the text to encrypt, either in CHAR or BLOB format
  • CipherText, the result of encryption, replacing both of the above, a Base64 string in CHAR format

For Decrypt, the child nodes under Decrypt are:

  • Seed, an initial seed value in CHAR format
  • CipherText, the text to decrypt, a Base64 string in CHAR format
  • ClearText, the result of decryption, replacing both of the above, either in CHAR or BLOB format

As before, if any required child node is not present, and exception will be thrown.

The Seed value is used as the initialization vector for the encrypt operation.  Ideally, this is at least 8 chars; the POJO uses the first 8 chars, and will pad to 8 with an internal random value when less than 8.   You can use a fixed value, or better yet use a piece of data related to the ClearText value.  Note that this is not the encryption key, but the same value is needed for both encrypt and decrypt operations; that is, whatever you pass in for the seed to encrypt particular data, you need to pass the same value to decrypt it.

If the operation is successful, the result field will be created and the original inputs are deleted.  If the operation is unsuccessful, an exception will be thrown.

The code does not alter the input message received; it simply will pass through.  All inputs and results are in Environment.


Singleton POJO

The code that actually performs the encrypt and decrypt operations is a Singleton POJO.  I thought that the Singleton pattern would be better for performance under load, since the Java Cipher objects can be created once and used multiple times.

Note: This code does some things that are fine for a POC, but may not work for real production work. 

When the singleton is instantiated, it will create two Cipher objects; one to encrypt and one to decrypt, using the master key hard-coded into this module.  

Static synchronized encrypt and decrypt methods make it easier for callers to use; these static methods will get the singleton instance via getInstance() and call the proper method, and are also thread-safe.

For both encrypt and decrypt, the seed is either truncated or padded to exactly 8 bytes; there is a random string hard-coded into this module which is used for the padding.  This is then used to initialize the cipher object for the operation.

For an encrypt operation, the object passed in (String or byte[]) is then written to an ObjectOutputStream backed by a ByteArrayOutputStream; this allows both datatypes to be handled easily.  The Object stream is closed, and the Byte array is then padded to a multiple of 8 bytes as required, then encrypted and encoded as a Base64 String for output.

For a decrypt operation, the Base64 input String is decoded into bytes to be decrypted, into an ObjectInputStream backed by a ByteArrayInputStream.  Once decrypted, the original object is read out of the Object stream and returned as the output.



Using the node from ESQL

To test it, I created a simple message flow:

TestCrypto message flow

The flow expects and produces JSON message payloads.  To encrypt, it looks for Seed and ClearText children; the output replaces ClearText with CipherText.  ClearText can be a string or a JSON Object.  To decrypt, it looks for Seed and CipherText.  If the ClearText result is a BLOB, it will parse the result to try to create the original input object.


Inputs and Outputs

I then created 3 test input files:

Test1.json: {"Seed": "Hello", "ClearText": "This is secret text"}

Test2.json: {"Seed": "World", "ClearText": "This is secret text"}

Test3.json: {"Seed": "Hello", "ClearText": { "Name": "Password", "Value": "This is secret text"} }

Running them through the Encrypt flow, I received:




Running those outputs through the Decrypt flow, I received:

{"Seed":"Hello","ClearText":"This is secret text"}

{"Seed":"World","ClearText":"This is secret text"}

{"Seed":"Hello","ClearText":{"Name":"Password","Value":"This is secret text"}}

Potential improvements

  • The key value in the POJO is hard-coded clear text. This probably should be stored outside of the POJO and be obtained in a more secure manner.  This is left as an exercise for the reader.
  • The padding value in the POJO is hard-coded clear text. Ideally, all uses should pass in a value at least 8 chars long so that this is not needed.  I could have also chosen to simply repeat the input as many times as needed to get to 8 chars, or just let the cipher algorithm throw an exception instead.