How can one decrypt an encrypted JSON object in Mule using Java code and a private key?

How can one decrypt an encrypted JSON object in Mule using Java code and a private key?

Hello,

Here I am going to present a solution for decrypting an encrypted JSON object using Java code within a MuleSoft application, with the decryption key provided in .PEM format.

As an illustration, suppose we receive an encrypted JSON response. The encrypted data utilizes asymmetric encryption, employing a public-key cryptography technique. The message encryption is implemented via symmetric encryption using Advanced Encryption Standard (AES), Galois Counter Mode (GCM) with 128-bit or 256-bit key size. The encryption of keys is supported using RSA Optimal Asymmetric Encryption Padding (OAEP) with 2048-bit key size.The encryption service is based on JWE and works on top of SSL and requires separate key-pairs for Request and Response legs of the transaction. To decipher the following message, it is necessary to obtain the private key (.PEM) from the client who encrypted the message.

{
    "encryptedData": "eyJjdHkiOiJhcHBsaWNhdGlvbi9qc29uIiwiZW5jIjoiQTEyOEdDTSIsImlhdCI6MTcwNjk3NzY2NDAzMywiYWxnIjoiUlNBLU9BRVAtMjU2In0.V2UfrEYAEKALjN8DDXuPflIgyIj3vbnux-0QgohARypu53cKLEFfAviQoe5R0UQGoiKPn_mxabYuWVdlitIEDuBiut9z0sxS7MtjSPv_H1z_cDJQ9MPw0aGup-mmZk7cwFR_8KmsqTDAGdBvJCsda2AdLF7sHqNw3dKMk1VVfoW3LBCk0AQut9goTjl7U9s9Wlq15H3cKUJ06x4HnCy0IUtS1I_Kkl35RYGoRLQl97X2A3Rps7ZKunrBx1o3Jt-XFZcgQ9SeO7j_ONDAIJrkQrWnBl8SyIvMQOCdM403ky3iWPrcyB0s3MkW1nOEDaxHl-hd5Ny2MuaX--tfbAqZlQ.D7FG-yAB-V5mSuTh.I80bVx_ml6WrzXnxRO6cCdyi_U7Vn713e7VHzFX5l7oWTmxa1mtP4yA7EX70z6GGpTKR4WQTnB6uKvk7ZSVoyrp2l_71MhlH9Yc2ti_tGWSBVtApZOLfQ7E1vDWkaoAiCuGv7dup9Ik7A07EhECioLO_A1Zjunn3FOmV0oJFLh39zd-ZG_my6cUkVA.42Ezg2euwdUIZQ3sPMuDSA"
}        

Here, we see how to decrypt the above in Mule 4 by using Java code:

  • Build a new Mule application with an HTTP listener as the source.
  • Develop a Java class containing the provided code (This code includes encryption and decryption mechanism). In case of any Java import errors, include the necessary dependencies in the POM file, as listed at the end of this document.

package com;


import java.io.*;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;
import java.util.Enumeration;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1Sequence;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.RSADecrypter;
import com.nimbusds.jose.crypto.RSAEncrypter;
import com.nimbusds.jose.util.Base64;

public class DecryptionUtils {

    private static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
    private static final String END_CERT = "-----END CERTIFICATE-----";
    private static final String BEGIN_RSA_PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----";
    private static final String END_RSA_PRIVATE_KEY = "-----END RSA PRIVATE KEY-----";

    public static String getEncryptedPayload(String payload, String pemEncodedPublicKey) {
    	try {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(Include.NON_NULL);
        mapper.setSerializationInclusion(Include.NON_EMPTY);
        String plainText = payload == null ? "" : payload;
        JWEHeader.Builder headerBuilder = new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A128GCM);
        headerBuilder.customParam("iat", System.currentTimeMillis());
        
        JWEObject jweObject = new JWEObject(headerBuilder.build(), new Payload(plainText));
        jweObject.encrypt(new RSAEncrypter(getRSAPublicKey(pemEncodedPublicKey)));
        return jweObject.serialize();
    	}
		catch (Exception e) {
			return e.getMessage();
		}
    }
    
    /* * Converts PEM file content to RSAPublicKey*/
    private static RSAPublicKey getRSAPublicKey(String pemEncodedPublicKey) {
        try {
            Base64 base64 = new Base64(
            pemEncodedPublicKey.replaceAll(BEGIN_CERT, "").replaceAll(END_CERT, ""));
            Certificate cf = CertificateFactory.getInstance("X.509").generateCertificate(new ByteArrayInputStream(base64.decode()));
            return (RSAPublicKey) cf.getPublicKey();
    	}
    	catch (Exception e) {
    		return null;
    	}
   }

    public static String getDecryptedPayload(String encryptedPayload, String mlePrivateKeyPath)  {
        try {
            JWEObject jweObject = JWEObject.parse(encryptedPayload);
            jweObject.decrypt(new RSADecrypter(getRSAPrivateKey(mlePrivateKeyPath)));
            String response = jweObject.getPayload().toString();          
            return response;
        } catch (Exception e) {
            return e.getMessage();
        }
    }
    
    /** Converts PEM file content to RSAPrivateKey */
    private static PrivateKey getRSAPrivateKey(String pemEncodedPrivateKey) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        Base64 base64 = new Base64(pemEncodedPrivateKey.replaceAll(BEGIN_RSA_PRIVATE_KEY, "").replaceAll(END_RSA_PRIVATE_KEY, ""));
        ASN1Sequence primitive = (ASN1Sequence) ASN1Sequence.fromByteArray(base64.decode());
        Enumeration<?> e = primitive.getObjects();
        BigInteger v = ((ASN1Integer) e.nextElement()).getValue();
        int version = v.intValue();
        if (version != 0 && version != 1) {
            throw new IllegalArgumentException("wrong version for RSA private key");
        }
        BigInteger modulus = ((ASN1Integer) e.nextElement()).getValue();
        ((ASN1Integer) e.nextElement()).getValue();
        BigInteger privateExponent = ((ASN1Integer) e.nextElement()).getValue();
        ((ASN1Integer) e.nextElement()).getValue();
        ((ASN1Integer) e.nextElement()).getValue();
        ((ASN1Integer) e.nextElement()).getValue();
        ((ASN1Integer) e.nextElement()).getValue();
        ((ASN1Integer) e.nextElement()).getValue();
        RSAPrivateKeySpec privateKeySpec = new RSAPrivateKeySpec(modulus, privateExponent);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return (PrivateKey) keyFactory.generatePrivate(privateKeySpec);
    }

}        

  • Utilize the Transform Message component to convert the JSON value of "encryptedData" value into text format.

%dw 2.0
output application/json
var encryptedPayload = readUrl("classpath://encrypted-data.json" , "application/json")
---
encryptedPayload.encryptedData as String        

  • Include another Transform Message component to Import the Java code and provide the "encryptedData" and private key (.PEM) values as strings and read the decrypted data in JSON format.

%dw 2.0
import java!com::DecryptionUtils
output application/json
var decryptData =  DecryptionUtils::getDecryptedPayload(payload, readUrl("classpath://private-key.pem" , "text/plain"))
var decryptRes = read(decryptData, "application/json")
---
decryptRes         

  • Upon the successful execution of the flow, the encrypted data will be decrypted and presented in JSON format.

{
    "receivedTimestamp": "2024-02-03T16:27:44.030Z",
    "processingTimeinMs": 0,
    "resource": {
        "pin": true,
        "pinSupported": true
    }
}        

  • Below is my mule 4 code. Decrypt the encrypted payload using the given code, employing your own private key and encrypted payload.

<flow name="decryptionFlow"
		doc:id="7c09be7e-3df8-4bac-9184-2fa1a6d8e3f3">
		<http:listener doc:name="Listener"
			doc:id="ea48334a-1edc-45b5-9a7d-cf1d38753ca3"
			config-ref="HTTP_Listener_config" path="/decrypt" />
		<ee:transform doc:name="Encrypted data to string"
			doc:id="2fc97975-f43a-4e95-855b-82c1e017b6d8">
			<ee:message>
				<ee:set-payload><![CDATA[%dw 2.0
output application/json
var encryptedPayload = readUrl("classpath://encrypted-data.json" , "application/json")
---
encryptedPayload.encryptedData as String]]></ee:set-payload>
			</ee:message>
		</ee:transform>
		<logger level="INFO" doc:name="Logger"
			doc:id="01d7619b-f2e7-4814-9a88-6f778e53f651" message="#[payload]" />
		<ee:transform doc:name="Decrypting the Data" doc:id="66fad45c-2573-4d87-a14f-74a284d8b6fa" >
			<ee:message >
				<ee:set-payload ><![CDATA[%dw 2.0
import java!com::DecryptionUtils
output application/json
var decryptData =  DecryptionUtils::getDecryptedPayload(payload, readUrl("classpath://private-key.pem" , "text/plain"))
var decryptRes = read(decryptData, "application/json")
---
decryptRes ]]></ee:set-payload>
			</ee:message>
		</ee:transform>
		<logger level="INFO" doc:name="Logger" doc:id="2a068998-0de6-4192-ab0b-613f9be2fb54" message="#[payload]"/>
	</flow>        

  • I am sharing a screenshot of my code, where you can locate the private key and encrypted data files in the resource folder.

Article content
Mule Flow

Note: Please ensure to replace "encryptedData" and private key values with the actual data in your implementation. You'll get an error if you use invalid key. Private key should starts with "-----BEGIN RSA PRIVATE KEY-----" and ends with "-----END RSA PRIVATE KEY-----" tags.

The following dependencies are necessary for the Java code in Mule.

<dependency>
	<groupId>commons-codec</groupId>
	<artifactId>commons-codec</artifactId>
	<version>1.16.0</version>
</dependency>
<dependency>
	<groupId>com.nimbusds</groupId>
	<artifactId>nimbus-jose-jwt</artifactId>
	<version>9.37.3</version>
</dependency>
<dependency>
	<groupId>org.bouncycastle</groupId>
	<artifactId>bcpkix-jdk15on</artifactId>
	<version>1.70</version>
</dependency>
<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-annotations</artifactId>
	<version>2.16.1</version>
</dependency>
<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-core</artifactId>
	<version>2.16.1</version>
</dependency>
<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	<version>2.16.1</version>
</dependency>
<dependency>
	<groupId>org.mule.module</groupId>
	<artifactId>mule-java-module</artifactId>
	<version>1.2.11</version>
	<classifier>mule-plugin</classifier>
</dependency>        


Thank you and hope this article helps you! If you have any questions regarding this, please don't hesitate to reach out to me on LinkedIn at www.garudax.id/in/harish-vadlamudi.

To view or add a comment, sign in

Others also viewed

Explore content categories