Skip to content

Commit 0184984

Browse files
committed
Added support for array encryption/decryption, added ability to decrypt data in the root JSON object
1 parent 58b5c2c commit 0184984

File tree

2 files changed

+147
-9
lines changed

2 files changed

+147
-9
lines changed

src/main/java/com/mastercard/developer/encryption/FieldLevelEncryption.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,19 +162,28 @@ private static void decryptPayloadPath(DocumentContext payloadContext, String js
162162

163163
// Read and remove encrypted data and encryption fields at the given JSON path
164164
JsonElement encryptedValueJsonElement = inJsonObject.remove(config.encryptedValueFieldName);
165+
if (encryptedValueJsonElement == null) {
166+
// Nothing to decrypt
167+
return;
168+
}
169+
payloadContext.delete(jsonPathIn + "." + config.encryptedValueFieldName);
165170
JsonElement encryptedKeyJsonElement = inJsonObject.remove(config.encryptedKeyFieldName);
171+
payloadContext.delete(jsonPathIn + "." + config.encryptedKeyFieldName);
166172
JsonElement ivJsonElement = inJsonObject.remove(config.ivFieldName);
173+
payloadContext.delete(jsonPathIn + "." + config.ivFieldName);
167174
JsonElement oaepDigestAlgorithmJsonElement = null;
168175
if (config.oaepPaddingDigestAlgorithmFieldName != null) {
169176
oaepDigestAlgorithmJsonElement = inJsonObject.remove(config.oaepPaddingDigestAlgorithmFieldName);
177+
payloadContext.delete(jsonPathIn + "." + config.oaepPaddingDigestAlgorithmFieldName);
170178
}
171179
if (config.encryptionCertificateFingerprintFieldName != null) {
172180
inJsonObject.remove(config.encryptionCertificateFingerprintFieldName);
181+
payloadContext.delete(jsonPathIn + "." + config.encryptionCertificateFingerprintFieldName);
173182
}
174183
if (config.encryptionKeyFingerprintFieldName != null) {
175184
inJsonObject.remove(config.encryptionKeyFingerprintFieldName);
185+
payloadContext.delete(jsonPathIn + "." + config.encryptionKeyFingerprintFieldName);
176186
}
177-
payloadContext.set(jsonPathIn, inJsonObject);
178187

179188
// Decrypt the AES secret key
180189
byte[] encryptedSecretKeyBytes = decodeValue(encryptedKeyJsonElement.getAsString(), config.fieldValueEncoding);
@@ -193,22 +202,21 @@ private static void decryptPayloadPath(DocumentContext payloadContext, String js
193202
String decryptedValue = new String(decryptedValueBytes, StandardCharsets.UTF_8);
194203
decryptedValue = sanitizeJson(decryptedValue);
195204
JsonElement decryptedValueJsonElement = new Gson().fromJson(decryptedValue, JsonElement.class);
196-
JsonObject outJsonObject = readOrCreateOutObject(payloadContext, jsonPathOut);
205+
readOrCreateOutObject(payloadContext, jsonPathOut);
197206
if (!decryptedValueJsonElement.isJsonObject()) {
198-
// Primitive type: overwrite
207+
// Primitive or array: overwrite
199208
payloadContext.set(jsonPathOut, decryptedValueJsonElement);
200209
} else {
201210
// Object: merge
202211
for (Entry<String, JsonElement> entry : ((JsonObject)decryptedValueJsonElement).entrySet()) {
203-
outJsonObject.remove(entry.getKey());
204-
outJsonObject.add(entry.getKey(), entry.getValue());
212+
payloadContext.delete(jsonPathOut + "." + entry.getKey());
213+
payloadContext.put(jsonPathOut, entry.getKey(), entry.getValue());
205214
}
206-
payloadContext.set(jsonPathOut, outJsonObject);
207215
}
208216

209217
// Remove the input object if now empty
210218
inJsonObject = readJsonObject(payloadContext, jsonPathIn, jsonPathConfig);
211-
if (inJsonObject != null && inJsonObject.keySet().isEmpty()) {
219+
if (inJsonObject != null && inJsonObject.keySet().isEmpty() && !"$".equals(jsonPathIn)) {
212220
payloadContext.delete(jsonPathIn);
213221
}
214222
}

src/test/java/com/mastercard/developer/encryption/FieldLevelEncryptionTest.java

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,31 @@ public void testEncryptPayload_ShouldEncryptPrimitiveTypes() throws Exception {
9595
// THEN
9696
JsonObject encryptedPayloadObject = new Gson().fromJson(encryptedPayload, JsonObject.class);
9797
assertNull(encryptedPayloadObject.get("data"));
98-
JsonObject encryptedData = (JsonObject) encryptedPayloadObject.get("encryptedData");
99-
assertNotNull(encryptedData);
98+
assertNotNull(encryptedPayloadObject.get("encryptedData"));
99+
}
100+
101+
@Test
102+
public void testEncryptPayload_ShouldEncryptArrays() throws Exception {
103+
104+
// GIVEN
105+
String payload = "{" +
106+
" \"items\": [" +
107+
" {}," +
108+
" {}" +
109+
" ]" +
110+
"}";
111+
FieldLevelEncryptionConfig config = getTestFieldLevelEncryptionConfigBuilder()
112+
.withEncryptionPath("items", "encryptedItems")
113+
.withOaepPaddingDigestAlgorithm("SHA-256")
114+
.build();
115+
116+
// WHEN
117+
String encryptedPayload = FieldLevelEncryption.encryptPayload(payload, config);
118+
119+
// THEN
120+
JsonObject encryptedPayloadObject = new Gson().fromJson(encryptedPayload, JsonObject.class);
121+
assertNull(encryptedPayloadObject.get("items"));
122+
assertNotNull(encryptedPayloadObject.get("encryptedItems"));
100123
}
101124

102125
@Test
@@ -438,6 +461,32 @@ public void testDecryptPayload_ShouldDecryptPrimitiveTypes() throws Exception {
438461
assertEquals("\"string\"", payloadObject.get("data").toString());
439462
}
440463

464+
@Test
465+
public void testDecryptPayload_ShouldDecryptArrays() throws Exception {
466+
467+
// GIVEN
468+
String encryptedPayload = "{" +
469+
" \"encryptedItems\": {" +
470+
" \"iv\": \"34010a3ea7231126a0d1e088ec8db173\"," +
471+
" \"encryptedKey\": \"072aee9f7dd6cf381eb61e6d93c2e19e4032e1166d36d3ccb32ec379815f472e27d82a0de48617ff440d37a534bb38b170cf236a78148a375971e83b087eb7d05807863e70b43baa446934fe6f70150e3ca4e49e70fecabb1969c1fc5a38f13a75e318077760e4fe53e25ca011781d1038d19bb3a16928d35302bc7e389c8fb089230b8c0acc3c7e59c120cfe3aece6ff346aaa598a2baf003026f0a32307af022b9515fea564bb5d491b0159b20d909deb9cb5e8077d6471ad1ad3d7e743d6c3cf09f999c22006038980268b9d0cac1fd2e53b1a6e8e4d63b0a3e4457ff27ffab7cd025011b678e0ff56537c29e81ed087fe11988c2c92a7c7695f1fc6f856a\"," +
472+
" \"encryptedValue\": \"d91268566c92621d394b5e5d94069387\"," +
473+
" \"oaepHashingAlgorithm\": \"SHA256\"" +
474+
" }" +
475+
"}";
476+
FieldLevelEncryptionConfig config = getTestFieldLevelEncryptionConfigBuilder()
477+
.withDecryptionPath("$.encryptedItems", "$.items")
478+
.build();
479+
480+
// WHEN
481+
String payload = FieldLevelEncryption.decryptPayload(encryptedPayload, config);
482+
483+
// THEN
484+
JsonObject payloadObject = new Gson().fromJson(payload, JsonObject.class);
485+
assertNull(payloadObject.get("encryptedItems"));
486+
assertNotNull(payloadObject.get("items"));
487+
assertEquals(2, payloadObject.get("items").getAsJsonArray().size());
488+
}
489+
441490
@Test
442491
public void testDecryptPayload_ShouldSupportBase64FieldValueDecoding() throws Exception {
443492

@@ -483,6 +532,31 @@ public void testDecryptPayload_ShouldDoNothing_WhenInPathDoesNotExistInPayload()
483532
assertEquals("{\"data\":{}}", payload);
484533
}
485534

535+
@Test
536+
public void testDecryptPayload_ShouldDoNothing_WhenEncryptedValueDoesNotExistInPayload() throws Exception {
537+
538+
// GIVEN
539+
String encryptedPayload = "{" +
540+
" \"encryptedData\": {" +
541+
" \"iv\": \"ba574b07248f63756bce778f8a115819\"," +
542+
" \"encryptedKey\": \"26687f6d03d27145451d20bdaa29cc199e2533bb9eb7351772e31d1290b98380b43dbf47b9a337cc2ecaff9d3d9fb45305950f13382c5ad822ee6df79e1a57b14a3c58c71090121994a9f771ef96472669671718b55a0fa8d9f76de9e172fedcabbc87d64b5a994899e43abb19afa840269012c397b5b18d4babc0e41c1ad698db98c89121bbe5b2d227cfc5d3c3c87f4f4c8b04b509d326199b39adfbd8bca8bf0a150fcf3c37b9717382af502ad8d4d28b17b91762bf108d34aba0fb40ca410c2ecaeb30d68003af20dce27d9d034e4c557b8104e85f859de0eb709b23f9978869bae545c7f1b62173887eae9e75e4b6d6b4b01d7172ccc8c5774c0db51c24\"," +
543+
" \"oaepHashingAlgorithm\": \"SHA256\"" +
544+
" }" +
545+
"}";
546+
FieldLevelEncryptionConfig config = getTestFieldLevelEncryptionConfigBuilder()
547+
.withDecryptionPath("encryptedData", "data")
548+
.withOaepPaddingDigestAlgorithm("SHA-256")
549+
.build();
550+
551+
// WHEN
552+
String payload = FieldLevelEncryption.decryptPayload(encryptedPayload, config);
553+
554+
// THEN
555+
JsonObject payloadObject = new Gson().fromJson(payload, JsonObject.class);
556+
assertNotNull(payloadObject.get("encryptedData"));
557+
assertNotNull(payloadObject.get("encryptedData").getAsJsonObject().get("iv"));
558+
}
559+
486560
@Test
487561
public void testDecryptPayload_ShouldCreateDataFields_WhenOutPathParentExistsInPayload() throws Exception {
488562

@@ -714,6 +788,62 @@ public void testDecryptPayload_ShouldOverwriteInputObject_WhenOutPathSameAsInPat
714788
assertEquals("\"field2Value\"", payloadObject.get("encryptedData").getAsJsonObject().get("field2").toString());
715789
}
716790

791+
@Test
792+
public void testDecryptPayload_ShouldSupportRootAsInputPath() throws Exception {
793+
794+
// GIVEN
795+
String encryptedPayload = "{" +
796+
" \"iv\": \"17492f69d92d2008ee9289cf3e07bd36\"," +
797+
" \"encryptedKey\": \"22b3df5e70777cef394c39ac74bacfcdbfc8cef4a4da771f1d07611f18b4dc9eacde7297870acb421abe77b8b974f53b2e5b834a68e11a4ddab53ece2d37ae7dee5646dc3f4c5c17166906258615b9c7c52f7242a1afa6edf24815c3dbec4b2092a027de11bcdab4c47de0159ce76d2449394f962a07196a5a5b41678a085d77730baee0d3d0e486eb4719aae8f1f1c0fd7026aea7b0872c049e8df1e7eed088fa84fc613602e989fa4e7a7b77ac40da212a462ae5d3df5078be96fcf3d0fe612e0ec401d27a243c0df1feb8241d49248697db5ec79571b9d52386064ee3db11d200156bfd3af03a289ea37ec2c8f315840e7804669a855bf9e34190e3b14d28\"," +
798+
" \"encryptedValue\": \"9cad34c0d7b2443f07bb7b7e19817ade132ba3f312b1176c09a312e5b5f908198e1e0cfac0fd8c9f66c70a9b05b1a701\"," +
799+
" \"oaepHashingAlgorithm\": \"SHA256\"" +
800+
"}";
801+
FieldLevelEncryptionConfig config = getTestFieldLevelEncryptionConfigBuilder()
802+
.withDecryptionPath("$", "$.encryptedData")
803+
.withOaepPaddingDigestAlgorithm("SHA-256")
804+
.build();
805+
806+
// WHEN
807+
String payload = FieldLevelEncryption.decryptPayload(encryptedPayload, config);
808+
809+
// THEN
810+
JsonObject payloadObject = new Gson().fromJson(payload, JsonObject.class);
811+
assertNull(payloadObject.get("iv"));
812+
assertNull(payloadObject.get("encryptedKey"));
813+
assertNull(payloadObject.get("encryptedValue"));
814+
assertNull(payloadObject.get("oaepHashingAlgorithm"));
815+
assertEquals("\"field1Value\"", payloadObject.get("encryptedData").getAsJsonObject().get("field1").toString());
816+
assertEquals("\"field2Value\"", payloadObject.get("encryptedData").getAsJsonObject().get("field2").toString());
817+
}
818+
819+
@Test
820+
public void testDecryptPayload_ShouldSupportRootAsInputPathAndOutputPath() throws Exception {
821+
822+
// GIVEN
823+
String encryptedPayload = "{" +
824+
" \"iv\": \"17492f69d92d2008ee9289cf3e07bd36\"," +
825+
" \"encryptedKey\": \"22b3df5e70777cef394c39ac74bacfcdbfc8cef4a4da771f1d07611f18b4dc9eacde7297870acb421abe77b8b974f53b2e5b834a68e11a4ddab53ece2d37ae7dee5646dc3f4c5c17166906258615b9c7c52f7242a1afa6edf24815c3dbec4b2092a027de11bcdab4c47de0159ce76d2449394f962a07196a5a5b41678a085d77730baee0d3d0e486eb4719aae8f1f1c0fd7026aea7b0872c049e8df1e7eed088fa84fc613602e989fa4e7a7b77ac40da212a462ae5d3df5078be96fcf3d0fe612e0ec401d27a243c0df1feb8241d49248697db5ec79571b9d52386064ee3db11d200156bfd3af03a289ea37ec2c8f315840e7804669a855bf9e34190e3b14d28\"," +
826+
" \"encryptedValue\": \"9cad34c0d7b2443f07bb7b7e19817ade132ba3f312b1176c09a312e5b5f908198e1e0cfac0fd8c9f66c70a9b05b1a701\"," +
827+
" \"oaepHashingAlgorithm\": \"SHA256\"" +
828+
"}";
829+
FieldLevelEncryptionConfig config = getTestFieldLevelEncryptionConfigBuilder()
830+
.withDecryptionPath("$", "$")
831+
.withOaepPaddingDigestAlgorithm("SHA-256")
832+
.build();
833+
834+
// WHEN
835+
String payload = FieldLevelEncryption.decryptPayload(encryptedPayload, config);
836+
837+
// THEN
838+
JsonObject payloadObject = new Gson().fromJson(payload, JsonObject.class);
839+
assertNull(payloadObject.get("iv"));
840+
assertNull(payloadObject.get("encryptedKey"));
841+
assertNull(payloadObject.get("encryptedValue"));
842+
assertNull(payloadObject.get("oaepHashingAlgorithm"));
843+
assertEquals("\"field1Value\"", payloadObject.get("field1").toString());
844+
assertEquals("\"field2Value\"", payloadObject.get("field2").toString());
845+
}
846+
717847
@Test
718848
public void testDecryptPayload_ShouldThrowEncryptionException_WhenDecryptionErrorOccurs() throws Exception {
719849

0 commit comments

Comments
 (0)