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.
| Version | Spring Boot | Jackson | Java |
|---|---|---|---|
| 2.x.x | 4.x.x | 3.x | 17+ |
| 1.x.x | 3.x.x | 2.x | 17+ |
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:<version>'With maven
<dependency>
<groupId>com.github.bancolombia</groupId>
<artifactId>data-mask-core</artifactId>
<version>2.0.1</version>
</dependency>
This library depends on:
org.apache.commons:commons-lang3tools.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",
"jhon.doe123@someservice.com",
"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 creditCardNumberproperty 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 |
Use versions 1.x.x of this library.
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
You also can use versions 2.x.x of this library. But you need to include the dependency of jackson 3 and use directly ObjectMapper from jackson 3.
implementation 'tools.jackson.core:jackson-databind:3.0.3'tools.jackson.databind.ObjectMapper mapper = new MaskingObjectMapper(awsCipher, awsDecipher);
String maskedJson = mapper.writeValueAsString(model);Use versions 2.x.x of this library.
Just declare the customized JsonMapper as a Bean, and add @Primary annotation to use instead of the default JsonMapper.
@Bean
@Primary
public JsonMapper jsonMapper(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.



