Encrypting Inside WSO2 ESB Using the AES Algorithm
Encrypting a payload before sending it to storage is the simplest way to ensure security. Learn how to do so using the AES algorithm.
Join the DZone community and get the full member experience.
Join For Free
In integration development, you will come across instances where you need to hold a payload in an intermediate place before it reaches its final destination, such as a queue. The problem is that the payload could be readable while it's there, and if the particular container has less security, it's prone to hacking.
The simplest solution is encrypting it before we send it to the temporary storage.
This blog series focuses on a method to handle this problem inside WSO2 ESB. For this example, the AES algorithm will be used for encryption.
In terms of high-level design:
There will be an ESB template which the user needs to pass the payload to do the encryption, and for decryption, it needs the encrypted payload alone with the secret key and ivByte, which are the results of the encryption process.
- Inside the template, a class mediator will be invoked to do the logic.
- For encryption, a secret key is generated and an ivByte is created, which is the combination that is required to get the real info from the encrypted string. Later, this can be bit altered to pass a secret key for encryption.
Below is the simple design diagram of the final result.
That's all for the description. Let's start with coding.
Since this is a WSO2 project, create each of below projects:
- ESB config project.
Composite application project.
Mediator project.
First, we will start with the Mediator project.
The encryption class (EncryptorAES.java) will have three main methods: encrypt(), decrypt(), and generateSecretKey(). The javax.crypto.Cipher class is used for encryption, and as you can see in decryption, an extra parameter is passed for the cipher.init() method. This is because, to decrypt it, we need extra information from the cipher instance after encryption. To understand the use of ivByte, encrypt a payload with the same encryption key few times; you will see that even though you keep the inputs the same, the encrypted result and ivByte are always different. This is because some information from the cipher is there in the ivByte, and that should be passed to the certain encrypted result. If you switch it with a different encrypted result of the same payload and encryption key, you will not get the correct decrypted payload, rather an error or jargon.
package encryptor.mediator;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidParameterSpecException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
public class EncryptorAES {
private static final String ALGORITHM = "AES";
private static final String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static final String ENCODING = "UTF-8";
private byte[] ivBytes;
private SecretKey secretKey;
public String encrypt(String payload)
throws NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException,
NoSuchPaddingException, UnsupportedEncodingException, InvalidParameterSpecException {
generateSecretKey();
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
AlgorithmParameters params = cipher.getParameters();
ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] encryptedTextBytes = cipher.doFinal(payload.getBytes(ENCODING));
return new String(new Base64().encode(encryptedTextBytes));
}
public String decrypt(String payload, String key, String bytes)
throws IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException, DecoderException {
setSecretKey(key);
setIVBytes(bytes);
byte[] encryptedTextBytes = Base64.decodeBase64(payload.getBytes());
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(ivBytes));
byte[] decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
return new String(decryptedTextBytes);
}
private void generateSecretKey() throws NoSuchAlgorithmException {
KeyGenerator generator = KeyGenerator.getInstance(ALGORITHM);
generator.init(128);
secretKey = generator.generateKey();
}
public String getSecretKey() {
return Hex.encodeHexString(secretKey.getEncoded());
}
public String getIVBytes() {
return Hex.encodeHexString(ivBytes);
}
private void setIVBytes(String ivBytes) throws DecoderException {
this.ivBytes = Hex.decodeHex(ivBytes.toCharArray());
}
private void setSecretKey(String secretKey) throws DecoderException {
byte[] byteArray = Hex.decodeHex(secretKey.toCharArray());
this.secretKey = new SecretKeySpec(byteArray, 0, byteArray.length, ALGORITHM);
}
}
This is the code of the mediator class:
package encryptor.mediator;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidParameterSpecException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.apache.commons.codec.DecoderException;
import org.apache.synapse.MessageContext;
import org.apache.synapse.mediators.AbstractMediator;
public class EncryptorMediator extends AbstractMediator {
public boolean mediate(MessageContext context) {
String payload = getPayload(context);
if(payload == null || payload.length() < 1){
System.out.println("1st condition");
return completeProcess(context, "Payload is empty.");
}else{
String key = getSecretKey(context);
String bytes = getIVByteString(context);
process(context, payload, key, bytes);
}
return true;
}
private boolean process(MessageContext ctx, String payload, String key, String bytes){
String respondPayload = "";
String encryptionKey = "";
String ivbyteString = "";
try {
EncryptorAES encryptor = new EncryptorAES();
if(bytes == "NULL"){
respondPayload = encryptor.encrypt(payload);
encryptionKey = encryptor.getSecretKey();
ivbyteString = encryptor.getIVBytes();
}else{
if(key != null && !key.equals("NULL") && bytes != null){
respondPayload = encryptor.decrypt(payload, key, bytes);
}else{
return completeProcess(ctx,"Encryption key or IVByte String is invalid.");
}
}
return completeProcess(ctx, respondPayload, encryptionKey, ivbyteString);
} catch (InvalidKeyException e) {
return completeProcess(ctx,e.getMessage());
} catch (NoSuchAlgorithmException e) {
return completeProcess(ctx,e.getMessage());
} catch (IllegalBlockSizeException e) {
return completeProcess(ctx,e.getMessage());
} catch (BadPaddingException e) {
return completeProcess(ctx,e.getMessage());
} catch (NoSuchPaddingException e) {
return completeProcess(ctx,e.getMessage());
} catch (UnsupportedEncodingException e) {
return completeProcess(ctx,e.getMessage());
} catch (InvalidParameterSpecException e) {
return completeProcess(ctx,e.getMessage());
} catch (InvalidAlgorithmParameterException e) {
return completeProcess(ctx,e.getMessage());
} catch (DecoderException e) {
return completeProcess(ctx,e.getMessage());
}
}
private String getPayload(MessageContext context){
return context.getProperty("Paylod").toString();
}
private String getSecretKey(MessageContext context){
return context.getProperty("SecretKey").toString();
}
private String getIVByteString(MessageContext context){
return context.getProperty("IVByteString").toString();
}
private boolean completeProcess(MessageContext context,String payload, String key, String bytes){
setResultPayload(context,payload);
setEncryptionKey(context,key);
setIVByteString(context,bytes);
setSuccess(context, "true");
setErrorMessage(context, "");
return true;
}
private boolean completeProcess(MessageContext context,String error){
setSuccess(context, "false");
setErrorMessage(context, error);
return true;
}
private void setSuccess(MessageContext context, String success){
context.setProperty("success", success);
}
private void setResultPayload(MessageContext context, String respondPayload){
context.setProperty("resultPayload", respondPayload);
}
private void setEncryptionKey(MessageContext context, String encryptionKey){
context.setProperty("encyptionKey", encryptionKey);
}
private void setErrorMessage(MessageContext context, String errorMessage){
context.setProperty("errorMessage", errorMessage);
}
private void setIVByteString(MessageContext context, String ivbyteString){
context.setProperty("ivByteString", ivbyteString);
}
}
I have also created a class to test the logic in the EncryptorAES class and a class for Exception handling. You can check those in the Git repo.
Next is the implementation of the template and test API. Note that implementing a template is not necessary; you can use the class directly with the class mediator after setting the required properties.
Below is the template and API:
<?xml version="1.0" encoding="UTF-8"?>
<template name="encryptor-template" xmlns="http://ws.apache.org/ns/synapse">
<parameter name="payload"/>
<parameter name="key"/>
<parameter name="bytestring"/>
<sequence>
<property expression="$func:payload" name="Paylod" scope="default" type="STRING"/>
<property expression="$func:key" name="SecretKey" scope="default" type="STRING"/>
<property expression="$func:bytestring" name="IVByteString" scope="default" type="STRING"/>
<class description="" name="encryptor.mediator.EncryptorMediator"/>
</sequence>
</template>
<?xml version="1.0" encoding="UTF-8"?>
<api context="/message" name="encryptor-api" xmlns="http://ws.apache.org/ns/synapse">
<resource methods="POST" protocol="https" uri-template="/encrypt">
<inSequence>
<log level="full">
<property name="Encrypt" value="Encrypting message"/>
</log>
<enrich description="">
<source clone="true" type="body"/>
<target property="PAYLOAD" type="property"/>
</enrich>
<call-template description="Call: encryptor-template" target="encryptor-template">
<with-param name="payload" value="{get-property('PAYLOAD')}"/>
<with-param name="key" value="NULL"/>
<with-param name="bytestring" value="NULL"/>
</call-template>
<payloadFactory description="Build Test Response" media-type="xml">
<format>
<payload xmlns="">
<success>$1</success>
<errorMessage>$2</errorMessage>
<encryptedPayload>$3</encryptedPayload>
<secretKey>$4</secretKey>
<ivByteString>$5</ivByteString>
</payload>
</format>
<args>
<arg evaluator="xml" expression="get-property('success')"/>
<arg evaluator="xml" expression="get-property('errorMessage')"/>
<arg evaluator="xml" expression="get-property('resultPayload')"/>
<arg evaluator="xml" expression="get-property('encyptionKey')"/>
<arg evaluator="xml" expression="get-property('ivByteString')"/>
</args>
</payloadFactory>
<respond/>
</inSequence>
<outSequence/>
<faultSequence/>
</resource>
<resource methods="POST" protocol="https" uri-template="/decrypt">
<inSequence>
<log level="full">
<property name="Decrypt" value="Decrypting message."/>
</log>
<enrich description="">
<source clone="true" type="body"/>
<target property="PAYLOAD" type="property"/>
</enrich>
<property description="Set: SECRET_KEY" expression="$trp:SecretKey" name="SECRET_KEY" scope="default" type="STRING"/>
<property description="Set: BYTE_STRING" expression="$trp:ByteString" name="BYTE_STRING" scope="default" type="STRING"/>
<call-template description="Call: encryptor-template" target="encryptor-v1encryptor-template">
<with-param name="payload" value="{get-property('PAYLOAD')}"/>
<with-param name="key" value="{get-property('SECRET_KEY')}"/>
<with-param name="bytestring" value="{get-property('BYTE_STRING')}"/>
</call-template>
<payloadFactory description="Build Test Response" media-type="xml">
<format>
<payload xmlns="">
<success>$1</success>
<errorMessage>$2</errorMessage>
<decryptedPayload>$3</decryptedPayload>
</payload>
</format>
<args>
<arg evaluator="xml" expression="get-property('success')"/>
<arg evaluator="xml" expression="get-property('errorMessage')"/>
<arg evaluator="xml" expression="get-property('resultPayload')"/>
</args>
</payloadFactory>
<respond/>
</inSequence>
<outSequence/>
<faultSequence/>
</resource>
</api>
Next, create a composite application project and select the template, API, and mediator artifacts. Deploy the cApp in the ESB and test it from Postman.
Published at DZone with permission of Priyashani Fernando. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments