Utility library to use with Jackson-Databind to provide custom POJO/JSON serialization and deserialization aiming to protect sensitive data via masking with additional encrypting-decrypting.
Functionality:
Using a customized Object Mapper you can:
-
Perform Masking on string members of an object
-
Scenario: masking a credit card number when serializing to json.
public class Customer { @Mask(rightVisible=4) public String creditCardNumber; } Customer c = new Customer(); c.creditCardNumber = "1111222233334444"; String json = objectMapper.writeValueAsString(c); assert json.equals("{ \"creditCardNumber\": \"***************4444\"}")
-
-
Encrypting:
Converting string members of an object into a pair of masked an ecrypted values.
-
As an composite String:
public class Customer { @Mask(rightVisible=4, queryOnly=false) public String creditCardNumber; } Customer c = new Customer(); c.creditCardNumber = "1111222233334444"; String json = objectMapper.writeValueAsString(c); assert json.equals("{ \"creditCardNumber\": \"masked_pair=***************4444|<credit card encrypted value>\" }")
-
Or as Json Object.
public class Customer { @Mask(rightVisible=4, queryOnly=false, format=DataMaskingConstants.ENCRYPTION_AS_OBJECT) public String creditCardNumber; } Customer c = new Customer(); c.creditCardNumber = "1111222233334444"; String json = objectMapper.writeValueAsString(c); assert json.equals("{ \"creditCardNumber\": { \"masked\": \"***************4444\", \"enc\": \"<credit card encrypted value>\" } }")
-
-
Decrypting: Reverting an encrypted string/json input value to its original plain value.
-
Having a JSON with a composite string value:
String json = "{ \"creditCardNumber\": \"masked_pair=***************4444|<credit card encrypted value>\" }"; Customer c = objectMapper.readValue(json, Customer.class); assert c.creditCardNumber.equals("1111222233334444");
-
Having a JSON with an Object value:
String json = "{ \"creditCardNumber\": { \"masked": \"***************4444\", \"enc": \"<credit card encrypted value>\" } }"; Customer c = objectMapper.readValue(json, Customer.class); assert c.creditCardNumber.equals("1111222233334444");
-
With Gradle
implementation 'com.github.bancolombia:data-mask-core:1.2.0'
With maven
<dependency>
<groupId>com.github.bancolombia</groupId>
<artifactId>data-mask-core</artifactId>
<version>1.2.0</version>
</dependency>
This library depends on:
org.apache.commons:commons-lang3
com.fasterxml.jackson.core:jackson-databind
This library defines two interfaces: DataCipher
and DataDecipher
which are used in the encryption/decryption
processes.
User of this library must define implementation for both interfaces.
Dummy Example:
var dummyCipher = new DataCipher() {
@Override
public String cipher(String plainData) {
return "the encrypted value";
}
};
var dummyDecipher = new DataDecipher() {
@Override
public String decipher(String encryptedData) {
return "the plain value";
}
};
This library defines a custom ObjectMapper
in order to provide the masking and unmasking functionality, and takes
as constructor arguments, the implementations of both DataCipher
and DataDecipher
interfaces.
public ObjectMapper objectMapper(DataCipher someCipherImpl, DataDecipher someDecipherImpl) {
return new MaskingObjectMapper(someCipherImpl, someDecipherImpl);
}
Members to be masked/encrypted should be annotated with @Mask
, eg:
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Customer {
private String name;
@Mask(leftVisible = 3, rightVisible=4)
private String email;
@Mask(rightVisible=4, queryOnly=false, format=DataMaskingConstants.ENCRYPTION_AS_OBJECT)
private String creditCardNumber;
}
Anotation Properties
Attribute | Default value | Description |
---|---|---|
leftVisible | 0 | Masking: how many characters should remain visible on left. Example: Hello****** |
rightVisible | 4 | Masking: how many characters should remain visible on right Example: *****World |
queryOnly | true | true : Serialization should generate masked value only. |
false : serialization should generate masked value and encrypted value. |
||
format | ENCRYPTION_AS_OBJECT | Describes how masked and encrypted data should be serialized. Using ENCRYPTION_INLINE means masked and encrypted values together are serialized as string: masked_pair=<masked_value>I<encrypted_value> |
Using ENCRYPTION_AS_OBJECT , means masked and encrypted values are serialized as a json object. |
||
isMultiMask | false | true : Enhanced the data masking capability to be dynamic using the separator property to identify each word separately and allowing masking each one. Example: H**** w****false : The dynamic data masking is not allowed and the behavior will be as before Example: Hello******. optional |
separator | " " | With this property you can define the divisor character to use the multimask capability optional |
Use the custom ObjectMapper
, so, having this example of annotated class:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Customer {
private String name;
@Mask(leftVisible = 3, rightVisible=4)
private String email;
@Mask(rightVisible=4, queryOnly=false, format=DataMaskingConstants.ENCRYPTION_AS_OBJECT)
private String creditCardNumber;
}
Serializing Process
An instance of example Customer
annotated class:
Customer customer = new Customer("Jhon Doe",
"[email protected]",
"4444555566665678");
Should be serialized as JSON like this:
String json = mapper.writeValueAsString(customer);
{
"name": "Jhon Doe",
"email": "Jho**************.com",
"creditCardNumber": {
"masked": "************5678",
"enc": "dGhpcyBzaG91bGQgYmUgYW4gZWNyeXB0ZWQgdmFsdWUK"
}
}
Deserializing Process
The deserialization process should construct an instance of the example Customer
with is creditCardNumber
property in plain text.
String json = "{\n" +
"\"name\": \"Jhon Doe\",\n" +
"\"email\": \"Jho**************.com\",\n" +
"\"creditCardNumber\": {\n" +
"\"masked\": \"************5678\",\n" +
"\"enc\": \"dGhpcyBzaG91bGQgYmUgYW4gZWNyeXB0ZWQgdmFsdWUK\"\n" +
"}\n" +
"}";
Customer customer = mapper.readValue(json, Customer.class);
assertEquals("4444555566665678", customer.creditCardNumber());
- Transformation of Json
The library offers a funtionability for transform JSON without a model known. The transformations supported are Cyphering, Decyphering and Masking.
DISCLAIMER: This require high computer resources because it looping over JSON searching specific fields that we configurate.
- How to configure specific field for cypher from JSON?
- How to configure specific field for masking from JSON?
- How to decypher a JSON previously cyphered?
1) Transforming obj(Any Json) with specific configuration explained in previos answers
2) Getting original obj previosly transformed
- Can I use cyphering and masking in only one search?
This library offers a concrete implementation for the DataCipher
and DataDecipher
interfaces
called data-mask-aws
which provides via the Aws crypto SDK and Secrets Manager for
the encryption and decryption functionality.
Cloud Dependencies:
- Secrets Manager for storing the symmetric key to configure the local AWS Crypto SDK
With Gradle
implementation 'com.github.bancolombia:data-mask-aws:1.2.0'
With maven
<dependency>
<groupId>com.github.bancolombia</groupId>
<artifactId>data-mask-aws</artifactId>
<version>1.2.0</version>
</dependency>
Passed via configuration application.properties
or application.yaml
Attribute | Default value | Description |
---|---|---|
secrets.dataMaskKey | Name of the secret that holds the symmetric key in AWS Secrets manager. Encryption key for data-masking should be at least 16 bytes. Other lengths supported for AES encryption are 24 and 32 bytes. Any given key greater than 16 bytes, and not multiple of 16, will be derived from first 16 bytes. |
|
dataMask.encryptionContext | "default_context" | (Optional) The context for additional protection of the encrypted data. See Usage of Encryption contexts. |
dataMask.keyId | [blank] | (Optional) The key Id |
adapters.aws.secrets-manager.region | Region for the Secrets Manager service | |
adapters.aws.secrets-manager.endpoint | (Optional) for local dev only |
Just declare the customized Object Mapper as a Bean, and add @Primary annotation to use instead of the default ObjectMapper.
@Bean
@Primary
public ObjectMapper objectMapper(DataCipher awsCipher, DataDecipher awsDecipher) {
return new MaskingObjectMapper(awsCipher, awsDecipher);
}
Then is all the same as described earlier in this guide in section C. Decorate-POJOs
Please read our Code of conduct and Contributing Guide.