josh
josh

Reputation: 14443

JSON PII data masking in Java

I would like to mask certain elements of JSON and print to logs. Masking can be either by substituting by dummy data or removing the key pair .Is there a utility to do the masking in Java ?

E.g.,

given JSON:

{
    "key1":"value1",
    "key2":"value2",
    "key3":"value3",
}

mask key 2 alone and print JSON:

{
    "key1":"value1",
    "key2":"xxxxxx",
    "key3":"value3",
}

or

{
    "key1":"value1",
    "key3":"value3",
}

Upvotes: 2

Views: 16005

Answers (3)

Breus
Breus

Reputation: 468

Recently we released the first release candidate of a high-performance JSON masker library in Java with no runtime dependencies: dev.blaauwendraad:json-masker which you might want to use to do this.

It works on any JSON value type, has an extensive and configurable API, and adds minimal computation overhead both in terms of CPU time and memory allocations. Additionally, it has limited JsonPath support.

In case the masking happens in a flow where performance is critical, or you don't want to have in-house code (depending on the APIs of Jackson) for this, you might want to use this library instead.

The README should contain plenty of examples to show how to use it for your use case. Additionally, all methods part of the API are documented using JavaDoc.

Your specific case to mask each string character with an 'x' and preserve the length is possible like this:

var jsonMasker = JsonMasker.getMasker(
        JsonMaskingConfig.builder()
                .maskKeys(Set.of("key2"))
                .maskStringCharactersWith("x")
                .build()
);

String maskedJson = jsonMasker.mask(json);

Upvotes: 3

Sureshkumar V
Sureshkumar V

Reputation: 31

input will be JSON object or array type in string format. Here the maskable keys only static otherwise input string will dynamic.

public final class MaskPIData {

  /**
   * Mask able keywords mentioned here. It should be in LOWER CASE.
   */
  private static final Set<String> MASKABLE_KEYS = new HashSet<>(Arrays.asList(
    "email",
    "emails",
    "phone",
    "pin",
    "password",
    "phonenumber",
    "moneys"));

  private static final String MASKING_VALUE = "****";

  private static final ObjectMapper OBJECTMAPPER =  new ObjectMapper();

  private MaskPIData() {
    super();
  }

  private static boolean isValidSet(Set<String> set) {
    return set != null && !set.isEmpty();
  }

  private static boolean isKnownPrimitiveWrapperModel(Object obj) {
    return obj == null || obj instanceof String || obj instanceof Integer || obj instanceof Long
      || obj instanceof Double;
  }

  @SuppressWarnings("unchecked")
  private static JSONObject maskingForJsonObject(Set<String> maskableKeys, JSONObject input) {
    if (!isValidSet(maskableKeys) || input == null) {
      return input;
    }

    Map<String, Object> inputMap = (Map<String, Object>) input;
    Map<String, Object> caseInsensitiveInputMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    caseInsensitiveInputMap.putAll(inputMap);

    for (Map.Entry<String, Object> entryPair : caseInsensitiveInputMap.entrySet()) {
      if (entryPair.getValue() instanceof JSONArray) {
        JSONArray jsonArr = (JSONArray) caseInsensitiveInputMap.get(entryPair.getKey());
        maskingForArray(maskableKeys, entryPair.getKey(), jsonArr);
        caseInsensitiveInputMap.put(entryPair.getKey(), jsonArr);
      } else if (entryPair.getValue() instanceof JSONObject) {
        JSONObject jsonObj = (JSONObject) caseInsensitiveInputMap.get(entryPair.getKey());
        caseInsensitiveInputMap.put(entryPair.getKey(), maskingForJsonObject(maskableKeys, jsonObj));
      } else if (entryPair.getKey() != null && maskableKeys.contains(entryPair.getKey().toLowerCase())) {
        caseInsensitiveInputMap.put(entryPair.getKey(), MASKING_VALUE);
      }
    }

    return OBJECTMAPPER.convertValue(caseInsensitiveInputMap, JSONObject.class);
  }

  @SuppressWarnings("unchecked")
  private static JSONArray maskingForArray(Set<String> maskableKeys, String key,
    JSONArray jsonArr) {
    JSONArray toRet = jsonArr;
    for (int idx = 0; idx < toRet.size(); idx++) {
      Object obj = toRet.get(idx);
      if (isKnownPrimitiveWrapperModel(obj)) {
        if (key != null && maskableKeys.contains(key.toLowerCase())) {
          toRet.remove(idx);
          toRet.add(idx, MASKING_VALUE);
        }
      } else {
        JSONObject jsonObjFromArray = (JSONObject) toRet.get(idx);
        JSONObject maskedJsonObj = maskingForJsonObject(maskableKeys, jsonObjFromArray);
        toRet.remove(idx);
        toRet.add(idx, maskedJsonObj);
      }
    }
    return toRet;
  }

  public static String doMask(String input) {
    String maskedData = input;
    if (maskedData != null && !maskedData.trim().isEmpty()) {
      try {
        if (new JSONParser().parse(maskedData) instanceof JSONObject) {
          JSONObject maskedOutput = maskingForJsonObject(MASKABLE_KEYS,
            (JSONObject) new JSONParser().parse(maskedData));
          maskedData = OBJECTMAPPER.writeValueAsString(maskedOutput);
        } else if (new JSONParser().parse(maskedData) instanceof JSONArray) {
          JSONArray maskedOutput = maskingForArray(MASKABLE_KEYS, null, (JSONArray) new JSONParser().parse(maskedData));
          maskedData = OBJECTMAPPER.writeValueAsString(maskedOutput);
        }
      } catch (Exception e) {
        // to do - Error while masking data
      }
    }
    return maskedData;
  }

  public static void main(String args[]) {
    String input = "{\"item\":{\"test\":\"test\",\"phone\":\"993244\",\"email\":\"[email protected]\"}}";
    System.out.println(doMask(input));
  }

Upvotes: 3

Oleg
Oleg

Reputation: 453

You could use jackson to convert json to map, process map and convert map back to json.

For example:

import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.type.TypeReference;

public void mask() throws IOException {
String jsonString = "{\n" +
            "    \"key1\":\"value1\",\n" +
            "    \"key2\":\"value2\",\n" +
            "    \"key3\":\"value3\"\n" +
            "}";
    Map<String, Object> map;    

    // Convert json to map
    ObjectMapper mapper = new ObjectMapper();
    try {
        TypeReference ref = new TypeReference<Map<String, Object>>() { };
        map = mapper.readValue(jsonString, ref);
    } catch (IOException e) {
        System.out.print("cannot create Map from json" + e.getMessage());
        throw e;
    }

    // Process map
    if(map.containsKey("key2")) {
        map.put("key2","xxxxxxxxx");
    }

    // Convert back map to json
    String jsonResult = "";
    try {
        jsonResult = mapper.writeValueAsString(map);
    } catch (IOException e) {
        System.out.print("cannot create json from Map" + e.getMessage());
    }

    System.out.print(jsonResult);

Upvotes: 2

Related Questions