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:
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);
}
}
%dw 2.0
output application/json
var encryptedPayload = readUrl("classpath://encrypted-data.json" , "application/json")
---
encryptedPayload.encryptedData as String
%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
Recommended by LinkedIn
{
"receivedTimestamp": "2024-02-03T16:27:44.030Z",
"processingTimeinMs": 0,
"resource": {
"pin": true,
"pinSupported": true
}
}
<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>
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.