Skip to content

Commit

Permalink
Merge pull request #8 from alexguerre/feature/add-feature-cipher-mask…
Browse files Browse the repository at this point in the history
…-json

Feature/add feature cipher mask json
  • Loading branch information
gabheadz authored Jan 5, 2023
2 parents 6a57a79 + 11a509f commit 6fecac9
Show file tree
Hide file tree
Showing 19 changed files with 911 additions and 125 deletions.
114 changes: 75 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
[contributing]: CONTRIBUTING.md
[encryption_context]: https://aws.amazon.com/blogs/security/how-to-protect-the-integrity-of-your-encrypted-data-by-using-aws-key-management-service-and-encryptioncontext/

Utility library to use with Jackson-Databind to provide custom
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.
sensitive data via masking with additional encrypting-decrypting.

Functionality:

Expand All @@ -15,7 +15,7 @@ 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.

```java
public class Customer {

Expand All @@ -32,11 +32,11 @@ Using a customized Object Mapper you can:
```

- Encrypting:

Converting string members of an object into a pair of masked an ecrypted values.

- As an composite String:

```java
public class Customer {

Expand All @@ -55,7 +55,7 @@ Using a customized Object Mapper you can:
```

- Or as Json Object.

```java
public class Customer {

Expand All @@ -80,33 +80,33 @@ Using a customized Object Mapper you can:

- Decrypting: Reverting an encrypted string/json input value to its original plain value.

- Having a JSON with a composite string value:

```java
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 a composite string value:

```java
String json = "{
\"creditCardNumber\": \"masked_pair=***************4444|<credit card encrypted value>\"
}";

- Having a JSON with an Object value:
Customer c = objectMapper.readValue(json, Customer.class);

```java
String json = "{
\"creditCardNumber\": {
\"masked": \"***************4444\",
\"enc": \"<credit card encrypted value>\"
}
}";

Customer c = objectMapper.readValue(json, Customer.class);

assert c.creditCardNumber.equals("1111222233334444");
```
assert c.creditCardNumber.equals("1111222233334444");
```

- Having a JSON with an Object value:

```java
String json = "{
\"creditCardNumber\": {
\"masked": \"***************4444\",
\"enc": \"<credit card encrypted value>\"
}
}";

Customer c = objectMapper.readValue(json, Customer.class);

assert c.creditCardNumber.equals("1111222233334444");
```

## Installing

With Gradle
Expand Down Expand Up @@ -155,7 +155,7 @@ var dummyDecipher = new DataDecipher() {

```

### B. Declare the customized Object Mapper.
### B. Declare the customized Object Mapper.

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.
Expand All @@ -166,9 +166,9 @@ as constructor arguments, the implementations of both `DataCipher` and `DataDeci
}
```

### C. Decorate POJO's
### C. Decorate POJO's

Members to be masked/encrypted should be annotated with `@Mask`, eg:
Members to be masked/encrypted should be annotated with `@Mask`, eg:

```java
@Data
Expand Down Expand Up @@ -245,7 +245,7 @@ String json = mapper.writeValueAsString(customer);

__Deserializing Process__

The deserialization process should construct an instance of the example `Customer`
The deserialization process should construct an instance of the example `Customer`
with is `creditCardNumber`property in plain text.

```java
Expand All @@ -261,6 +261,42 @@ String json = "{\n" +
Customer customer = mapper.readValue(json, Customer.class);
assertEquals("4444555566665678", customer.creditCardNumber());
```

### E. Using library without POJO model

- **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?*

![](https://i.imgur.com/cYmrxtI.png)


- *How to configure specific field for masking from JSON?*

![](https://i.imgur.com/7ZYWmPu.png)


- How to decypher a JSON previously cyphered?

![](https://i.imgur.com/insoc94.png)

**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?

![](https://i.imgur.com/SeiaFYj.png)




# AWS SDK integration

This library offers a concrete implementation for the `DataCipher` and `DataDecipher` interfaces
Expand All @@ -285,7 +321,7 @@ With maven

### Additional configuration

Passed via configuration `application.properties` or `application.yaml`
Passed via configuration `application.properties` or `application.yaml`

| Attribute | Default value | Description |
|---|---|---|
Expand All @@ -296,14 +332,14 @@ Passed via configuration `application.properties` or `application.yaml`

### Use with Spring-Boot

Just declare the customized Object Mapper as a Bean, and add **@Primary** annotation to use instead of the default ObjectMapper.
Just declare the customized Object Mapper as a Bean, and add **@Primary** annotation to use instead of the default ObjectMapper.

```java
@Bean
@Primary
public ObjectMapper objectMapper(DataCipher awsCipher, DataDecipher awsDecipher) {
@Primary
public ObjectMapper objectMapper(DataCipher awsCipher, DataDecipher awsDecipher) {
return new MaskingObjectMapper(awsCipher, awsDecipher);
}
}
```

# Contribute
Expand Down
4 changes: 3 additions & 1 deletion core/src/main/java/co/com/bancolombia/datamask/Mask.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package co.com.bancolombia.datamask;

import co.com.bancolombia.datamask.databind.util.TransformationType;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
Expand All @@ -11,6 +13,6 @@
int leftVisible() default 0;
int rightVisible() default 0;
boolean isEmail() default false;
boolean queryOnly() default true;
TransformationType queryOnly() default TransformationType.ONLY_MASK;
String format() default DataMaskingConstants.ENCRYPTION_INLINE;
}
39 changes: 29 additions & 10 deletions core/src/main/java/co/com/bancolombia/datamask/MaskUtils.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package co.com.bancolombia.datamask;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import org.apache.commons.lang3.StringUtils;

import java.util.Optional;

public class MaskUtils {

private MaskUtils() {
Expand All @@ -17,25 +21,27 @@ public static String mask(String fieldValue, int showFirstDigitCount, int showLa

var leftCount = cleanIntParam(showFirstDigitCount);
var rightCount = cleanIntParam(showLastDigitCount);
if (leftCount == 0 && rightCount == 0) {
rightCount = 4;
int length = fieldValue.length();
int shows = leftCount + rightCount;
int hidden = length-shows;
if(shows >= length || (leftCount == 0 && rightCount == 0)){
return StringUtils.repeat("*",length);
}

String leftPart = StringUtils.left(fieldValue, leftCount);
String maskedPart = StringUtils.repeat("*", fieldValue.length() - (leftCount + rightCount));
String rightPart = StringUtils.right(fieldValue, rightCount);

return leftPart + maskedPart + rightPart;
return StringUtils.overlay(fieldValue,StringUtils.repeat("*",hidden) ,leftCount, leftCount + hidden);
}

public static String maskAsEmail(String fieldValue) {
return maskAsEmail(fieldValue, 2, 2);
}

public static String maskAsEmail(String fieldValue, int leftVisible, int rightVisible) {
String[] parts = fieldValue.split("@");

var leftCount = 2;
var rightCount = 1;

String leftPart = StringUtils.left(parts[0], 2);
String rightPart = StringUtils.right(parts[0], 1);
String leftPart = StringUtils.left(parts[0], leftVisible);
String rightPart = StringUtils.right(parts[0], rightVisible);
String maskedPart = StringUtils.repeat("*", parts[0].length() - (leftCount + rightCount));

return leftPart + maskedPart + rightPart + "@" + parts[1];
Expand All @@ -47,4 +53,17 @@ private static int cleanIntParam(int input) {
else
return input;
}

public static String[] split(String input) {
return input.replace(DataMaskingConstants.MASKING_PREFIX,"")
.split("\\|");
}

public static boolean isEncryptedObject(JsonNode node) {
return Optional.of(node)
.filter(n -> n.getNodeType().equals(JsonNodeType.OBJECT))
.map(n -> n.findValue(DataMaskingConstants.MASKING_ATTR) != null
&& n.findValue(DataMaskingConstants.ENCRYPTED_ATTR) !=null)
.orElse(false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package co.com.bancolombia.datamask.databind.mask;

import lombok.Getter;
import lombok.NonNull;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Getter
public class DataMask<T> {
private final T data;
private final Map<IdentifyField, MaskingFormat> fields;

public DataMask(T data, Map<IdentifyField, MaskingFormat> fields){
this.data = data;
this.fields = fields;
}

public DataMask(T data, @NonNull List<IdentifyField> defaultFields){
this.data = data;
this.fields = defaultFields.stream()
.collect(Collectors.toMap(k -> k, v -> new MaskingFormat()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package co.com.bancolombia.datamask.databind.mask;

import co.com.bancolombia.datamask.databind.util.QueryType;
import lombok.Getter;
import lombok.Setter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class IdentifyField {
private String query;
private QueryType queryType;
}
Loading

0 comments on commit 6fecac9

Please sign in to comment.