diff --git a/README.md b/README.md
index 8a25748..2233542 100644
--- a/README.md
+++ b/README.md
@@ -178,6 +178,37 @@ JweConfig config = JweConfigBuilder.aJweEncryptionConfig()
.build();
```
+**AES-CBC HMAC Authentication (A128CBC-HS256)**
+
+For enhanced security when using AES-CBC mode (A128CBC-HS256), you can enable HMAC authentication tag verification. This ensures data authenticity and integrity according to the JWE specification (RFC 7516).
+
+By default, HMAC verification is **disabled** for backward compatibility. To enable it:
+
+```java
+JweConfig config = JweConfigBuilder.aJweEncryptionConfig()
+ .withEncryptionCertificate(encryptionCertificate)
+ .withDecryptionKey(decryptionKey)
+ .withEnableCbcHmacVerification(true) // Enable HMAC authentication
+ .build();
+```
+
+**When to enable HMAC verification:**
+- ✅ New integrations with systems that properly implement JWE A128CBC-HS256
+- ✅ When security and data authenticity are critical
+- ✅ When working with compliant JWE encryption sources
+
+**When to keep it disabled (default):**
+- ⚠️ Legacy systems that don't compute HMAC tags correctly
+- ⚠️ Maintaining backward compatibility with existing deployments
+- ⚠️ Encryption sources that don't fully follow the JWE specification
+
+**Technical Details:**
+When enabled, the library:
+- Splits the 256-bit Content Encryption Key (CEK) into a 128-bit HMAC key and 128-bit AES key
+- Computes HMAC-SHA256 over: AAD || IV || Ciphertext || AL (AAD length in bits)
+- Verifies the authentication tag (first 128 bits of HMAC output) before decryption
+- Throws an `EncryptionException` if the authentication tag is invalid
+
##### • Performing JWE Encryption
Call `JweEncryption.encryptPayload` with a JSON request payload and a `JweConfig` instance.
diff --git a/src/main/java/com/mastercard/developer/encryption/EncryptionConfig.java b/src/main/java/com/mastercard/developer/encryption/EncryptionConfig.java
index 7f6a1bf..ff26d0b 100644
--- a/src/main/java/com/mastercard/developer/encryption/EncryptionConfig.java
+++ b/src/main/java/com/mastercard/developer/encryption/EncryptionConfig.java
@@ -53,6 +53,14 @@ public enum Scheme {
Integer ivSize = 16;
+ /**
+ * Enable HMAC authentication tag verification for AES-CBC mode (A128CBC-HS256).
+ * When true, authentication tags are verified during decryption.
+ * Default is false for backward compatibility with systems that don't compute HMAC tags.
+ * Set to true to enable proper HMAC verification according to JWE spec.
+ */
+ Boolean enableCbcHmacVerification = false;
+
/**
* A list of JSON paths to encrypt in request payloads.
* Example:
@@ -116,4 +124,6 @@ String getEncryptedValueFieldName() {
}
public Integer getIVSize() { return ivSize; }
+
+ public Boolean getEnableCbcHmacVerification() { return enableCbcHmacVerification; }
}
diff --git a/src/main/java/com/mastercard/developer/encryption/EncryptionConfigBuilder.java b/src/main/java/com/mastercard/developer/encryption/EncryptionConfigBuilder.java
index 42c6005..7f8f022 100644
--- a/src/main/java/com/mastercard/developer/encryption/EncryptionConfigBuilder.java
+++ b/src/main/java/com/mastercard/developer/encryption/EncryptionConfigBuilder.java
@@ -24,6 +24,7 @@ abstract class EncryptionConfigBuilder {
protected String encryptedValueFieldName;
protected Integer ivSize = 16;
+ protected Boolean enableCbcHmacVerification = false;
void computeEncryptionKeyFingerprintWhenNeeded() throws EncryptionException {
try {
diff --git a/src/main/java/com/mastercard/developer/encryption/JweConfigBuilder.java b/src/main/java/com/mastercard/developer/encryption/JweConfigBuilder.java
index 59659cb..308938a 100644
--- a/src/main/java/com/mastercard/developer/encryption/JweConfigBuilder.java
+++ b/src/main/java/com/mastercard/developer/encryption/JweConfigBuilder.java
@@ -34,6 +34,7 @@ public JweConfig build() throws EncryptionException {
config.encryptedValueFieldName = this.encryptedValueFieldName == null ? "encryptedData" : this.encryptedValueFieldName;
config.scheme = EncryptionConfig.Scheme.JWE;
config.ivSize = ivSize;
+ config.enableCbcHmacVerification = enableCbcHmacVerification;
return config;
}
@@ -105,6 +106,17 @@ public JweConfigBuilder withEncryptionIVSize(Integer ivSize) {
}
throw new IllegalArgumentException("Supported IV Sizes are either 12 or 16!");
}
+ /**
+ * See: {@link EncryptionConfig#enableCbcHmacVerification}.
+ * Enable or disable HMAC authentication tag verification for AES-CBC mode (A128CBC-HS256).
+ * Default is false (disabled) for backward compatibility.
+ * Set to true to enable proper HMAC verification according to JWE spec.
+ */
+ public JweConfigBuilder withEnableCbcHmacVerification(Boolean enableCbcHmacVerification) {
+ this.enableCbcHmacVerification = enableCbcHmacVerification;
+ return this;
+ }
+
private void checkParameterValues() {
if (decryptionKey == null && encryptionCertificate == null && encryptionKey == null) {
diff --git a/src/main/java/com/mastercard/developer/encryption/aes/AESCBC.java b/src/main/java/com/mastercard/developer/encryption/aes/AESCBC.java
index 127a5bd..f098e02 100644
--- a/src/main/java/com/mastercard/developer/encryption/aes/AESCBC.java
+++ b/src/main/java/com/mastercard/developer/encryption/aes/AESCBC.java
@@ -1,13 +1,19 @@
package com.mastercard.developer.encryption.aes;
+import com.mastercard.developer.encryption.EncryptionException;
import com.mastercard.developer.encryption.jwe.JweObject;
+import com.mastercard.developer.utils.ByteUtils;
import com.mastercard.developer.utils.EncodingUtils;
import javax.crypto.Cipher;
+import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.Key;
+import java.security.MessageDigest;
import java.security.spec.AlgorithmParameterSpec;
public class AESCBC {
@@ -15,21 +21,64 @@ public class AESCBC {
private AESCBC() {
}
- private static final String CYPHER = "AES/CBC/PKCS5Padding";
+ private static final String CIPHER = "AES/CBC/PKCS5Padding";
+ private static final String HMAC_ALGORITHM = "HmacSHA256";
@java.lang.SuppressWarnings("squid:S3329")
- public static byte[] decrypt(Key secretKey, JweObject object) throws GeneralSecurityException {
- // First 16 bytes are the MAC key, so we only use the second 16 bytes
- SecretKeySpec aesKey = new SecretKeySpec(secretKey.getEncoded(), 16, 16, "AES");
+ public static byte[] decrypt(Key secretKey, JweObject object, boolean enableHmacVerification) throws GeneralSecurityException, EncryptionException {
+ byte[] cek = secretKey.getEncoded();
+
+ // For A128CBC-HS256: First 16 bytes are HMAC key, second 16 bytes are AES key
+ int keyLength = cek.length / 2;
+ SecretKeySpec aesKey = new SecretKeySpec(cek, keyLength, keyLength, "AES");
+
byte[] cipherText = EncodingUtils.base64Decode(object.getCipherText());
byte[] iv = EncodingUtils.base64Decode(object.getIv());
+ // Only verify authentication tag if enabled
+ if (enableHmacVerification) {
+ SecretKeySpec hmacKey = new SecretKeySpec(cek, 0, keyLength, HMAC_ALGORITHM);
+ byte[] authTag = EncodingUtils.base64Decode(object.getAuthTag());
+ byte[] aad = object.getRawHeader().getBytes(StandardCharsets.US_ASCII);
+
+ byte[] expectedTag = computeAuthTag(hmacKey, aad, iv, cipherText, keyLength);
+ if (!MessageDigest.isEqual(authTag, expectedTag)) {
+ throw new EncryptionException("Authentication tag verification failed");
+ }
+ }
+
return cipher(aesKey, new IvParameterSpec(iv), cipherText, Cipher.DECRYPT_MODE);
}
public static byte[] cipher(Key key, AlgorithmParameterSpec iv, byte[] bytes, int mode) throws GeneralSecurityException {
- Cipher cipher = Cipher.getInstance(CYPHER);
+ Cipher cipher = Cipher.getInstance(CIPHER);
cipher.init(mode, key, iv);
return cipher.doFinal(bytes);
}
+
+ /**
+ * Computes the authentication tag for AES-CBC-HMAC-SHA2
+ * HMAC is computed over: AAD || IV || Ciphertext || AL
+ * where AL is the length of AAD in bits expressed as a 64-bit big-endian integer
+ */
+ private static byte[] computeAuthTag(SecretKeySpec hmacKey, byte[] aad, byte[] iv, byte[] cipherText, int tagLength)
+ throws GeneralSecurityException {
+ Mac mac = Mac.getInstance(HMAC_ALGORITHM);
+ mac.init(hmacKey);
+
+ // Compute AL (AAD Length in bits as 64-bit big-endian)
+ long aadLengthBits = (long) aad.length * 8;
+ byte[] al = ByteBuffer.allocate(8).putLong(aadLengthBits).array();
+
+ // HMAC input: AAD || IV || Ciphertext || AL
+ mac.update(aad);
+ mac.update(iv);
+ mac.update(cipherText);
+ mac.update(al);
+
+ byte[] hmacOutput = mac.doFinal();
+
+ // Return first half (tagLength bytes) as the authentication tag
+ return ByteUtils.subArray(hmacOutput, 0, tagLength);
+ }
}
diff --git a/src/main/java/com/mastercard/developer/encryption/jwe/JweObject.java b/src/main/java/com/mastercard/developer/encryption/jwe/JweObject.java
index 7e38a36..1529a91 100644
--- a/src/main/java/com/mastercard/developer/encryption/jwe/JweObject.java
+++ b/src/main/java/com/mastercard/developer/encryption/jwe/JweObject.java
@@ -49,7 +49,7 @@ public String decrypt(JweConfig config) throws EncryptionException, GeneralSecur
if (AES_GCM_ENCRYPTION_METHODS.contains(encryptionMethod)) {
plainText = AESGCM.decrypt(cek, this);
} else if (encryptionMethod.equals(A128CBC_HS256)) {
- plainText = AESCBC.decrypt(cek, this);
+ plainText = AESCBC.decrypt(cek, this, config.getEnableCbcHmacVerification());
} else {
throw new EncryptionException(String.format("Encryption method %s not supported", encryptionMethod));
}
diff --git a/src/test/java/com/mastercard/developer/encryption/JweConfigBuilderTest.java b/src/test/java/com/mastercard/developer/encryption/JweConfigBuilderTest.java
index ac02937..ce443aa 100644
--- a/src/test/java/com/mastercard/developer/encryption/JweConfigBuilderTest.java
+++ b/src/test/java/com/mastercard/developer/encryption/JweConfigBuilderTest.java
@@ -193,6 +193,35 @@ public void testBuild_ShouldThrowIllegalArgumentException_WhenNotHavingWildcardO
.build();
}
+ @Test
+ public void testBuild_ShouldDisableCbcHmacVerificationByDefault() throws Exception {
+ JweConfig config = JweConfigBuilder.aJweEncryptionConfig()
+ .withEncryptionCertificate(TestUtils.getTestEncryptionCertificate())
+ .withDecryptionKey(TestUtils.getTestDecryptionKey())
+ .build();
+ Assert.assertFalse("HMAC verification should be disabled by default for backward compatibility", config.getEnableCbcHmacVerification());
+ }
+
+ @Test
+ public void testBuild_ShouldAllowDisablingCbcHmacVerification() throws Exception {
+ JweConfig config = JweConfigBuilder.aJweEncryptionConfig()
+ .withEncryptionCertificate(TestUtils.getTestEncryptionCertificate())
+ .withDecryptionKey(TestUtils.getTestDecryptionKey())
+ .withEnableCbcHmacVerification(false)
+ .build();
+ Assert.assertFalse("HMAC verification should be disabled when explicitly set to false", config.getEnableCbcHmacVerification());
+ }
+
+ @Test
+ public void testBuild_ShouldAllowEnablingCbcHmacVerificationExplicitly() throws Exception {
+ JweConfig config = JweConfigBuilder.aJweEncryptionConfig()
+ .withEncryptionCertificate(TestUtils.getTestEncryptionCertificate())
+ .withDecryptionKey(TestUtils.getTestDecryptionKey())
+ .withEnableCbcHmacVerification(true)
+ .build();
+ Assert.assertTrue("HMAC verification should be enabled when explicitly set to true", config.getEnableCbcHmacVerification());
+ }
+
@Test
public void testBuild_ShouldThrowIllegalArgumentException_WhenMultipleWildcardsOnEncryptionPaths() throws Exception {
expectedException.expect(IllegalArgumentException.class);
diff --git a/src/test/java/com/mastercard/developer/encryption/aes/AESCBCTest.java b/src/test/java/com/mastercard/developer/encryption/aes/AESCBCTest.java
new file mode 100644
index 0000000..3625c60
--- /dev/null
+++ b/src/test/java/com/mastercard/developer/encryption/aes/AESCBCTest.java
@@ -0,0 +1,218 @@
+package com.mastercard.developer.encryption.aes;
+
+import com.mastercard.developer.encryption.EncryptionException;
+import com.mastercard.developer.encryption.jwe.JweObject;
+import com.mastercard.developer.json.JsonEngine;
+import com.mastercard.developer.utils.EncodingUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+public class AESCBCTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void testDecrypt_ShouldDecryptSuccessfully_WhenHmacVerificationIsEnabledAndTagIsValid() throws Exception {
+ // Given: A properly constructed JWE with correct HMAC tag
+ byte[] cekBytes = new byte[32]; // 256-bit key (128-bit HMAC key + 128-bit AES key)
+ java.security.SecureRandom random = new java.security.SecureRandom();
+ random.nextBytes(cekBytes);
+
+ SecretKeySpec cek = new SecretKeySpec(cekBytes, "AES");
+ SecretKeySpec hmacKey = new SecretKeySpec(cekBytes, 0, 16, "HmacSHA256");
+ SecretKeySpec aesKey = new SecretKeySpec(cekBytes, 16, 16, "AES");
+
+ // Encrypt data
+ byte[] plainText = "Valid HMAC Test Data".getBytes(StandardCharsets.UTF_8);
+ byte[] iv = new byte[16];
+ random.nextBytes(iv);
+
+ javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
+ cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, aesKey, new javax.crypto.spec.IvParameterSpec(iv));
+ byte[] cipherText = cipher.doFinal(plainText);
+
+ // Compute proper HMAC according to JWE spec
+ String rawHeader = EncodingUtils.base64UrlEncode("{\"enc\":\"A128CBC-HS256\",\"alg\":\"RSA-OAEP-256\"}".getBytes());
+ byte[] aad = rawHeader.getBytes(StandardCharsets.US_ASCII);
+
+ javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256");
+ mac.init(hmacKey);
+ mac.update(aad);
+ mac.update(iv);
+ mac.update(cipherText);
+
+ // Add AL (AAD length in bits as 64-bit big-endian)
+ long aadLengthBits = (long) aad.length * 8;
+ java.nio.ByteBuffer alBuffer = java.nio.ByteBuffer.allocate(8);
+ alBuffer.putLong(aadLengthBits);
+ mac.update(alBuffer.array());
+
+ byte[] hmacOutput = mac.doFinal();
+ byte[] authTag = new byte[16]; // First 16 bytes (tag length = key length for A128CBC-HS256)
+ System.arraycopy(hmacOutput, 0, authTag, 0, 16);
+
+ // Construct JWE string
+ String jweString = rawHeader + ".dummy." + EncodingUtils.base64UrlEncode(iv) + "." +
+ EncodingUtils.base64UrlEncode(cipherText) + "." + EncodingUtils.base64UrlEncode(authTag);
+ JweObject jweObject = JweObject.parse(jweString, JsonEngine.getDefault());
+
+ // When: Decrypt with HMAC verification enabled
+ byte[] result = AESCBC.decrypt(cek, jweObject, true);
+
+ // Then: Should succeed and return correct plaintext
+ assertNotNull(result);
+ assertEquals("Valid HMAC Test Data", new String(result, StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void testDecrypt_ShouldThrowException_WhenHmacVerificationIsEnabledAndTagIsInvalid() throws Exception {
+ // Given: A JWE object with an invalid HMAC tag
+ String rawHeader = EncodingUtils.base64UrlEncode("{\"enc\":\"A128CBC-HS256\",\"alg\":\"RSA-OAEP-256\"}".getBytes());
+
+ // Create a JWE string with intentionally wrong auth tag
+ String encryptedKey = "dummy_encrypted_key_base64url";
+ String iv = EncodingUtils.base64UrlEncode(new byte[16]); // 16-byte IV
+ String cipherText = EncodingUtils.base64UrlEncode("encrypted_data".getBytes());
+ String invalidAuthTag = EncodingUtils.base64UrlEncode(new byte[16]); // Wrong tag
+
+ String jweString = rawHeader + "." + encryptedKey + "." + iv + "." + cipherText + "." + invalidAuthTag;
+ JweObject jweObject = JweObject.parse(jweString, JsonEngine.getDefault());
+
+ // When/Then: Decryption should fail with authentication tag verification error
+ expectedException.expect(EncryptionException.class);
+ expectedException.expectMessage("Authentication tag verification failed");
+
+ // Create a dummy CEK
+ byte[] cekBytes = new byte[32]; // 256-bit key
+ SecretKeySpec cek = new SecretKeySpec(cekBytes, "AES");
+
+ AESCBC.decrypt(cek, jweObject, true);
+ }
+
+ @Test
+ public void testDecrypt_ShouldDecryptWithoutVerification_WhenHmacVerificationIsDisabled() throws Exception {
+ // Given: A JWE object (even with wrong HMAC tag)
+ String rawHeader = EncodingUtils.base64UrlEncode("{\"enc\":\"A128CBC-HS256\",\"alg\":\"RSA-OAEP-256\"}".getBytes());
+
+ // Create a simple encrypted payload using AES-CBC
+ byte[] cekBytes = new byte[32]; // 256-bit key
+ SecretKeySpec cek = new SecretKeySpec(cekBytes, "AES");
+
+ // Encrypt some data first
+ byte[] plainText = "test".getBytes(StandardCharsets.UTF_8);
+ byte[] iv = new byte[16]; // Zero IV for testing
+
+ javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
+ javax.crypto.spec.SecretKeySpec aesKey = new javax.crypto.spec.SecretKeySpec(cekBytes, 16, 16, "AES");
+ cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, aesKey, new javax.crypto.spec.IvParameterSpec(iv));
+ byte[] encrypted = cipher.doFinal(plainText);
+
+ String encryptedKey = "dummy_encrypted_key_base64url";
+ String ivB64 = EncodingUtils.base64UrlEncode(iv);
+ String cipherTextB64 = EncodingUtils.base64UrlEncode(encrypted);
+ String authTag = EncodingUtils.base64UrlEncode(new byte[16]); // Wrong tag, but should be ignored
+
+ String jweString = rawHeader + "." + encryptedKey + "." + ivB64 + "." + cipherTextB64 + "." + authTag;
+ JweObject jweObject = JweObject.parse(jweString, JsonEngine.getDefault());
+
+ // When: Decrypt with HMAC verification disabled
+ byte[] result = AESCBC.decrypt(cek, jweObject, false);
+
+ // Then: Should succeed and return decrypted data
+ assertNotNull(result);
+ assertEquals("test", new String(result, StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void testDecrypt_ShouldUseCorrectKeySplit_WhenDecrypting() throws Exception {
+ // Given: A 256-bit CEK that should be split into HMAC key (first 128 bits) and AES key (second 128 bits)
+ byte[] cekBytes = new byte[32];
+ // Fill with a pattern to verify correct split
+ for (int i = 0; i < 16; i++) {
+ cekBytes[i] = (byte) 0xAA; // HMAC key part
+ cekBytes[i + 16] = (byte) 0xBB; // AES key part
+ }
+ SecretKeySpec cek = new SecretKeySpec(cekBytes, "AES");
+
+ // Create encrypted data using only the second half (AES key)
+ byte[] plainText = "testdata".getBytes(StandardCharsets.UTF_8);
+ byte[] iv = new byte[16];
+
+ javax.crypto.spec.SecretKeySpec aesKey = new javax.crypto.spec.SecretKeySpec(cekBytes, 16, 16, "AES");
+ javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
+ cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, aesKey, new javax.crypto.spec.IvParameterSpec(iv));
+ byte[] encrypted = cipher.doFinal(plainText);
+
+ String rawHeader = EncodingUtils.base64UrlEncode("{\"enc\":\"A128CBC-HS256\",\"alg\":\"RSA-OAEP-256\"}".getBytes());
+ String jweString = rawHeader + ".dummy." + EncodingUtils.base64UrlEncode(iv) + "." +
+ EncodingUtils.base64UrlEncode(encrypted) + "." + EncodingUtils.base64UrlEncode(new byte[16]);
+ JweObject jweObject = JweObject.parse(jweString, JsonEngine.getDefault());
+
+ // When: Decrypt without HMAC verification (to avoid tag mismatch)
+ byte[] result = AESCBC.decrypt(cek, jweObject, false);
+
+ // Then: Should correctly use the second half of CEK for decryption
+ assertEquals("testdata", new String(result, StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void testDecrypt_ShouldComputeCorrectHmac_WhenVerificationIsEnabled() throws Exception {
+ // Given: A properly constructed JWE with correct HMAC
+ byte[] cekBytes = new byte[32];
+ java.security.SecureRandom random = new java.security.SecureRandom();
+ random.nextBytes(cekBytes);
+
+ SecretKeySpec cek = new SecretKeySpec(cekBytes, "AES");
+ SecretKeySpec hmacKey = new SecretKeySpec(cekBytes, 0, 16, "HmacSHA256");
+ SecretKeySpec aesKey = new SecretKeySpec(cekBytes, 16, 16, "AES");
+
+ // Encrypt data
+ byte[] plainText = "Hello, World!".getBytes(StandardCharsets.UTF_8);
+ byte[] iv = new byte[16];
+ random.nextBytes(iv);
+
+ javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding");
+ cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, aesKey, new javax.crypto.spec.IvParameterSpec(iv));
+ byte[] cipherText = cipher.doFinal(plainText);
+
+ // Compute proper HMAC
+ String rawHeader = EncodingUtils.base64UrlEncode("{\"enc\":\"A128CBC-HS256\",\"alg\":\"RSA-OAEP-256\"}".getBytes());
+ byte[] aad = rawHeader.getBytes(StandardCharsets.US_ASCII);
+
+ javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256");
+ mac.init(hmacKey);
+ mac.update(aad);
+ mac.update(iv);
+ mac.update(cipherText);
+
+ // Add AL (AAD length in bits as 64-bit big-endian)
+ long aadLengthBits = (long) aad.length * 8;
+ java.nio.ByteBuffer alBuffer = java.nio.ByteBuffer.allocate(8);
+ alBuffer.putLong(aadLengthBits);
+ mac.update(alBuffer.array());
+
+ byte[] hmacOutput = mac.doFinal();
+ byte[] authTag = new byte[16]; // First 16 bytes
+ System.arraycopy(hmacOutput, 0, authTag, 0, 16);
+
+ // Construct JWE
+ String jweString = rawHeader + ".dummy." + EncodingUtils.base64UrlEncode(iv) + "." +
+ EncodingUtils.base64UrlEncode(cipherText) + "." + EncodingUtils.base64UrlEncode(authTag);
+ JweObject jweObject = JweObject.parse(jweString, JsonEngine.getDefault());
+
+ // When: Decrypt with HMAC verification enabled
+ byte[] result = AESCBC.decrypt(cek, jweObject, true);
+
+ // Then: Should succeed and return correct plaintext
+ assertEquals("Hello, World!", new String(result, StandardCharsets.UTF_8));
+ }
+}
+
diff --git a/src/test/java/com/mastercard/developer/encryption/jwe/JWEObjectTest.java b/src/test/java/com/mastercard/developer/encryption/jwe/JWEObjectTest.java
index 45c7bf0..ac449db 100644
--- a/src/test/java/com/mastercard/developer/encryption/jwe/JWEObjectTest.java
+++ b/src/test/java/com/mastercard/developer/encryption/jwe/JWEObjectTest.java
@@ -21,6 +21,26 @@ public void testDecrypt_ShouldReturnDecryptedPayload_WhenPayloadIsCbcEncrypted()
assertEquals("bar", decryptedPayload);
}
+ @Test
+ public void testDecrypt_ShouldReturnDecryptedPayload_WhenPayloadIsCbcEncryptedAndHmacVerificationDisabled() throws Exception {
+ JweObject jweObject = TestUtils.getTestCbcJweObject();
+ String decryptedPayload = jweObject.decrypt(TestUtils.getTestJweConfigBuilder()
+ .withEnableCbcHmacVerification(false)
+ .build());
+
+ assertEquals("bar", decryptedPayload);
+ }
+
+ @Test
+ public void testDecrypt_ShouldReturnDecryptedPayload_WhenPayloadIsCbcEncryptedAndHmacVerificationExplicitlyEnabled() throws Exception {
+ JweObject jweObject = TestUtils.getTestCbcJweObject();
+ String decryptedPayload = jweObject.decrypt(TestUtils.getTestJweConfigBuilder()
+ .withEnableCbcHmacVerification(true)
+ .build());
+
+ assertEquals("bar", decryptedPayload);
+ }
+
private static Stream aesGcmJweObjects() {
return ImmutableList.of(
TestUtils.getTestAes128GcmJweObject(),