De-identifying sensitive data in text content

The Cloud Data Loss Prevention (DLP) API can de-identify sensitive data in text content, including text stored in container structures such as tables. De-identification is the process of removing identifying information from data. The API detects sensitive data such as personally identifiable information (PII), and then uses a de-identification transformation to mask, delete, or otherwise obscure the data. For example, de-identification techniques can include any of the following:

  • "Masking" sensitive data by partially or fully replacing characters with a symbol, such as an asterisk (*) or hash (#).
  • Replacing each instance of sensitive data with a "token," or surrogate, string.
  • Encrypting and replacing sensitive data using a randomly generated or pre-determined key.

You can feed information to the API using JSON over HTTP, as well as the CLI and several programming languages using the DLP client libraries. To set up the CLI, refer to the quickstart. For more information about submitting information in JSON format, see the JSON quickstart.

To de-identify sensitive data, use the DLP API’s content.deidentify method. It takes the following as arguments:

  • One or more text strings or table structures (ContentItem objects within an items[] array argument) for the API to inspect.
  • A deidentifyConfig argument, which specifies de-identification configuration information (DeidentifyConfig). This argument is covered in more detail in the following section.
  • You can also include an inspectConfig argument to specify detection configuration information (InspectConfig) such as what types of data (or “infoTypes”—for example, phone numbers) to look for and whether to filter findings above a certain likelihood threshold.

The API returns the same items you gave it, in the same format, but any text identified as containing sensitive information according to your criteria has been de-identified.

Transformations

To use the de-identification feature effectively, you must specify one or more transformations. There are two categories of transformations:

  • InfoTypeTransformations: Transformations that are only applied to values within submitted text that are identified as a specific infoType.
  • RecordTransformations: Transformations that are only applied to values within submitted tabular text data that are identified as a specific infoType, or on an entire column of tabular data.

InfoType Transformations

You can specify one or more infoType transformations per request. Within each InfoTypeTransformation object, you specify both of the following:

  • One or more infoTypes to which a transformation should be applied (the infoTypes[] array object).
  • A primitive transformation (the PrimitiveTransformation object). Note that specifying an infoType is optional, and if not specified, the API will match all available infoTypes.

Primitive Transformations

You must specify at least one primitive transformation to apply to input text, regardless of whether you're applying it only to certain infoTypes or to the entire text string. You have several transformation options, which are summarized here:

Transformation Object Description
Replacement ReplaceValueConfig Replaces each input value with a given value.
Redaction RedactConfig Redacts a value by removing it.
Mask with character CharacterMaskConfig Masks a string either fully or partially by replacing a given number of characters with a specified fixed character.
Pseudonymization by replacing input value with cryptographic hash CryptoHashConfig Replaces input values with a 32-byte hexadecimal string generated using a given data encryption key.
Obfuscation of dates DateShiftConfig Shifts dates by a random number of days, with the option to be consistent for the same context.
Pseudonymization by replacing with cryptographic format preserving token CryptoReplaceFfxFpeConfig Replaces an input value with a “token,” or surrogate value, of the same length using format-preserving encryption (FPE) with the FFX mode of operation.
Bucket values based on fixed size ranges FixedSizeBucketingConfig Masks input values by replacing them with “buckets,” or ranges within which the input value falls.
Bucket values based on custom size ranges BucketingConfig Buckets input values based on user-configurable ranges and replacement values.
Replace with infoType ReplaceWithInfoTypeConfig Replaces an input value with the name of its infoType.
Extract time data TimePartConfig Extracts or preserves a portion of Date, Timestamp, and TimeOfDay values.
replaceConfig

Setting replaceConfig to a ReplaceValueConfig object replaces matched input values with a value you specify.

For example, suppose you’ve set replaceConfig to "<phone number>" for all PHONE_NUMBER infoTypes, and the following string is sent to the DLP API:

John Smith, 123 Main St, Seattle, WA 98122, 206-555-0123.

The returned string will be the following:

John Smith, 123 Main St, Seattle, WA 98122, <phone number>.

redactConfig

Specifying redactConfig redacts a given value by removing it completely. The redactConfig message has no arguments; specifying it enables its transformation.

For example, suppose you’ve specified redactConfig for all PHONE_NUMBER infoTypes, and the following string is sent to the DLP API:

John Smith, 123 Main St, Seattle, WA 98122, 206-555-0123.

The returned string will be the following:

John Smith, 123 Main St, Seattle, WA 98122, .
characterMaskConfig

Setting characterMaskConfig to a CharacterMaskConfig object partially masks a string by replacing a given number of characters with a fixed character. Masking can start from the beginning or end of the string. This transformation also works with number types such as long integers.

The CharacterMaskConfig object has several of its own arguments:

  • maskingCharacter: The character to use to mask each character of a sensitive value. For example, you could specify an asterisk (*) or hash (#) to mask a series of numbers such as those in a credit card number.
  • numberToMask: The number of characters to mask. If you don’t set this value, all matching characters will be masked.
  • reverseOrder: Whether to mask characters in reverse order. Setting reverseOrder to true causes characters in matched values to be masked from the end toward the beginning of the value. Setting it to false causes masking to begin at the start of the value.
  • charactersToIgnore[]: One or more characters to skip when masking values. For example, specify a hyphen here to leave the hyphens in place when masking a telephone number. You can also specify a group of common characters (CharsToIgnore) to ignore when masking.

For example, suppose you've set characterMaskConfig to the following values for all PHONE_NUMBER infoTypes:

{
  "maskingCharacter": "#",
  "numberToMask": 5,
  "reverseOrder": true,
  "charactersToIgnore": [
    {
      "charactersToSkip":"-"
    }
  ],
}

Then you send the following string to the DLP API:

John Smith, 123 Main St, Seattle, WA 98122, 206-555-0123.

The returned string will be the following:

John Smith, 123 Main St, Seattle, WA 98122, 206-55#-####.

Following is sample code in several languages that demonstrates how to use the DLP API to de-identify sensitive data using masking techniques.

Running this code requires a client library to be installed. For more information about installing and creating a DLP API client, see DLP API client libraries.

Java

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

  /**
   * Deidentify a string by masking sensitive information with a character using the DLP API.
   *
   * @param string The string to deidentify.
   * @param maskingCharacter (Optional) The character to mask sensitive data with.
   * @param numberToMask (Optional) The number of characters' worth of sensitive data to mask.
   *     Omitting this value or setting it to 0 masks all sensitive chars.
   * @param projectId ID of Google Cloud project to run the API under.
   */
  private static void deIdentifyWithMask(
      String string, Character maskingCharacter, int numberToMask, String projectId) {

    // instantiate a client
    try (DlpServiceClient dlpServiceClient = DlpServiceClient.create()) {

      ContentItem contentItem = ContentItem.newBuilder().setValue(string).build();

      CharacterMaskConfig characterMaskConfig =
          CharacterMaskConfig.newBuilder()
              .setMaskingCharacter(maskingCharacter.toString())
              .setNumberToMask(numberToMask)
              .build();

      // Create the deidentification transformation configuration
      PrimitiveTransformation primitiveTransformation =
          PrimitiveTransformation.newBuilder().setCharacterMaskConfig(characterMaskConfig).build();

      InfoTypeTransformation infoTypeTransformationObject =
          InfoTypeTransformation.newBuilder()
              .setPrimitiveTransformation(primitiveTransformation)
              .build();

      InfoTypeTransformations infoTypeTransformationArray =
          InfoTypeTransformations.newBuilder()
              .addTransformations(infoTypeTransformationObject)
              .build();

      DeidentifyConfig deidentifyConfig =
          DeidentifyConfig.newBuilder()
              .setInfoTypeTransformations(infoTypeTransformationArray)
              .build();

      // Create the deidentification request object
      DeidentifyContentRequest request =
          DeidentifyContentRequest.newBuilder()
              .setParent(ProjectName.of(projectId).toString())
              .setDeidentifyConfig(deidentifyConfig)
              .setItem(contentItem)
              .build();

      // Execute the deidentification request
      DeidentifyContentResponse response = dlpServiceClient.deidentifyContent(request);

      // Print the character-masked input value
      // e.g. "My SSN is 123456789" --> "My SSN is *********"
      String result = response.getItem().getValue();
      System.out.println(result);
    } catch (Exception e) {
      System.out.println("Error in deidentifyWithMask: " + e.getMessage());
    }
  }

  /**
   * Deidentify a string by encrypting sensitive information while preserving format.
   *
   * @param string The string to deidentify.
   * @param alphabet The set of characters to use when encrypting the input. For more information,
   *     see cloud.google.com/dlp/docs/reference/rest/v2/content/deidentify
   * @param keyName The name of the Cloud KMS key to use when decrypting the wrapped key.
   * @param wrappedKey The encrypted (or "wrapped") AES-256 encryption key.
   * @param projectId ID of Google Cloud project to run the API under.
   */
  private static void deIdentifyWithFpe(
      String string,
      FfxCommonNativeAlphabet alphabet,
      String keyName,
      String wrappedKey,
      String projectId,
      String surrogateType) {
    // instantiate a client
    try (DlpServiceClient dlpServiceClient = DlpServiceClient.create()) {
      ContentItem contentItem = ContentItem.newBuilder().setValue(string).build();

      // Create the format-preserving encryption (FPE) configuration
      KmsWrappedCryptoKey kmsWrappedCryptoKey =
          KmsWrappedCryptoKey.newBuilder()
              .setWrappedKey(ByteString.copyFrom(BaseEncoding.base64().decode(wrappedKey)))
              .setCryptoKeyName(keyName)
              .build();

      CryptoKey cryptoKey = CryptoKey.newBuilder().setKmsWrapped(kmsWrappedCryptoKey).build();

      CryptoReplaceFfxFpeConfig cryptoReplaceFfxFpeConfig =
          CryptoReplaceFfxFpeConfig.newBuilder()
              .setCryptoKey(cryptoKey)
              .setCommonAlphabet(alphabet)
              .setSurrogateInfoType(InfoType.newBuilder().setName(surrogateType).build())
              .build();

      // Create the deidentification transformation configuration
      PrimitiveTransformation primitiveTransformation =
          PrimitiveTransformation.newBuilder()
              .setCryptoReplaceFfxFpeConfig(cryptoReplaceFfxFpeConfig)
              .build();

      InfoTypeTransformation infoTypeTransformationObject =
          InfoTypeTransformation.newBuilder()
              .setPrimitiveTransformation(primitiveTransformation)
              .build();

      InfoTypeTransformations infoTypeTransformationArray =
          InfoTypeTransformations.newBuilder()
              .addTransformations(infoTypeTransformationObject)
              .build();

      // Create the deidentification request object
      DeidentifyConfig deidentifyConfig =
          DeidentifyConfig.newBuilder()
              .setInfoTypeTransformations(infoTypeTransformationArray)
              .build();

      DeidentifyContentRequest request =
          DeidentifyContentRequest.newBuilder()
              .setParent(ProjectName.of(projectId).toString())
              .setDeidentifyConfig(deidentifyConfig)
              .setItem(contentItem)
              .build();

      // Execute the deidentification request
      DeidentifyContentResponse response = dlpServiceClient.deidentifyContent(request);

      // Print the deidentified input value
      // e.g. "My SSN is 123456789" --> "My SSN is 7261298621"
      String result = response.getItem().getValue();
      System.out.println(result);
    } catch (Exception e) {
      System.out.println("Error in deidentifyWithFpe: " + e.getMessage());
    }
  }

  /**
   * Reidentify a string by encrypting sensitive information while preserving format.
   *
   * @param string The string to reidentify.
   * @param alphabet The set of characters used when encrypting the input. For more information, see
   *     cloud.google.com/dlp/docs/reference/rest/v2/content/deidentify
   * @param keyName The name of the Cloud KMS key to use when decrypting the wrapped key.
   * @param wrappedKey The encrypted (or "wrapped") AES-256 encryption key.
   * @param projectId ID of Google Cloud project to run the API under.
   * @param surrogateType The name of the surrogate custom info type to used during the encryption
   *     process.
   */
  private static void reIdentifyWithFpe(
      String string,
      FfxCommonNativeAlphabet alphabet,
      String keyName,
      String wrappedKey,
      String projectId,
      String surrogateType) {
    // instantiate a client
    try (DlpServiceClient dlpServiceClient = DlpServiceClient.create()) {
      ContentItem contentItem = ContentItem.newBuilder().setValue(string).build();

      InfoType surrogateTypeObject = InfoType.newBuilder().setName(surrogateType).build();

      // Create the format-preserving encryption (FPE) configuration
      KmsWrappedCryptoKey kmsWrappedCryptoKey =
          KmsWrappedCryptoKey.newBuilder()
              .setWrappedKey(ByteString.copyFrom(BaseEncoding.base64().decode(wrappedKey)))
              .setCryptoKeyName(keyName)
              .build();

      CryptoKey cryptoKey = CryptoKey.newBuilder().setKmsWrapped(kmsWrappedCryptoKey).build();

      CryptoReplaceFfxFpeConfig cryptoReplaceFfxFpeConfig =
          CryptoReplaceFfxFpeConfig.newBuilder()
              .setCryptoKey(cryptoKey)
              .setCommonAlphabet(alphabet)
              .setSurrogateInfoType(surrogateTypeObject)
              .build();

      // Create the deidentification transformation configuration
      PrimitiveTransformation primitiveTransformation =
          PrimitiveTransformation.newBuilder()
              .setCryptoReplaceFfxFpeConfig(cryptoReplaceFfxFpeConfig)
              .build();

      InfoTypeTransformation infoTypeTransformationObject =
          InfoTypeTransformation.newBuilder()
              .setPrimitiveTransformation(primitiveTransformation)
              .addInfoTypes(surrogateTypeObject)
              .build();

      InfoTypeTransformations infoTypeTransformationArray =
          InfoTypeTransformations.newBuilder()
              .addTransformations(infoTypeTransformationObject)
              .build();

      // Create the inspection config
      CustomInfoType customInfoType =
          CustomInfoType.newBuilder()
              .setInfoType(surrogateTypeObject)
              .setSurrogateType(SurrogateType.newBuilder().build())
              .build();

      InspectConfig inspectConfig =
          InspectConfig.newBuilder().addCustomInfoTypes(customInfoType).build();

      // Create the reidentification request object
      DeidentifyConfig reidentifyConfig =
          DeidentifyConfig.newBuilder()
              .setInfoTypeTransformations(infoTypeTransformationArray)
              .build();

      ReidentifyContentRequest request =
          ReidentifyContentRequest.newBuilder()
              .setParent(ProjectName.of(projectId).toString())
              .setReidentifyConfig(reidentifyConfig)
              .setInspectConfig(inspectConfig)
              .setItem(contentItem)
              .build();

      // Execute the deidentification request
      ReidentifyContentResponse response = dlpServiceClient.reidentifyContent(request);

      // Print the reidentified input value
      // e.g. "My SSN is 7261298621" --> "My SSN is 123456789"
      String result = response.getItem().getValue();
      System.out.println(result);
    } catch (Exception e) {
      System.out.println("Error in reidentifyWithFpe: " + e.getMessage());
    }
  }

  /**
   * @param inputCsvPath The path to the CSV file to deidentify
   * @param outputCsvPath (Optional) path to the output CSV file
   * @param dateFields The list of (date) fields in the CSV file to date shift
   * @param lowerBoundDays The maximum number of days to shift a date backward
   * @param upperBoundDays The maximum number of days to shift a date forward
   * @param contextFieldId (Optional) The column to determine date shift, default : a random shift
   *     amount
   * @param wrappedKey (Optional) The encrypted ('wrapped') AES-256 key to use when shifting dates
   * @param keyName (Optional) The name of the Cloud KMS key used to encrypt ('wrap') the AES-256
   *     key
   * @param projectId ID of Google Cloud project to run the API under.
   */
  private static void deidentifyWithDateShift(
      Path inputCsvPath,
      Path outputCsvPath,
      String[] dateFields,
      int lowerBoundDays,
      int upperBoundDays,
      String contextFieldId,
      String wrappedKey,
      String keyName,
      String projectId)
      throws Exception {
    // instantiate a client
    try (DlpServiceClient dlpServiceClient = DlpServiceClient.create()) {

      // Set the maximum days to shift a day backward (lowerbound), forward (upperbound)
      DateShiftConfig.Builder dateShiftConfigBuilder =
          DateShiftConfig.newBuilder()
              .setLowerBoundDays(lowerBoundDays)
              .setUpperBoundDays(upperBoundDays);

      // If contextFieldId, keyName or wrappedKey is set: all three arguments must be valid
      if (contextFieldId != null && keyName != null && wrappedKey != null) {
        dateShiftConfigBuilder.setContext(FieldId.newBuilder().setName(contextFieldId).build());
        KmsWrappedCryptoKey kmsWrappedCryptoKey =
            KmsWrappedCryptoKey.newBuilder()
                .setCryptoKeyName(keyName)
                .setWrappedKey(ByteString.copyFrom(BaseEncoding.base64().decode(wrappedKey)))
                .build();
        dateShiftConfigBuilder.setCryptoKey(
            CryptoKey.newBuilder().setKmsWrapped(kmsWrappedCryptoKey).build());

      } else if (contextFieldId != null || keyName != null || wrappedKey != null) {
        throw new IllegalArgumentException(
            "You must set either ALL or NONE of {contextFieldId, keyName, wrappedKey}!");
      }

      // Read and parse the CSV file
      BufferedReader br = null;
      String line;
      List<Table.Row> rows = new ArrayList<>();
      List<FieldId> headers;

      br = new BufferedReader(new FileReader(inputCsvPath.toFile()));

      // convert csv header to FieldId
      headers =
          Arrays.stream(br.readLine().split(","))
              .map(header -> FieldId.newBuilder().setName(header).build())
              .collect(Collectors.toList());

      while ((line = br.readLine()) != null) {
        // convert csv rows to Table.Row
        rows.add(convertCsvRowToTableRow(line));
      }
      br.close();

      Table table = Table.newBuilder().addAllHeaders(headers).addAllRows(rows).build();

      List<FieldId> dateFieldIds =
          Arrays.stream(dateFields)
              .map(field -> FieldId.newBuilder().setName(field).build())
              .collect(Collectors.toList());

      DateShiftConfig dateShiftConfig = dateShiftConfigBuilder.build();

      FieldTransformation fieldTransformation =
          FieldTransformation.newBuilder()
              .addAllFields(dateFieldIds)
              .setPrimitiveTransformation(
                  PrimitiveTransformation.newBuilder().setDateShiftConfig(dateShiftConfig).build())
              .build();

      DeidentifyConfig deidentifyConfig =
          DeidentifyConfig.newBuilder()
              .setRecordTransformations(
                  RecordTransformations.newBuilder()
                      .addFieldTransformations(fieldTransformation)
                      .build())
              .build();

      ContentItem tableItem = ContentItem.newBuilder().setTable(table).build();

      DeidentifyContentRequest request =
          DeidentifyContentRequest.newBuilder()
              .setParent(ProjectName.of(projectId).toString())
              .setDeidentifyConfig(deidentifyConfig)
              .setItem(tableItem)
              .build();

      // Execute the deidentification request
      DeidentifyContentResponse response = dlpServiceClient.deidentifyContent(request);

      // Write out the response as a CSV file
      List<FieldId> outputHeaderFields = response.getItem().getTable().getHeadersList();
      List<Table.Row> outputRows = response.getItem().getTable().getRowsList();

      List<String> outputHeaders =
          outputHeaderFields.stream().map(FieldId::getName).collect(Collectors.toList());

      File outputFile = outputCsvPath.toFile();
      if (!outputFile.exists()) {
        outputFile.createNewFile();
      }
      BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(outputFile));

      // write out headers
      bufferedWriter.append(String.join(",", outputHeaders) + "\n");

      // write out each row
      for (Table.Row outputRow : outputRows) {
        String row =
            outputRow
                .getValuesList()
                .stream()
                .map(value -> value.getStringValue())
                .collect(Collectors.joining(","));
        bufferedWriter.append(row + "\n");
      }

      bufferedWriter.flush();
      bufferedWriter.close();

      System.out.println("Successfully saved date-shift output to: " + outputCsvPath.getFileName());
    } catch (Exception e) {
      System.out.println("Error in deidentifyWithDateShift: " + e.getMessage());
    }
  }

  // Parse string to valid date, return null when invalid
  private static LocalDate getValidDate(String dateString) {
    try {
      return LocalDate.parse(dateString);
    } catch (DateTimeParseException e) {
      return null;
    }
  }

  // convert CSV row into Table.Row
  private static Table.Row convertCsvRowToTableRow(String row) {
    String[] values = row.split(",");
    Table.Row.Builder tableRowBuilder = Table.Row.newBuilder();
    for (String value : values) {
      LocalDate date = getValidDate(value);
      if (date != null) {
        // convert to com.google.type.Date
        Date dateValue =
            Date.newBuilder()
                .setYear(date.getYear())
                .setMonth(date.getMonthValue())
                .setDay(date.getDayOfMonth())
                .build();
        Value tableValue = Value.newBuilder().setDateValue(dateValue).build();
        tableRowBuilder.addValues(tableValue);
      } else {
        tableRowBuilder.addValues(Value.newBuilder().setStringValue(value).build());
      }
    }
    return tableRowBuilder.build();
  }

  /**
   * Command line application to de-identify data using the Data Loss Prevention API. Supported data
   * format: strings
   */
  public static void main(String[] args) throws Exception {

    OptionGroup optionsGroup = new OptionGroup();
    optionsGroup.setRequired(true);

    Option deidentifyMaskingOption =
        new Option("m", "mask", true, "Deidentify with character masking.");
    optionsGroup.addOption(deidentifyMaskingOption);

    Option deidentifyFpeOption =
        new Option("f", "fpe", true, "Deidentify with format-preserving encryption.");
    optionsGroup.addOption(deidentifyFpeOption);

    Option reidentifyFpeOption =
        new Option("r", "reid", true, "Reidentify with format-preserving encryption.");
    optionsGroup.addOption(reidentifyFpeOption);

    Option deidentifyDateShiftOption =
        new Option("d", "date", false, "Deidentify dates in a CSV file.");
    optionsGroup.addOption(deidentifyDateShiftOption);

    Options commandLineOptions = new Options();
    commandLineOptions.addOptionGroup(optionsGroup);

    Option maskingCharacterOption =
        Option.builder("maskingCharacter").hasArg(true).required(false).build();
    commandLineOptions.addOption(maskingCharacterOption);

    Option surrogateTypeOption =
        Option.builder("surrogateType").hasArg(true).required(false).build();
    commandLineOptions.addOption(surrogateTypeOption);

    Option numberToMaskOption = Option.builder("numberToMask").hasArg(true).required(false).build();
    commandLineOptions.addOption(numberToMaskOption);

    Option alphabetOption = Option.builder("commonAlphabet").hasArg(true).required(false).build();
    commandLineOptions.addOption(alphabetOption);

    Option wrappedKeyOption = Option.builder("wrappedKey").hasArg(true).required(false).build();
    commandLineOptions.addOption(wrappedKeyOption);

    Option keyNameOption = Option.builder("keyName").hasArg(true).required(false).build();
    commandLineOptions.addOption(keyNameOption);

    Option inputCsvPathOption = Option.builder("inputCsvPath").hasArg(true).required(false).build();
    commandLineOptions.addOption(inputCsvPathOption);

    Option outputCsvPathOption =
        Option.builder("outputCsvPath").hasArg(true).required(false).build();
    commandLineOptions.addOption(outputCsvPathOption);

    Option dateFieldsOption = Option.builder("dateFields").hasArg(true).required(false).build();
    commandLineOptions.addOption(dateFieldsOption);

    Option lowerBoundDaysOption =
        Option.builder("lowerBoundDays").hasArg(true).required(false).build();
    commandLineOptions.addOption(lowerBoundDaysOption);

    Option upperBoundDaysOption =
        Option.builder("upperBoundDays").hasArg(true).required(false).build();
    commandLineOptions.addOption(upperBoundDaysOption);

    Option contextFieldNameOption =
        Option.builder("contextField").hasArg(true).required(false).build();
    commandLineOptions.addOption(contextFieldNameOption);

    Option projectIdOption = Option.builder("projectId").hasArg(true).required(false).build();
    commandLineOptions.addOption(projectIdOption);

    CommandLineParser parser = new DefaultParser();
    HelpFormatter formatter = new HelpFormatter();
    CommandLine cmd;

    try {
      cmd = parser.parse(commandLineOptions, args);
    } catch (ParseException e) {
      System.out.println(e.getMessage());
      formatter.printHelp(DeIdentification.class.getName(), commandLineOptions);
      System.exit(1);
      return;
    }

    // default to auto-detected project id when not explicitly provided
    String projectId =
        cmd.getOptionValue(projectIdOption.getOpt(), ServiceOptions.getDefaultProjectId());

    if (cmd.hasOption("m")) {
      // deidentification with character masking
      int numberToMask = Integer.parseInt(cmd.getOptionValue(numberToMaskOption.getOpt(), "0"));
      char maskingCharacter = cmd.getOptionValue(maskingCharacterOption.getOpt(), "*").charAt(0);
      String val = cmd.getOptionValue(deidentifyMaskingOption.getOpt());
      deIdentifyWithMask(val, maskingCharacter, numberToMask, projectId);
    } else if (cmd.hasOption("f")) {
      // deidentification with FPE
      String wrappedKey = cmd.getOptionValue(wrappedKeyOption.getOpt());
      String keyName = cmd.getOptionValue(keyNameOption.getOpt());
      String val = cmd.getOptionValue(deidentifyFpeOption.getOpt());
      String surrogateType = cmd.getOptionValue(surrogateTypeOption.getOpt());
      FfxCommonNativeAlphabet alphabet =
          FfxCommonNativeAlphabet.valueOf(
              cmd.getOptionValue(
                  alphabetOption.getOpt(), FfxCommonNativeAlphabet.ALPHA_NUMERIC.name()));
      deIdentifyWithFpe(val, alphabet, keyName, wrappedKey, projectId, surrogateType);
    } else if (cmd.hasOption("d")) {
      //deidentify with date shift
      String inputCsv = cmd.getOptionValue(inputCsvPathOption.getOpt());
      String outputCsv = cmd.getOptionValue(outputCsvPathOption.getOpt());

      String contextField = cmd.getOptionValue(contextFieldNameOption.getOpt(), null);
      String wrappedKey = cmd.getOptionValue(wrappedKeyOption.getOpt(), null);
      String keyName = cmd.getOptionValue(keyNameOption.getOpt(), null);

      String[] dateFields = cmd.getOptionValue(dateFieldsOption.getOpt(), "").split(",");

      int lowerBoundsDay = Integer.valueOf(cmd.getOptionValue(lowerBoundDaysOption.getOpt()));
      int upperBoundsDay = Integer.valueOf(cmd.getOptionValue(upperBoundDaysOption.getOpt()));

      deidentifyWithDateShift(
          Paths.get(inputCsv),
          Paths.get(outputCsv),
          dateFields,
          lowerBoundsDay,
          upperBoundsDay,
          contextField,
          wrappedKey,
          keyName,
          projectId);
    } else if (cmd.hasOption("r")) {
      // reidentification with FPE
      String wrappedKey = cmd.getOptionValue(wrappedKeyOption.getOpt());
      String keyName = cmd.getOptionValue(keyNameOption.getOpt());
      String val = cmd.getOptionValue(reidentifyFpeOption.getOpt());
      String surrogateType = cmd.getOptionValue(surrogateTypeOption.getOpt());
      FfxCommonNativeAlphabet alphabet =
          FfxCommonNativeAlphabet.valueOf(
              cmd.getOptionValue(
                  alphabetOption.getOpt(), FfxCommonNativeAlphabet.ALPHA_NUMERIC.name()));
      reIdentifyWithFpe(val, alphabet, keyName, wrappedKey, projectId, surrogateType);
    }
  }
}

Node.js

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

// Imports the Google Cloud Data Loss Prevention library
const DLP = require('@google-cloud/dlp');

// Instantiates a client
const dlp = new DLP.DlpServiceClient();

// The project ID to run the API call under
// const callingProjectId = process.env.GCLOUD_PROJECT;

// The string to deidentify
// const string = 'My SSN is 372819127';

// (Optional) The maximum number of sensitive characters to mask in a match
// If omitted from the request or set to 0, the API will mask any matching characters
// const numberToMask = 5;

// (Optional) The character to mask matching sensitive data with
// const maskingCharacter = 'x';

// Construct deidentification request
const item = {value: string};
const request = {
  parent: dlp.projectPath(callingProjectId),
  deidentifyConfig: {
    infoTypeTransformations: {
      transformations: [
        {
          primitiveTransformation: {
            characterMaskConfig: {
              maskingCharacter: maskingCharacter,
              numberToMask: numberToMask,
            },
          },
        },
      ],
    },
  },
  item: item,
};

// Run deidentification request
dlp
  .deidentifyContent(request)
  .then(response => {
    const deidentifiedItem = response[0].item;
    console.log(deidentifiedItem.value);
  })
  .catch(err => {
    console.log(`Error in deidentifyWithMask: ${err.message || err}`);
  });

Python

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

def deidentify_with_mask(project, string, masking_character=None,
                         number_to_mask=0):
    """Uses the Data Loss Prevention API to deidentify sensitive data in a
    string by masking it with a character.
    Args:
        project: The Google Cloud project id to use as a parent resource.
        item: The string to deidentify (will be treated as text).
        masking_character: The character to mask matching sensitive data with.
        number_to_mask: The maximum number of sensitive characters to mask in
            a match. If omitted or set to zero, the API will default to no
            maximum.
    Returns:
        None; the response from the API is printed to the terminal.
    """

    # Import the client library
    import google.cloud.dlp

    # Instantiate a client
    dlp = google.cloud.dlp.DlpServiceClient()

    # Convert the project id into a full resource id.
    parent = dlp.project_path(project)

    # Construct deidentify configuration dictionary
    deidentify_config = {
        'info_type_transformations': {
            'transformations': [
                {
                    'primitive_transformation': {
                        'character_mask_config': {
                            'masking_character': masking_character,
                            'number_to_mask': number_to_mask
                        }
                    }
                }
            ]
        }
    }

    # Construct item
    item = {'value': string}

    # Call the API
    response = dlp.deidentify_content(
        parent, deidentify_config=deidentify_config, item=item)

    # Print out the results.
    print(response.item.value)

Go

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

// mask deidentifies the input by masking all info types with maskingCharacter and
// prints the result to w.
func mask(w io.Writer, client *dlp.Client, project, input, maskingCharacter string, numberToMask int32) {
	// Create a configured request.
	req := &dlppb.DeidentifyContentRequest{
		Parent: "projects/" + project,
		DeidentifyConfig: &dlppb.DeidentifyConfig{
			Transformation: &dlppb.DeidentifyConfig_InfoTypeTransformations{
				InfoTypeTransformations: &dlppb.InfoTypeTransformations{
					Transformations: []*dlppb.InfoTypeTransformations_InfoTypeTransformation{
						{
							InfoTypes: []*dlppb.InfoType{}, // Match all info types.
							PrimitiveTransformation: &dlppb.PrimitiveTransformation{
								Transformation: &dlppb.PrimitiveTransformation_CharacterMaskConfig{
									CharacterMaskConfig: &dlppb.CharacterMaskConfig{
										MaskingCharacter: maskingCharacter,
										NumberToMask:     numberToMask,
									},
								},
							},
						},
					},
				},
			},
		},
		// The item to analyze.
		Item: &dlppb.ContentItem{
			DataItem: &dlppb.ContentItem_Value{
				Value: input,
			},
		},
	}
	// Send the request.
	r, err := client.DeidentifyContent(context.Background(), req)
	if err != nil {
		log.Fatal(err)
	}
	// Print the result.
	fmt.Fprint(w, r.GetItem().GetValue())
}

PHP

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

use Google\Cloud\Dlp\V2\CharacterMaskConfig;
use Google\Cloud\Dlp\V2\DlpServiceClient;
use Google\Cloud\Dlp\V2\InfoType;
use Google\Cloud\Dlp\V2\PrimitiveTransformation;
use Google\Cloud\Dlp\V2\DeidentifyConfig;
use Google\Cloud\Dlp\V2\InfoTypeTransformations_InfoTypeTransformation;
use Google\Cloud\Dlp\V2\InfoTypeTransformations;
use Google\Cloud\Dlp\V2\ContentItem;

/**
 * Deidentify sensitive data in a string by masking it with a character.
 * @param string $callingProjectId The GCP Project ID to run the API call under
 * @param string $string The string to deidentify
 * @param int $numberToMask (Optional) The maximum number of sensitive characters to mask in a match
 * @param string $maskingCharacter (Optional) The character to mask matching sensitive data with
 */
function deidentify_mask(
  $callingProjectId,
  $string,
  $numberToMask = 0,
  $maskingCharacter = 'x'
) {
    // Instantiate a client.
    $dlp = new DlpServiceClient();

    // The infoTypes of information to mask
    $ssnInfoType = (new InfoType())
        ->setName('US_SOCIAL_SECURITY_NUMBER');
    $infoTypes = [$ssnInfoType];

    // Create the masking configuration object
    $maskConfig = (new CharacterMaskConfig())
        ->setMaskingCharacter($maskingCharacter)
        ->setNumberToMask($numberToMask);

    // Create the information transform configuration objects
    $primitiveTransformation = (new PrimitiveTransformation())
        ->setCharacterMaskConfig($maskConfig);

    $infoTypeTransformation = (new InfoTypeTransformations_InfoTypeTransformation())
        ->setPrimitiveTransformation($primitiveTransformation);

    $infoTypeTransformations = (new InfoTypeTransformations())
        ->setTransformations([$infoTypeTransformation]);

    // Create the deidentification configuration object
    $deidentifyConfig = (new DeidentifyConfig())
        ->setInfoTypeTransformations($infoTypeTransformations);

    $item = (new ContentItem())
        ->setValue($string);

    $parent = $dlp->projectName($callingProjectId);

    // Run request
    $response = $dlp->deidentifyContent($parent, [
        'deidentifyConfig' => $deidentifyConfig,
        'item' => $item
    ]);

    $likelihoods = ['Unknown', 'Very unlikely', 'Unlikely', 'Possible',
                    'Likely', 'Very likely'];

    // Print the results
    $deidentifiedValue = $response->getItem()->getValue();
    print($deidentifiedValue);
}

C#

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

public static object DeidMask(
    string projectId,
    string dataValue,
    string infoTypes,
    string maskingCharacter,
    int numberToMask,
    bool reverseOrder)
{
    var request = new DeidentifyContentRequest
    {
        ParentAsProjectName = new ProjectName(projectId),
        DeidentifyConfig = new DeidentifyConfig
        {
            InfoTypeTransformations = new InfoTypeTransformations
            {
                Transformations = {
                    new InfoTypeTransformations.Types.InfoTypeTransformation
                    {
                        PrimitiveTransformation = new PrimitiveTransformation
                        {
                            CharacterMaskConfig = new CharacterMaskConfig
                            {
                                MaskingCharacter = maskingCharacter,
                                NumberToMask = numberToMask,
                                ReverseOrder = reverseOrder
                            }
                        }
                    }
                }
            }
        },
        Item = new ContentItem
        {
            Value = dataValue
        }
    };

    DlpServiceClient dlp = DlpServiceClient.Create();
    var response = dlp.DeidentifyContent(request);

    Console.WriteLine($"Deidentified content: {response.Item.Value}");
    return 0;
}

cryptoHashConfig

Setting cryptoHashConfig to a CryptoHashConfig object performs pseudonymization on an input value by replacing an input value with an encrypted "digest," or hash value. The digest is computed by taking the SHA-256 hash of the input value. The cryptographic key used to take the hash is taken from the CryptoKey object. The digest is encoded as a 32-byte hexadecimal string.

For example, suppose you’ve specified cryptoHashConfig for all PHONE_NUMBER infoTypes, and the CryptoKey object consists of a TransientCryptoKey, which is a randomly-generated key. Then, the following string is sent to the DLP API:

John Smith, 123 Main St, Seattle, WA 98122, 206-555-0123.

The returned string will look like the following:

John Smith, 123 Main St, Seattle, WA 98122, 41D1567F7F99F1DC2A5FAB886DEE5BEE.

Of course, the hex string will be cryptographically generated and different from the one shown here.

dateShiftConfig

Setting dateShiftConfig to a DateShiftConfig object performs date shifting on a date input value by shifting the dates by a random number of days.

Date shifting techniques randomly shift a set of dates but preserve the sequence and duration of a period of time. Shifting dates is usually done in context to an individual or an entity. That is, you want to shift all of the dates for a specific individual using the same shift differential, but use a separate shift differential for each other individual.

For more information about date shifting, see the date shifting concept topic.

Following is sample code in several languages that demonstrates how to use the DLP API to de-identify dates using date shifting.

Running this code requires a client library to be installed. For more information about installing and creating a DLP API client, see DLP API client libraries.

Java

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

/**
 * @param inputCsvPath The path to the CSV file to deidentify
 * @param outputCsvPath (Optional) path to the output CSV file
 * @param dateFields The list of (date) fields in the CSV file to date shift
 * @param lowerBoundDays The maximum number of days to shift a date backward
 * @param upperBoundDays The maximum number of days to shift a date forward
 * @param contextFieldId (Optional) The column to determine date shift, default : a random shift
 *     amount
 * @param wrappedKey (Optional) The encrypted ('wrapped') AES-256 key to use when shifting dates
 * @param keyName (Optional) The name of the Cloud KMS key used to encrypt ('wrap') the AES-256
 *     key
 * @param projectId ID of Google Cloud project to run the API under.
 */
private static void deidentifyWithDateShift(
    Path inputCsvPath,
    Path outputCsvPath,
    String[] dateFields,
    int lowerBoundDays,
    int upperBoundDays,
    String contextFieldId,
    String wrappedKey,
    String keyName,
    String projectId)
    throws Exception {
  // instantiate a client
  try (DlpServiceClient dlpServiceClient = DlpServiceClient.create()) {

    // Set the maximum days to shift a day backward (lowerbound), forward (upperbound)
    DateShiftConfig.Builder dateShiftConfigBuilder =
        DateShiftConfig.newBuilder()
            .setLowerBoundDays(lowerBoundDays)
            .setUpperBoundDays(upperBoundDays);

    // If contextFieldId, keyName or wrappedKey is set: all three arguments must be valid
    if (contextFieldId != null && keyName != null && wrappedKey != null) {
      dateShiftConfigBuilder.setContext(FieldId.newBuilder().setName(contextFieldId).build());
      KmsWrappedCryptoKey kmsWrappedCryptoKey =
          KmsWrappedCryptoKey.newBuilder()
              .setCryptoKeyName(keyName)
              .setWrappedKey(ByteString.copyFrom(BaseEncoding.base64().decode(wrappedKey)))
              .build();
      dateShiftConfigBuilder.setCryptoKey(
          CryptoKey.newBuilder().setKmsWrapped(kmsWrappedCryptoKey).build());

    } else if (contextFieldId != null || keyName != null || wrappedKey != null) {
      throw new IllegalArgumentException(
          "You must set either ALL or NONE of {contextFieldId, keyName, wrappedKey}!");
    }

    // Read and parse the CSV file
    BufferedReader br = null;
    String line;
    List<Table.Row> rows = new ArrayList<>();
    List<FieldId> headers;

    br = new BufferedReader(new FileReader(inputCsvPath.toFile()));

    // convert csv header to FieldId
    headers =
        Arrays.stream(br.readLine().split(","))
            .map(header -> FieldId.newBuilder().setName(header).build())
            .collect(Collectors.toList());

    while ((line = br.readLine()) != null) {
      // convert csv rows to Table.Row
      rows.add(convertCsvRowToTableRow(line));
    }
    br.close();

    Table table = Table.newBuilder().addAllHeaders(headers).addAllRows(rows).build();

    List<FieldId> dateFieldIds =
        Arrays.stream(dateFields)
            .map(field -> FieldId.newBuilder().setName(field).build())
            .collect(Collectors.toList());

    DateShiftConfig dateShiftConfig = dateShiftConfigBuilder.build();

    FieldTransformation fieldTransformation =
        FieldTransformation.newBuilder()
            .addAllFields(dateFieldIds)
            .setPrimitiveTransformation(
                PrimitiveTransformation.newBuilder().setDateShiftConfig(dateShiftConfig).build())
            .build();

    DeidentifyConfig deidentifyConfig =
        DeidentifyConfig.newBuilder()
            .setRecordTransformations(
                RecordTransformations.newBuilder()
                    .addFieldTransformations(fieldTransformation)
                    .build())
            .build();

    ContentItem tableItem = ContentItem.newBuilder().setTable(table).build();

    DeidentifyContentRequest request =
        DeidentifyContentRequest.newBuilder()
            .setParent(ProjectName.of(projectId).toString())
            .setDeidentifyConfig(deidentifyConfig)
            .setItem(tableItem)
            .build();

    // Execute the deidentification request
    DeidentifyContentResponse response = dlpServiceClient.deidentifyContent(request);

    // Write out the response as a CSV file
    List<FieldId> outputHeaderFields = response.getItem().getTable().getHeadersList();
    List<Table.Row> outputRows = response.getItem().getTable().getRowsList();

    List<String> outputHeaders =
        outputHeaderFields.stream().map(FieldId::getName).collect(Collectors.toList());

    File outputFile = outputCsvPath.toFile();
    if (!outputFile.exists()) {
      outputFile.createNewFile();
    }
    BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(outputFile));

    // write out headers
    bufferedWriter.append(String.join(",", outputHeaders) + "\n");

    // write out each row
    for (Table.Row outputRow : outputRows) {
      String row =
          outputRow
              .getValuesList()
              .stream()
              .map(value -> value.getStringValue())
              .collect(Collectors.joining(","));
      bufferedWriter.append(row + "\n");
    }

    bufferedWriter.flush();
    bufferedWriter.close();

    System.out.println("Successfully saved date-shift output to: " + outputCsvPath.getFileName());
  } catch (Exception e) {
    System.out.println("Error in deidentifyWithDateShift: " + e.getMessage());
  }
}

// Parse string to valid date, return null when invalid
private static LocalDate getValidDate(String dateString) {
  try {
    return LocalDate.parse(dateString);
  } catch (DateTimeParseException e) {
    return null;
  }
}

// convert CSV row into Table.Row
private static Table.Row convertCsvRowToTableRow(String row) {
  String[] values = row.split(",");
  Table.Row.Builder tableRowBuilder = Table.Row.newBuilder();
  for (String value : values) {
    LocalDate date = getValidDate(value);
    if (date != null) {
      // convert to com.google.type.Date
      Date dateValue =
          Date.newBuilder()
              .setYear(date.getYear())
              .setMonth(date.getMonthValue())
              .setDay(date.getDayOfMonth())
              .build();
      Value tableValue = Value.newBuilder().setDateValue(dateValue).build();
      tableRowBuilder.addValues(tableValue);
    } else {
      tableRowBuilder.addValues(Value.newBuilder().setStringValue(value).build());
    }
  }
  return tableRowBuilder.build();
}

Node.js

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

  // Imports the Google Cloud Data Loss Prevention library
  const DLP = require('@google-cloud/dlp');

  // Instantiates a client
  const dlp = new DLP.DlpServiceClient();

  // Import other required libraries
  const fs = require('fs');

  // The project ID to run the API call under
  // const callingProjectId = process.env.GCLOUD_PROJECT;

  // The path to the CSV file to deidentify
  // The first row of the file must specify column names, and all other rows
  // must contain valid values
  // const inputCsvFile = '/path/to/input/file.csv';

  // The path to save the date-shifted CSV file to
  // const outputCsvFile = '/path/to/output/file.csv';

  // The list of (date) fields in the CSV file to date shift
  // const dateFields = [{ name: 'birth_date'}, { name: 'register_date' }];

  // The maximum number of days to shift a date backward
  // const lowerBoundDays = 1;

  // The maximum number of days to shift a date forward
  // const upperBoundDays = 1;

  // (Optional) The column to determine date shift amount based on
  // If this is not specified, a random shift amount will be used for every row
  // If this is specified, then 'wrappedKey' and 'keyName' must also be set
  // const contextFieldId = [{ name: 'user_id' }];

  // (Optional) The name of the Cloud KMS key used to encrypt ('wrap') the AES-256 key
  // If this is specified, then 'wrappedKey' and 'contextFieldId' must also be set
  // const keyName = 'projects/YOUR_GCLOUD_PROJECT/locations/YOUR_LOCATION/keyRings/YOUR_KEYRING_NAME/cryptoKeys/YOUR_KEY_NAME';

  // (Optional) The encrypted ('wrapped') AES-256 key to use when shifting dates
  // This key should be encrypted using the Cloud KMS key specified above
  // If this is specified, then 'keyName' and 'contextFieldId' must also be set
  // const wrappedKey = 'YOUR_ENCRYPTED_AES_256_KEY'

  // Helper function for converting CSV rows to Protobuf types
  const rowToProto = row => {
    const values = row.split(',');
    const convertedValues = values.map(value => {
      if (Date.parse(value)) {
        const date = new Date(value);
        return {
          dateValue: {
            year: date.getFullYear(),
            month: date.getMonth() + 1,
            day: date.getDate(),
          },
        };
      } else {
        // Convert all non-date values to strings
        return {stringValue: value.toString()};
      }
    });
    return {values: convertedValues};
  };

  // Read and parse a CSV file
  const csvLines = fs
    .readFileSync(inputCsvFile)
    .toString()
    .split('\n')
    .filter(line => line.includes(','));
  const csvHeaders = csvLines[0].split(',');
  const csvRows = csvLines.slice(1);

  // Construct the table object
  const tableItem = {
    table: {
      headers: csvHeaders.map(header => {
        return {name: header};
      }),
      rows: csvRows.map(row => rowToProto(row)),
    },
  };

  // Construct DateShiftConfig
  const dateShiftConfig = {
    lowerBoundDays: lowerBoundDays,
    upperBoundDays: upperBoundDays,
  };

  if (contextFieldId && keyName && wrappedKey) {
    dateShiftConfig.context = {name: contextFieldId};
    dateShiftConfig.cryptoKey = {
      kmsWrapped: {
        wrappedKey: wrappedKey,
        cryptoKeyName: keyName,
      },
    };
  } else if (contextFieldId || keyName || wrappedKey) {
    throw new Error(
      'You must set either ALL or NONE of {contextFieldId, keyName, wrappedKey}!'
    );
  }

  // Construct deidentification request
  const request = {
    parent: dlp.projectPath(callingProjectId),
    deidentifyConfig: {
      recordTransformations: {
        fieldTransformations: [
          {
            fields: dateFields,
            primitiveTransformation: {
              dateShiftConfig: dateShiftConfig,
            },
          },
        ],
      },
    },
    item: tableItem,
  };

  // Run deidentification request
  dlp
    .deidentifyContent(request)
    .then(response => {
      const tableRows = response[0].item.table.rows;

      // Write results to a CSV file
      tableRows.forEach((row, rowIndex) => {
        const rowValues = row.values.map(
          value =>
            value.stringValue ||
            `${value.dateValue.month}/${value.dateValue.day}/${
              value.dateValue.year
            }`
        );
        csvLines[rowIndex + 1] = rowValues.join(',');
      });
      csvLines.push('');
      fs.writeFileSync(outputCsvFile, csvLines.join('\n'));

      // Print status
      console.log(`Successfully saved date-shift output to ${outputCsvFile}`);
    })
    .catch(err => {
      console.log(`Error in deidentifyWithDateShift: ${err.message || err}`);
    });
}

function deidentifyWithFpe(
  callingProjectId,
  string,
  alphabet,
  surrogateType,
  keyName,
  wrappedKey
) {
  // Imports the Google Cloud Data Loss Prevention library
  const DLP = require('@google-cloud/dlp');

  // Instantiates a client
  const dlp = new DLP.DlpServiceClient();

  // The project ID to run the API call under
  // const callingProjectId = process.env.GCLOUD_PROJECT;

  // The string to deidentify
  // const string = 'My SSN is 372819127';

  // The set of characters to replace sensitive ones with
  // For more information, see https://cloud.google.com/dlp/docs/reference/rest/v2/organizations.deidentifyTemplates#ffxcommonnativealphabet
  // const alphabet = 'ALPHA_NUMERIC';

  // The name of the Cloud KMS key used to encrypt ('wrap') the AES-256 key
  // const keyName = 'projects/YOUR_GCLOUD_PROJECT/locations/YOUR_LOCATION/keyRings/YOUR_KEYRING_NAME/cryptoKeys/YOUR_KEY_NAME';

  // The encrypted ('wrapped') AES-256 key to use
  // This key should be encrypted using the Cloud KMS key specified above
  // const wrappedKey = 'YOUR_ENCRYPTED_AES_256_KEY'

  // (Optional) The name of the surrogate custom info type to use
  // Only necessary if you want to reverse the deidentification process
  // Can be essentially any arbitrary string, as long as it doesn't appear
  // in your dataset otherwise.
  // const surrogateType = 'SOME_INFO_TYPE_DEID';

  // Construct FPE config
  const cryptoReplaceFfxFpeConfig = {
    cryptoKey: {
      kmsWrapped: {
        wrappedKey: wrappedKey,
        cryptoKeyName: keyName,
      },
    },
    commonAlphabet: alphabet,
  };
  if (surrogateType) {
    cryptoReplaceFfxFpeConfig.surrogateInfoType = {
      name: surrogateType,
    };
  }

  // Construct deidentification request
  const item = {value: string};
  const request = {
    parent: dlp.projectPath(callingProjectId),
    deidentifyConfig: {
      infoTypeTransformations: {
        transformations: [
          {
            primitiveTransformation: {
              cryptoReplaceFfxFpeConfig: cryptoReplaceFfxFpeConfig,
            },
          },
        ],
      },
    },
    item: item,
  };

  // Run deidentification request
  dlp
    .deidentifyContent(request)
    .then(response => {
      const deidentifiedItem = response[0].item;
      console.log(deidentifiedItem.value);
    })
    .catch(err => {
      console.log(`Error in deidentifyWithFpe: ${err.message || err}`);
    });
}

function reidentifyWithFpe(
  callingProjectId,
  string,
  alphabet,
  surrogateType,
  keyName,
  wrappedKey
) {
  // Imports the Google Cloud Data Loss Prevention library
  const DLP = require('@google-cloud/dlp');

  // Instantiates a client
  const dlp = new DLP.DlpServiceClient();

  // The project ID to run the API call under
  // const callingProjectId = process.env.GCLOUD_PROJECT;

  // The string to reidentify
  // const string = 'My SSN is PHONE_TOKEN(9):#########';

  // The set of characters to replace sensitive ones with
  // For more information, see https://cloud.google.com/dlp/docs/reference/rest/v2/organizations.deidentifyTemplates#ffxcommonnativealphabet
  // const alphabet = 'ALPHA_NUMERIC';

  // The name of the Cloud KMS key used to encrypt ('wrap') the AES-256 key
  // const keyName = 'projects/YOUR_GCLOUD_PROJECT/locations/YOUR_LOCATION/keyRings/YOUR_KEYRING_NAME/cryptoKeys/YOUR_KEY_NAME';

  // The encrypted ('wrapped') AES-256 key to use
  // This key should be encrypted using the Cloud KMS key specified above
  // const wrappedKey = 'YOUR_ENCRYPTED_AES_256_KEY'

  // The name of the surrogate custom info type to use when reidentifying data
  // const surrogateType = 'SOME_INFO_TYPE_DEID';

  // Construct deidentification request
  const item = {value: string};
  const request = {
    parent: dlp.projectPath(callingProjectId),
    reidentifyConfig: {
      infoTypeTransformations: {
        transformations: [
          {
            primitiveTransformation: {
              cryptoReplaceFfxFpeConfig: {
                cryptoKey: {
                  kmsWrapped: {
                    wrappedKey: wrappedKey,
                    cryptoKeyName: keyName,
                  },
                },
                commonAlphabet: alphabet,
                surrogateInfoType: {
                  name: surrogateType,
                },
              },
            },
          },
        ],
      },
    },
    inspectConfig: {
      customInfoTypes: [
        {
          infoType: {
            name: surrogateType,
          },
          surrogateType: {},
        },
      ],
    },
    item: item,
  };

  // Run reidentification request
  dlp
    .reidentifyContent(request)
    .then(response => {
      const reidentifiedItem = response[0].item;
      console.log(reidentifiedItem.value);
    })
    .catch(err => {
      console.log(`Error in reidentifyWithFpe: ${err.message || err}`);
    });
}

const cli = require(`yargs`)
  .demand(1)
  .command(
    `deidMask <string>`,
    `Deidentify sensitive data in a string by masking it with a character.`,
    {
      maskingCharacter: {
        type: 'string',
        alias: 'm',
        default: '',
      },
      numberToMask: {
        type: 'number',
        alias: 'n',
        default: 0,
      },
    },
    opts =>
      deidentifyWithMask(
        opts.callingProjectId,
        opts.string,
        opts.maskingCharacter,
        opts.numberToMask
      )
  )
  .command(
    `deidFpe <string> <wrappedKey> <keyName>`,
    `Deidentify sensitive data in a string using Format Preserving Encryption (FPE).`,
    {
      alphabet: {
        type: 'string',
        alias: 'a',
        default: 'ALPHA_NUMERIC',
        choices: [
          'NUMERIC',
          'HEXADECIMAL',
          'UPPER_CASE_ALPHA_NUMERIC',
          'ALPHA_NUMERIC',
        ],
      },
      surrogateType: {
        type: 'string',
        alias: 's',
        default: '',
      },
    },
    opts =>
      deidentifyWithFpe(
        opts.callingProjectId,
        opts.string,
        opts.alphabet,
        opts.surrogateType,
        opts.keyName,
        opts.wrappedKey
      )
  )
  .command(
    `reidFpe <string> <surrogateType> <wrappedKey> <keyName>`,
    `Reidentify sensitive data in a string using Format Preserving Encryption (FPE).`,
    {
      alphabet: {
        type: 'string',
        alias: 'a',
        default: 'ALPHA_NUMERIC',
        choices: [
          'NUMERIC',
          'HEXADECIMAL',
          'UPPER_CASE_ALPHA_NUMERIC',
          'ALPHA_NUMERIC',
        ],
      },
    },
    opts =>
      reidentifyWithFpe(
        opts.callingProjectId,
        opts.string,
        opts.alphabet,
        opts.surrogateType,
        opts.keyName,
        opts.wrappedKey
      )
  )
  .command(
    `deidDateShift <inputCsvFile> <outputCsvFile> <lowerBoundDays> <upperBoundDays> [dateFields...]`,
    `Deidentify dates in a CSV file by pseudorandomly shifting them.`,
    {
      contextFieldId: {
        type: 'string',
        alias: 'f',
        default: '',
      },
      wrappedKey: {
        type: 'string',
        alias: 'w',
        default: '',
      },
      keyName: {
        type: 'string',
        alias: 'n',
        default: '',
      },
    },
    opts =>
      deidentifyWithDateShift(
        opts.callingProjectId,
        opts.inputCsvFile,
        opts.outputCsvFile,
        opts.dateFields.map(f => {
          return {name: f};
        }),
        opts.lowerBoundDays,
        opts.upperBoundDays,
        opts.contextFieldId,
        opts.wrappedKey,
        opts.keyName
      )
  )
  .option('c', {
    type: 'string',
    alias: 'callingProjectId',
    default: process.env.GCLOUD_PROJECT || '',
  })
  .example(`node $0 deidMask "My SSN is 372819127"`)
  .example(
    `node $0 deidFpe "My SSN is 372819127" <YOUR_ENCRYPTED_AES_256_KEY> projects/my-project/locations/global/keyrings/my-keyring -s SSN_TOKEN`
  )
  .example(
    `node $0 reidFpe "My SSN is SSN_TOKEN(9):#########" <YOUR_ENCRYPTED_AES_256_KEY> projects/my-project/locations/global/keyrings/my-keyring SSN_TOKEN -a NUMERIC`
  )
  .example(
    `node $0 deidDateShift dates.csv dates-shifted.csv 30 30 birth_date register_date [-w <YOUR_ENCRYPTED_AES_256_KEY> -n projects/my-project/locations/global/keyrings/my-keyring]`
  )
  .wrap(120)
  .recommendCommands()
  .epilogue(`For more information, see https://cloud.google.com/dlp/docs.`);

if (module === require.main) {
  cli.help().strict().argv; // eslint-disable-line
}

Python

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

def deidentify_with_date_shift(project, input_csv_file=None,
                               output_csv_file=None, date_fields=None,
                               lower_bound_days=None, upper_bound_days=None,
                               context_field_id=None, wrapped_key=None,
                               key_name=None):
    """Uses the Data Loss Prevention API to deidentify dates in a CSV file by
        pseudorandomly shifting them.
    Args:
        project: The Google Cloud project id to use as a parent resource.
        input_csv_file: The path to the CSV file to deidentify. The first row
            of the file must specify column names, and all other rows must
            contain valid values.
        output_csv_file: The path to save the date-shifted CSV file.
        date_fields: The list of (date) fields in the CSV file to date shift.
            Example: ['birth_date', 'register_date']
        lower_bound_days: The maximum number of days to shift a date backward
        upper_bound_days: The maximum number of days to shift a date forward
        context_field_id: (Optional) The column to determine date shift amount
            based on. If this is not specified, a random shift amount will be
            used for every row. If this is specified, then 'wrappedKey' and
            'keyName' must also be set. Example:
            contextFieldId = [{ 'name': 'user_id' }]
        key_name: (Optional) The name of the Cloud KMS key used to encrypt
            ('wrap') the AES-256 key. Example:
            key_name = 'projects/YOUR_GCLOUD_PROJECT/locations/YOUR_LOCATION/
            keyRings/YOUR_KEYRING_NAME/cryptoKeys/YOUR_KEY_NAME'
        wrapped_key: (Optional) The encrypted ('wrapped') AES-256 key to use.
            This key should be encrypted using the Cloud KMS key specified by
            key_name.
    Returns:
        None; the response from the API is printed to the terminal.
    """
    # Import the client library
    import google.cloud.dlp

    # Instantiate a client
    dlp = google.cloud.dlp.DlpServiceClient()

    # Convert the project id into a full resource id.
    parent = dlp.project_path(project)

    # Convert date field list to Protobuf type
    def map_fields(field):
        return {'name': field}

    if date_fields:
        date_fields = map(map_fields, date_fields)
    else:
        date_fields = []

    # Read and parse the CSV file
    import csv
    from datetime import datetime
    f = []
    with open(input_csv_file, 'r') as csvfile:
        reader = csv.reader(csvfile)
        for row in reader:
            f.append(row)

    #  Helper function for converting CSV rows to Protobuf types
    def map_headers(header):
        return {'name': header}

    def map_data(value):
        try:
            date = datetime.strptime(value, '%m/%d/%Y')
            return {
                'date_value': {
                    'year': date.year,
                    'month': date.month,
                    'day': date.day
                }
            }
        except ValueError:
            return {'string_value': value}

    def map_rows(row):
        return {'values': map(map_data, row)}

    # Using the helper functions, convert CSV rows to protobuf-compatible
    # dictionaries.
    csv_headers = map(map_headers, f[0])
    csv_rows = map(map_rows, f[1:])

    # Construct the table dict
    table_item = {
        'table': {
            'headers': csv_headers,
            'rows': csv_rows
        }
    }

    # Construct date shift config
    date_shift_config = {
        'lower_bound_days': lower_bound_days,
        'upper_bound_days': upper_bound_days
    }

    # If using a Cloud KMS key, add it to the date_shift_config.
    # The wrapped key is base64-encoded, but the library expects a binary
    # string, so decode it here.
    if context_field_id and key_name and wrapped_key:
        import base64
        date_shift_config['context'] = {'name': context_field_id}
        date_shift_config['crypto_key'] = {
            'kms_wrapped': {
                'wrapped_key': base64.b64decode(wrapped_key),
                'crypto_key_name': key_name
            }
        }
    elif context_field_id or key_name or wrapped_key:
        raise ValueError("""You must set either ALL or NONE of
        [context_field_id, key_name, wrapped_key]!""")

    # Construct Deidentify Config
    deidentify_config = {
        'record_transformations': {
            'field_transformations': [
                {
                    'fields': date_fields,
                    'primitive_transformation': {
                        'date_shift_config': date_shift_config
                    }
                }
            ]
        }
    }

    # Write to CSV helper methods
    def write_header(header):
        return header.name

    def write_data(data):
        return data.string_value or '%s/%s/%s' % (data.date_value.month,
                                                  data.date_value.day,
                                                  data.date_value.year)

    # Call the API
    response = dlp.deidentify_content(
        parent, deidentify_config=deidentify_config, item=table_item)

    # Write results to CSV file
    with open(output_csv_file, 'w') as csvfile:
        write_file = csv.writer(csvfile, delimiter=',')
        write_file.writerow(map(write_header, response.item.table.headers))
        for row in response.item.table.rows:
            write_file.writerow(map(write_data, row.values))
    # Print status
    print('Successfully saved date-shift output to {}'.format(
                output_csv_file))

Go

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

// deidentifyDateShift shifts dates found in the input between lowerBoundDays and
// upperBoundDays.
func deidentifyDateShift(w io.Writer, client *dlp.Client, project string, lowerBoundDays, upperBoundDays int32, input string) {
	// Create a configured request.
	req := &dlppb.DeidentifyContentRequest{
		Parent: "projects/" + project,
		DeidentifyConfig: &dlppb.DeidentifyConfig{
			Transformation: &dlppb.DeidentifyConfig_InfoTypeTransformations{
				InfoTypeTransformations: &dlppb.InfoTypeTransformations{
					Transformations: []*dlppb.InfoTypeTransformations_InfoTypeTransformation{
						{
							InfoTypes: []*dlppb.InfoType{}, // Match all info types.
							PrimitiveTransformation: &dlppb.PrimitiveTransformation{
								Transformation: &dlppb.PrimitiveTransformation_DateShiftConfig{
									DateShiftConfig: &dlppb.DateShiftConfig{
										LowerBoundDays: lowerBoundDays,
										UpperBoundDays: upperBoundDays,
									},
								},
							},
						},
					},
				},
			},
		},
		// The InspectConfig is used to identify the DATE fields.
		InspectConfig: &dlppb.InspectConfig{
			InfoTypes: []*dlppb.InfoType{
				{
					Name: "DATE",
				},
			},
		},
		// The item to analyze.
		Item: &dlppb.ContentItem{
			DataItem: &dlppb.ContentItem_Value{
				Value: input,
			},
		},
	}
	// Send the request.
	r, err := client.DeidentifyContent(context.Background(), req)
	if err != nil {
		log.Fatal(err)
	}
	// Print the result.
	fmt.Fprint(w, r.GetItem().GetValue())
}

PHP

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

use Google\Cloud\Dlp\V2\ContentItem;
use Google\Cloud\Dlp\V2\CryptoKey;
use Google\Cloud\Dlp\V2\DateShiftConfig;
use Google\Cloud\Dlp\V2\DeidentifyConfig;
use Google\Cloud\Dlp\V2\DlpServiceClient;
use Google\Cloud\Dlp\V2\FieldId;
use Google\Cloud\Dlp\V2\FieldTransformation;
use Google\Cloud\Dlp\V2\KmsWrappedCryptoKey;
use Google\Cloud\Dlp\V2\PrimitiveTransformation;
use Google\Cloud\Dlp\V2\RecordTransformations;
use Google\Cloud\Dlp\V2\Table;
use Google\Cloud\Dlp\V2\Table_Row;
use Google\Cloud\Dlp\V2\Value;
use Google\Type\Date;
use DateTime;

/**
 * Deidentify dates in a CSV file by pseudorandomly shifting them.
 *
 * @param string $callingProject The GCP Project ID to run the API call under
 * @param string $inputCsvFile The path to the CSV file to deidentify
 * @param string $outputCsvFile The path to save the date-shifted CSV file to
 * @param array $dateFieldNames The list of (date) fields in the CSV file to date shift
 * @param string $lowerBoundDays The maximum number of days to shift a date backward
 * @param string $upperBoundDays The maximum number of days to shift a date forward
 * @param string contextFieldName (Optional) The column to determine date shift amount based on
 *        If this is not specified, a random shift amount will be used for every row.
 *        If this is specified, then 'wrappedKey' and 'keyName' must also be set
 * @param string keyName (Optional) The encrypted ('wrapped') AES-256 key to use when shifting dates
 *        If this is specified, then 'wrappedKey' and 'contextFieldName' must also be set
 * @param string wrappedKey (Optional) The name of the Cloud KMS key used to encrypt ('wrap') the AES-256 key
 *        If this is specified, then 'keyName' and 'contextFieldName' must also be set
 */
function deidentify_dates(
    $callingProjectId,
    $inputCsvFile,
    $outputCsvFile,
    $dateFieldNames,
    $lowerBoundDays,
    $upperBoundDays,
    $contextFieldName = '',
    $keyName = '',
    $wrappedKey = ''
) {
    // Instantiate a client.
    $dlp = new DlpServiceClient();

    // Read a CSV file
    $csvLines = file($inputCsvFile, FILE_IGNORE_NEW_LINES);
    $csvHeaders = explode(',', $csvLines[0]);
    $csvRows = array_slice($csvLines, 1);

    // Convert CSV file into protobuf objects
    $tableHeaders = array_map(function ($csvHeader) {
        return (new FieldId)->setName($csvHeader);
    }, $csvHeaders);

    $tableRows = array_map(function ($csvRow) {
        $rowValues = array_map(function ($csvValue) {
            if ($csvDate = DateTime::createFromFormat('m/d/Y', $csvValue)) {
                $date = (new Date())
                    ->setYear((int) $csvDate->format('Y'))
                    ->setMonth((int) $csvDate->format('m'))
                    ->setDay((int) $csvDate->format('d'));
                return (new Value())
                    ->setDateValue($date);
            } else {
                return (new Value())
                    ->setStringValue($csvValue);
            }
        }, explode(',', $csvRow));

        return (new Table_Row())
            ->setValues($rowValues);
    }, $csvRows);

    // Convert date fields into protobuf objects
    $dateFields = array_map(function ($dateFieldName) {
        return (new FieldId())->setName($dateFieldName);
    }, $dateFieldNames);

    // Construct the table object
    $table = (new Table())
        ->setHeaders($tableHeaders)
        ->setRows($tableRows);

    $item = (new ContentItem())
        ->setTable($table);

    // Construct dateShiftConfig
    $dateShiftConfig = (new DateShiftConfig())
        ->setLowerBoundDays($lowerBoundDays)
        ->setUpperBoundDays($upperBoundDays);

    if ($contextFieldName && $keyName && $wrappedKey) {
        $contextField = (new FieldId())
            ->setName($contextFieldName);

        // Create the wrapped crypto key configuration object
        $kmsWrappedCryptoKey = (new KmsWrappedCryptoKey())
            ->setWrappedKey(base64_decode($wrappedKey))
            ->setCryptoKeyName($keyName);

        $cryptoKey = (new CryptoKey())
            ->setKmsWrapped($kmsWrappedCryptoKey);

        $dateShiftConfig
            ->setContext($contextField)
            ->setCryptoKey($cryptoKey);
    } elseif ($contextFieldName || $keyName || $wrappedKey) {
        throw new Exception('You must set either ALL or NONE of {$contextFieldName, $keyName, $wrappedKey}!');
    }

    // Create the information transform configuration objects
    $primitiveTransformation = (new PrimitiveTransformation())
        ->setDateShiftConfig($dateShiftConfig);

    $fieldTransformation = (new FieldTransformation())
        ->setPrimitiveTransformation($primitiveTransformation)
        ->setFields($dateFields);

    $recordTransformations = (new RecordTransformations())
        ->setFieldTransformations([$fieldTransformation]);

    // Create the deidentification configuration object
    $deidentifyConfig = (new DeidentifyConfig())
        ->setRecordTransformations($recordTransformations);

    $parent = $dlp->projectName($callingProjectId);

    // Run request
    $response = $dlp->deidentifyContent($parent, [
        'deidentifyConfig' => $deidentifyConfig,
        'item' => $item
    ]);

    // Check for errors
    foreach ($response->getOverview()->getTransformationSummaries() as $summary) {
        foreach ($summary->getResults() as $result) {
            if ($details = $result->getDetails()) {
                printf('Error: %s' . PHP_EOL, $details);
                return;
            }
        }
    }

    // Save the results to a file
    $csvRef = fopen($outputCsvFile, 'w');
    fputcsv($csvRef, $csvHeaders);
    foreach ($response->getItem()->getTable()->getRows() as $tableRow) {
        $values = array_map(function ($tableValue) {
            if ($tableValue->getStringValue()) {
                return $tableValue->getStringValue();
            }
            $protoDate = $tableValue->getDateValue();
            $date = mktime(0, 0, 0, $protoDate->getMonth(), $protoDate->getDay(), $protoDate->getYear());
            return strftime('%D', $date);
        }, iterator_to_array($tableRow->getValues()));
        fputcsv($csvRef, $values);
    };
    fclose($csvRef);
    printf('Deidentified dates written to %s' . PHP_EOL, $outputCsvFile);
}

C#

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

public static object DeidDateShift(
    string projectId,
    string inputCsvFile,
    string outputCsvFile,
    int lowerBoundDays,
    int upperBoundDays,
    string dateFields,
    string contextField = "",
    string keyName = "",
    string wrappedKey = "")
{
    var dlp = DlpServiceClient.Create();

    // Read file
    string[] csvLines = File.ReadAllLines(inputCsvFile);
    string[] csvHeaders = csvLines[0].Split(',');
    string[] csvRows = csvLines.Skip(1).ToArray();

    // Convert dates to protobuf format, and everything else to a string
    var protoHeaders = csvHeaders.Select(header => new FieldId { Name = header });
    var protoRows = csvRows.Select(CsvRow =>
    {
        var rowValues = CsvRow.Split(',');
        var protoValues = rowValues.Select(RowValue =>
        {
            System.DateTime parsedDate;
            if (System.DateTime.TryParse(RowValue, out parsedDate))
            {
                return new Value
                {
                    DateValue = new Google.Type.Date
                    {
                        Year = parsedDate.Year,
                        Month = parsedDate.Month,
                        Day = parsedDate.Day
                    }
                };
            }
            else
            {
                return new Value
                {
                    StringValue = RowValue
                };
            }
        });

        var rowObject = new Table.Types.Row();
        rowObject.Values.Add(protoValues);
        return rowObject;
    });

    var dateFieldList = dateFields
        .Split(',')
        .Select(field => new FieldId { Name = field });

    // Construct + execute the request
    var dateShiftConfig = new DateShiftConfig
    {
        LowerBoundDays = lowerBoundDays,
        UpperBoundDays = upperBoundDays
    };
    bool hasKeyName = !String.IsNullOrEmpty(keyName);
    bool hasWrappedKey = !String.IsNullOrEmpty(wrappedKey);
    bool hasContext = !String.IsNullOrEmpty(contextField);
    if (hasKeyName && hasWrappedKey && hasContext)
    {
        dateShiftConfig.Context = new FieldId { Name = contextField };
        dateShiftConfig.CryptoKey = new CryptoKey
        {
            KmsWrapped = new KmsWrappedCryptoKey
            {
                WrappedKey = ByteString.FromBase64(wrappedKey),
                CryptoKeyName = keyName
            }
        };
    }
    else if (hasKeyName || hasWrappedKey || hasContext)
    {
        throw new ArgumentException("Must specify ALL or NONE of: {contextFieldId, keyName, wrappedKey}!");
    }

    var deidConfig = new DeidentifyConfig
    {
        RecordTransformations = new RecordTransformations
        {
            FieldTransformations =
            {
                new FieldTransformation
                {
                    PrimitiveTransformation = new PrimitiveTransformation
                    {
                        DateShiftConfig = dateShiftConfig
                    },
                    Fields = { dateFieldList }
                }
            }
        }
    };

    DeidentifyContentResponse response = dlp.DeidentifyContent(
        new DeidentifyContentRequest
        {
            Parent = $"projects/{projectId}",
            DeidentifyConfig = deidConfig,
            Item = new ContentItem
            {
                Table = new Table
                {
                    Headers = { protoHeaders },
                    Rows = { protoRows }
                }
            }
        });

    // Save the results
    List<String> outputLines = new List<string>();
    outputLines.Add(csvLines[0]);

    outputLines.AddRange(response.Item.Table.Rows.Select(ProtoRow =>
    {
        var Values = ProtoRow.Values.Select(ProtoValue =>
        {
            if (ProtoValue.DateValue != null)
            {
                var ProtoDate = ProtoValue.DateValue;
                System.DateTime Date = new System.DateTime(
                    ProtoDate.Year, ProtoDate.Month, ProtoDate.Day);
                return Date.ToShortDateString();
            }
            else
            {
                return ProtoValue.StringValue;
            }
        });
        return String.Join(',', Values);
    }));

    File.WriteAllLines(outputCsvFile, outputLines);

    return 0;
}

cryptoReplaceFfxFpeConfig

Setting cryptoReplaceFfxFpeConfig to a CryptoReplaceFfxFpeConfig object performs pseudonymization on an input value by replacing an input value with a token. This token is:

  • The encrypted input value.
  • The same length as the input value.
  • Computed using format-preserving encryption (FPE) in FFX mode keyed on the cryptographic key specified by cryptoKey.
  • Comprised of the characters specified by alphabet.

The input value:

  • Must be at least two characters long (or the empty string).
  • Must be comprised of the characters specified by alphabet.

Following is sample code in several languages that demonstrates how to use the DLP API to de-identify sensitive data by replacing an input value with a token.

Running this code requires a client library to be installed. For more information about installing and creating a DLP API client, see DLP API client libraries.

Java

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

/**
 * Deidentify a string by encrypting sensitive information while preserving format.
 *
 * @param string The string to deidentify.
 * @param alphabet The set of characters to use when encrypting the input. For more information,
 *     see cloud.google.com/dlp/docs/reference/rest/v2/content/deidentify
 * @param keyName The name of the Cloud KMS key to use when decrypting the wrapped key.
 * @param wrappedKey The encrypted (or "wrapped") AES-256 encryption key.
 * @param projectId ID of Google Cloud project to run the API under.
 */
private static void deIdentifyWithFpe(
    String string,
    FfxCommonNativeAlphabet alphabet,
    String keyName,
    String wrappedKey,
    String projectId,
    String surrogateType) {
  // instantiate a client
  try (DlpServiceClient dlpServiceClient = DlpServiceClient.create()) {
    ContentItem contentItem = ContentItem.newBuilder().setValue(string).build();

    // Create the format-preserving encryption (FPE) configuration
    KmsWrappedCryptoKey kmsWrappedCryptoKey =
        KmsWrappedCryptoKey.newBuilder()
            .setWrappedKey(ByteString.copyFrom(BaseEncoding.base64().decode(wrappedKey)))
            .setCryptoKeyName(keyName)
            .build();

    CryptoKey cryptoKey = CryptoKey.newBuilder().setKmsWrapped(kmsWrappedCryptoKey).build();

    CryptoReplaceFfxFpeConfig cryptoReplaceFfxFpeConfig =
        CryptoReplaceFfxFpeConfig.newBuilder()
            .setCryptoKey(cryptoKey)
            .setCommonAlphabet(alphabet)
            .setSurrogateInfoType(InfoType.newBuilder().setName(surrogateType).build())
            .build();

    // Create the deidentification transformation configuration
    PrimitiveTransformation primitiveTransformation =
        PrimitiveTransformation.newBuilder()
            .setCryptoReplaceFfxFpeConfig(cryptoReplaceFfxFpeConfig)
            .build();

    InfoTypeTransformation infoTypeTransformationObject =
        InfoTypeTransformation.newBuilder()
            .setPrimitiveTransformation(primitiveTransformation)
            .build();

    InfoTypeTransformations infoTypeTransformationArray =
        InfoTypeTransformations.newBuilder()
            .addTransformations(infoTypeTransformationObject)
            .build();

    // Create the deidentification request object
    DeidentifyConfig deidentifyConfig =
        DeidentifyConfig.newBuilder()
            .setInfoTypeTransformations(infoTypeTransformationArray)
            .build();

    DeidentifyContentRequest request =
        DeidentifyContentRequest.newBuilder()
            .setParent(ProjectName.of(projectId).toString())
            .setDeidentifyConfig(deidentifyConfig)
            .setItem(contentItem)
            .build();

    // Execute the deidentification request
    DeidentifyContentResponse response = dlpServiceClient.deidentifyContent(request);

    // Print the deidentified input value
    // e.g. "My SSN is 123456789" --> "My SSN is 7261298621"
    String result = response.getItem().getValue();
    System.out.println(result);
  } catch (Exception e) {
    System.out.println("Error in deidentifyWithFpe: " + e.getMessage());
  }
}

Node.js

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

  // Imports the Google Cloud Data Loss Prevention library
  const DLP = require('@google-cloud/dlp');

  // Instantiates a client
  const dlp = new DLP.DlpServiceClient();

  // The project ID to run the API call under
  // const callingProjectId = process.env.GCLOUD_PROJECT;

  // The string to deidentify
  // const string = 'My SSN is 372819127';

  // The set of characters to replace sensitive ones with
  // For more information, see https://cloud.google.com/dlp/docs/reference/rest/v2/organizations.deidentifyTemplates#ffxcommonnativealphabet
  // const alphabet = 'ALPHA_NUMERIC';

  // The name of the Cloud KMS key used to encrypt ('wrap') the AES-256 key
  // const keyName = 'projects/YOUR_GCLOUD_PROJECT/locations/YOUR_LOCATION/keyRings/YOUR_KEYRING_NAME/cryptoKeys/YOUR_KEY_NAME';

  // The encrypted ('wrapped') AES-256 key to use
  // This key should be encrypted using the Cloud KMS key specified above
  // const wrappedKey = 'YOUR_ENCRYPTED_AES_256_KEY'

  // (Optional) The name of the surrogate custom info type to use
  // Only necessary if you want to reverse the deidentification process
  // Can be essentially any arbitrary string, as long as it doesn't appear
  // in your dataset otherwise.
  // const surrogateType = 'SOME_INFO_TYPE_DEID';

  // Construct FPE config
  const cryptoReplaceFfxFpeConfig = {
    cryptoKey: {
      kmsWrapped: {
        wrappedKey: wrappedKey,
        cryptoKeyName: keyName,
      },
    },
    commonAlphabet: alphabet,
  };
  if (surrogateType) {
    cryptoReplaceFfxFpeConfig.surrogateInfoType = {
      name: surrogateType,
    };
  }

  // Construct deidentification request
  const item = {value: string};
  const request = {
    parent: dlp.projectPath(callingProjectId),
    deidentifyConfig: {
      infoTypeTransformations: {
        transformations: [
          {
            primitiveTransformation: {
              cryptoReplaceFfxFpeConfig: cryptoReplaceFfxFpeConfig,
            },
          },
        ],
      },
    },
    item: item,
  };

  // Run deidentification request
  dlp
    .deidentifyContent(request)
    .then(response => {
      const deidentifiedItem = response[0].item;
      console.log(deidentifiedItem.value);
    })
    .catch(err => {
      console.log(`Error in deidentifyWithFpe: ${err.message || err}`);
    });
}

function reidentifyWithFpe(
  callingProjectId,
  string,
  alphabet,
  surrogateType,
  keyName,
  wrappedKey
) {
  // Imports the Google Cloud Data Loss Prevention library
  const DLP = require('@google-cloud/dlp');

  // Instantiates a client
  const dlp = new DLP.DlpServiceClient();

  // The project ID to run the API call under
  // const callingProjectId = process.env.GCLOUD_PROJECT;

  // The string to reidentify
  // const string = 'My SSN is PHONE_TOKEN(9):#########';

  // The set of characters to replace sensitive ones with
  // For more information, see https://cloud.google.com/dlp/docs/reference/rest/v2/organizations.deidentifyTemplates#ffxcommonnativealphabet
  // const alphabet = 'ALPHA_NUMERIC';

  // The name of the Cloud KMS key used to encrypt ('wrap') the AES-256 key
  // const keyName = 'projects/YOUR_GCLOUD_PROJECT/locations/YOUR_LOCATION/keyRings/YOUR_KEYRING_NAME/cryptoKeys/YOUR_KEY_NAME';

  // The encrypted ('wrapped') AES-256 key to use
  // This key should be encrypted using the Cloud KMS key specified above
  // const wrappedKey = 'YOUR_ENCRYPTED_AES_256_KEY'

  // The name of the surrogate custom info type to use when reidentifying data
  // const surrogateType = 'SOME_INFO_TYPE_DEID';

  // Construct deidentification request
  const item = {value: string};
  const request = {
    parent: dlp.projectPath(callingProjectId),
    reidentifyConfig: {
      infoTypeTransformations: {
        transformations: [
          {
            primitiveTransformation: {
              cryptoReplaceFfxFpeConfig: {
                cryptoKey: {
                  kmsWrapped: {
                    wrappedKey: wrappedKey,
                    cryptoKeyName: keyName,
                  },
                },
                commonAlphabet: alphabet,
                surrogateInfoType: {
                  name: surrogateType,
                },
              },
            },
          },
        ],
      },
    },
    inspectConfig: {
      customInfoTypes: [
        {
          infoType: {
            name: surrogateType,
          },
          surrogateType: {},
        },
      ],
    },
    item: item,
  };

  // Run reidentification request
  dlp
    .reidentifyContent(request)
    .then(response => {
      const reidentifiedItem = response[0].item;
      console.log(reidentifiedItem.value);
    })
    .catch(err => {
      console.log(`Error in reidentifyWithFpe: ${err.message || err}`);
    });

Python

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

def deidentify_with_fpe(project, string, alphabet=None,
                        surrogate_type=None, key_name=None, wrapped_key=None):
    """Uses the Data Loss Prevention API to deidentify sensitive data in a
    string using Format Preserving Encryption (FPE).
    Args:
        project: The Google Cloud project id to use as a parent resource.
        item: The string to deidentify (will be treated as text).
        alphabet: The set of characters to replace sensitive ones with. For
            more information, see https://cloud.google.com/dlp/docs/reference/
            rest/v2beta2/organizations.deidentifyTemplates#ffxcommonnativealphabet
        surrogate_type: The name of the surrogate custom info type to use. Only
            necessary if you want to reverse the deidentification process. Can
            be essentially any arbitrary string, as long as it doesn't appear
            in your dataset otherwise.
        key_name: The name of the Cloud KMS key used to encrypt ('wrap') the
            AES-256 key. Example:
            key_name = 'projects/YOUR_GCLOUD_PROJECT/locations/YOUR_LOCATION/
            keyRings/YOUR_KEYRING_NAME/cryptoKeys/YOUR_KEY_NAME'
        wrapped_key: The encrypted ('wrapped') AES-256 key to use. This key
            should be encrypted using the Cloud KMS key specified by key_name.
    Returns:
        None; the response from the API is printed to the terminal.
    """
    # Import the client library
    import google.cloud.dlp

    # Instantiate a client
    dlp = google.cloud.dlp.DlpServiceClient()

    # Convert the project id into a full resource id.
    parent = dlp.project_path(project)

    # The wrapped key is base64-encoded, but the library expects a binary
    # string, so decode it here.
    import base64
    wrapped_key = base64.b64decode(wrapped_key)

    # Construct FPE configuration dictionary
    crypto_replace_ffx_fpe_config = {
        'crypto_key': {
            'kms_wrapped': {
                'wrapped_key': wrapped_key,
                'crypto_key_name': key_name
            }
        },
        'common_alphabet': alphabet
    }

    # Add surrogate type
    if surrogate_type:
        crypto_replace_ffx_fpe_config['surrogate_info_type'] = {
            'name': surrogate_type
        }

    # Construct deidentify configuration dictionary
    deidentify_config = {
        'info_type_transformations': {
            'transformations': [
                {
                    'primitive_transformation': {
                        'crypto_replace_ffx_fpe_config':
                            crypto_replace_ffx_fpe_config
                    }
                }
            ]
        }
    }

    # Convert string to item
    item = {'value': string}

    # Call the API
    response = dlp.deidentify_content(
        parent, deidentify_config=deidentify_config, item=item)

    # Print results
    print(response.item.value)

Go

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

// deidentifyFPE deidentifies the input with FPE (Format Preserving Encryption).
// keyFileName is the file name with the KMS wrapped key and cryptoKeyName is the
// full KMS key resource name used to wrap the key. surrogateInfoType is an
// optional identifier needed for reidentification. surrogateInfoType can be any
// value not found in your input.
func deidentifyFPE(w io.Writer, client *dlp.Client, project, input, keyFileName, cryptoKeyName, surrogateInfoType string) {
	// Read the key file.
	keyBytes, err := ioutil.ReadFile(keyFileName)
	if err != nil {
		log.Fatalf("error reading file: %v", err)
	}
	// Create a configured request.
	req := &dlppb.DeidentifyContentRequest{
		Parent: "projects/" + project,
		DeidentifyConfig: &dlppb.DeidentifyConfig{
			Transformation: &dlppb.DeidentifyConfig_InfoTypeTransformations{
				InfoTypeTransformations: &dlppb.InfoTypeTransformations{
					Transformations: []*dlppb.InfoTypeTransformations_InfoTypeTransformation{
						{
							InfoTypes: []*dlppb.InfoType{}, // Match all info types.
							PrimitiveTransformation: &dlppb.PrimitiveTransformation{
								Transformation: &dlppb.PrimitiveTransformation_CryptoReplaceFfxFpeConfig{
									CryptoReplaceFfxFpeConfig: &dlppb.CryptoReplaceFfxFpeConfig{
										CryptoKey: &dlppb.CryptoKey{
											Source: &dlppb.CryptoKey_KmsWrapped{
												KmsWrapped: &dlppb.KmsWrappedCryptoKey{
													WrappedKey:    keyBytes,
													CryptoKeyName: cryptoKeyName,
												},
											},
										},
										// Set the alphabet used for the output.
										Alphabet: &dlppb.CryptoReplaceFfxFpeConfig_CommonAlphabet{
											CommonAlphabet: dlppb.CryptoReplaceFfxFpeConfig_ALPHA_NUMERIC,
										},
										// Set the surrogate info type, used for reidentification.
										SurrogateInfoType: &dlppb.InfoType{
											Name: surrogateInfoType,
										},
									},
								},
							},
						},
					},
				},
			},
		},
		// The item to analyze.
		Item: &dlppb.ContentItem{
			DataItem: &dlppb.ContentItem_Value{
				Value: input,
			},
		},
	}
	// Send the request.
	r, err := client.DeidentifyContent(context.Background(), req)
	if err != nil {
		log.Fatal(err)
	}
	// Print the result.
	fmt.Fprint(w, r.GetItem().GetValue())
}

PHP

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

use Google\Cloud\Dlp\V2\CryptoReplaceFfxFpeConfig;
use Google\Cloud\Dlp\V2\CryptoReplaceFfxFpeConfig_FfxCommonNativeAlphabet;
use Google\Cloud\Dlp\V2\CryptoKey;
use Google\Cloud\Dlp\V2\DlpServiceClient;
use Google\Cloud\Dlp\V2\PrimitiveTransformation;
use Google\Cloud\Dlp\V2\KmsWrappedCryptoKey;
use Google\Cloud\Dlp\V2\InfoType;
use Google\Cloud\Dlp\V2\DeidentifyConfig;
use Google\Cloud\Dlp\V2\InfoTypeTransformations_InfoTypeTransformation;
use Google\Cloud\Dlp\V2\InfoTypeTransformations;
use Google\Cloud\Dlp\V2\ContentItem;

/**
 * Deidentify a string using Format-Preserving Encryption (FPE).
 *
 * @param string $callingProjectId The GCP Project ID to run the API call under
 * @param string $string The string to deidentify
 * @param string $keyName The name of the Cloud KMS key used to encrypt ('wrap') the AES-256 key
 * @param wrappedKey $wrappedKey The AES-256 key to use, encrypted ('wrapped') with the KMS key
 *        defined by $keyName.
 * @param string $surrogateTypeName Optional surrogate custom info type to enable
 *        reidentification. Can be essentially any arbitrary string that doesn't
 *        appear in your dataset'
 */
function deidentify_fpe(
    $callingProjectId,
    $string,
    $keyName,
    $wrappedKey,
    $surrogateTypeName = ''
) {
    // Instantiate a client.
    $dlp = new DlpServiceClient();

    // The infoTypes of information to mask
    $ssnInfoType = (new InfoType())
        ->setName('US_SOCIAL_SECURITY_NUMBER');
    $infoTypes = [$ssnInfoType];

    // Create the wrapped crypto key configuration object
    $kmsWrappedCryptoKey = (new KmsWrappedCryptoKey())
        ->setWrappedKey(base64_decode($wrappedKey))
        ->setCryptoKeyName($keyName);

    // The set of characters to replace sensitive ones with
    // For more information, see https://cloud.google.com/dlp/docs/reference/rest/V2/organizations.deidentifyTemplates#ffxcommonnativealphabet
    $commonAlphabet = CryptoReplaceFfxFpeConfig_FfxCommonNativeAlphabet::NUMERIC;

    // Create the crypto key configuration object
    $cryptoKey = (new CryptoKey())
        ->setKmsWrapped($kmsWrappedCryptoKey);

    // Create the crypto FFX FPE configuration object
    $cryptoReplaceFfxFpeConfig = (new CryptoReplaceFfxFpeConfig())
        ->setCryptoKey($cryptoKey)
        ->setCommonAlphabet($commonAlphabet);
    if ($surrogateTypeName) {
        $surrogateType = (new InfoType())
            ->setName($surrogateTypeName);
        $cryptoReplaceFfxFpeConfig->setSurrogateInfoType($surrogateType);
    }

    // Create the information transform configuration objects
    $primitiveTransformation = (new PrimitiveTransformation())
        ->setCryptoReplaceFfxFpeConfig($cryptoReplaceFfxFpeConfig);

    $infoTypeTransformation = (new InfoTypeTransformations_InfoTypeTransformation())
        ->setPrimitiveTransformation($primitiveTransformation);

    $infoTypeTransformations = (new InfoTypeTransformations())
        ->setTransformations([$infoTypeTransformation]);

    // Create the deidentification configuration object
    $deidentifyConfig = (new DeidentifyConfig())
        ->setInfoTypeTransformations($infoTypeTransformations);

    $content = (new ContentItem())
        ->setValue($string);

    $parent = $dlp->projectName($callingProjectId);

    // Run request
    $response = $dlp->deidentifyContent($parent, [
        'deidentifyConfig' => $deidentifyConfig,
        'item' => $content
    ]);

    $likelihoods = ['Unknown', 'Very unlikely', 'Unlikely', 'Possible',
                    'Likely', 'Very likely'];

    // Print the results
    $deidentifiedValue = $response->getItem()->getValue();
    print($deidentifiedValue);
}

C#

For more on installing and creating a DLP API client, refer to DLP API Client Libraries.

public static object DeidFpe(
    string projectId,
    string dataValue,
    string keyName,
    string wrappedKey,
    string alphabet)
{
    var deidentifyConfig = new DeidentifyConfig
    {
        InfoTypeTransformations = new InfoTypeTransformations
        {
            Transformations =
            {
                new InfoTypeTransformations.Types.InfoTypeTransformation
                {
                    PrimitiveTransformation = new PrimitiveTransformation
                    {
                        CryptoReplaceFfxFpeConfig = new CryptoReplaceFfxFpeConfig
                        {
                            CommonAlphabet = (FfxCommonNativeAlphabet) Enum.Parse(typeof(FfxCommonNativeAlphabet), alphabet),
                            CryptoKey = new CryptoKey
                            {
                                KmsWrapped = new KmsWrappedCryptoKey
                                {
                                    CryptoKeyName = keyName,
                                    WrappedKey = ByteString.FromBase64(wrappedKey)
                                }
                            },
                            SurrogateInfoType = new InfoType
                            {
                                Name = "TOKEN"
                            }
                        }
                    }
                }
            }
        }
    };

    DlpServiceClient dlp = DlpServiceClient.Create();
    var response = dlp.DeidentifyContent(
        new DeidentifyContentRequest
        {
            ParentAsProjectName = new ProjectName(projectId),
            DeidentifyConfig = deidentifyConfig,
            Item = new ContentItem { Value = dataValue }
        });

    Console.WriteLine($"Deidentified content: {response.Item.Value}");
    return 0;
}

fixedSizeBucketingConfig

The bucketing transformations—this one and bucketingConfig—serve to mask numerical data by “bucketing” it into ranges. The resulting number range is a hyphenated string consisting of a lower bound, a hyphen, and an upper bound.

Setting fixedSizeBucketingConfig to a FixedSizeBucketingConfig object buckets input values based on fixed size ranges. The FixedSizeBucketingConfig object consists of the following:

  • lowerBound: The lower bound value of all of the buckets. Values less than this one are grouped together in a single bucket.
  • upperBound: The upper bound value of all of the buckets. Values greater than this one are grouped together in a single bucket.
  • bucketSize: The size of each bucket other than the minimum and maximum buckets.

For example, if lowerBound is set to 10, upperBound is set to 89, and bucketSize is set to 10, then the following buckets would be used: -10, 10-20, 20-30, 30-40, 40-50, 50-60, 60-70, 70-80, 80-89, 89+.

For more information about the concept of bucketing, see Generalization and Bucketing.

bucketingConfig

The bucketingConfig transformation offers more flexibility than the other bucketing transformation, fixedSizeBucketingConfig. Instead of specifying upper and lower bounds and an interval value with which to create equal-sized buckets, you specify the maximum and minimum values for each bucket you want created. Each maximum and minimum value pair must have the same type.

Setting bucketingConfig to a BucketingConfig object specifies custom buckets. The BucketingConfig object consists of a buckets[] array of Bucket objects. Each Bucket object consists of the following:

  • min: The lower bound of the bucket’s range. Omit this value to create a bucket that has no lower bound.
  • max: The upper bound of the bucket’s range. Omit this value to create a bucket that has no upper bound.
  • replacementValue: The value with which to replace values that fall within the lower and upper bounds. If you don’t provide a replacementValue, a hyphenated min-max range will be used instead.

If a value falls outside of the defined ranges, the TransformationSummary returned will contain an error message.

For example, consider the following configuration for the bucketingConfig transformation:

"bucketingConfig":{
  "buckets":[
    {
      "min":{
        "integerValue":"1"
      },
      "max":{
        "integerValue":"30"
      },
      "replacementValue":{
        "stringValue":"LOW"
      }
    },
    {
      "min":{
        "integerValue":"31"
      },
      "max":{
        "integerValue":"65"
      },
      "replacementValue":{
        "stringValue":"MEDIUM"
      }
    },
    {
      "min":{
        "integerValue":"66"
      },
      "max":{
        "integerValue":"100"
      },
      "replacementValue":{
        "stringValue":"HIGH"
      }
    }
  ]
}

This defines the following behavior:

  • Integer values falling between 1 and 30 are masked by being replaced with LOW.
  • Integer values falling between 31-65 are masked by being replaced with MEDIUM.
  • Integer values falling between 66-100 are masked by being replaced with HIGH.

For more information about the concept of bucketing, see Generalization and Bucketing.

replaceWithInfoTypeConfig

Specifying replaceWithInfoTypeConfig replaces each matched value with the name of the infoType. The replaceWithInfoTypeConfig message has no arguments; specifying it enables its transformation.

For example, suppose you’ve specified replaceWithInfoTypeConfig for all PHONE_NUMBER infoTypes, and the following string is sent to the DLP API:

John Smith, 123 Main St, Seattle, WA 98122, 206-555-0123.

The returned string will be the following:

John Smith, 123 Main St, Seattle, WA 98122, PHONE_NUMBER.
timePartConfig

Setting timePartConfig to a TimePartConfig object preserves a portion of a matched value that includes Date, Timestamp, and TimeOfDay values. The TimePartConfig object consists of a partToExtract argument, which can be set to any of the TimePart enumerated values, including year, month, day of the month, and so on.

For example, suppose you’ve configured a timePartConfig transformation by setting partToExtract to YEAR. After sending the data in the first column below to the DLP API, you’d end up with the transformed values in the second column:

Original values Transformed values
9/21/1976 1976
6/7/1945 1945
1/20/2009 2009
7/4/1776 1776
8/1/1984 1984
4/21/1982 1982

Record Transformations

Record transformations (the RecordTransformations object) are only applied to values within tabular data that are identified as a specific infoType. Within RecordTransformations, there are two further subcategories of transformations:

  • fieldTransformations[]: Transformations that apply various field transformations.
  • recordSuppressions[]: Rules defining which records get suppressed completely. Records that match any suppression rule within recordSuppressions[] are omitted from the output.

Field Transformations

Each FieldTransformation object includes three arguments:

  • fields: One or more input fields (FieldID objects) to apply the transformation to.
  • condition: A condition (a RecordCondition object) that must evaluate to true for the transformation to be applied. For example, apply a bucket transformation to an age column of a record only if the ZIP code column for the same record is within a specific range. Or, redact a field only if the birthdate field puts a person's age at 85 or above.
  • One of the following two transformation type arguments. Specifying one is required:

Record Suppressions

In addition to applying transformations to field data, you can also instruct the DLP API to de-identify data by simply suppressing records when certain suppression conditions evaluate to true. You can apply both field transformations and record suppressions in the same request.

You set the recordSuppressions message of the RecordTransformations object to an array of one or more RecordSuppression objects.

Each RecordSuppression object contains a single RecordCondition object, which in turn contains a single Expressions object.

An Expressions object contains:

  • logicalOperator: One of the LogicalOperator enumerated types.
  • conditions: A Conditions object, containing an array of one or more Condition objects. A Condition is a comparison of a field value and another value, both of which be of type string, boolean, integer, double, Timestamp, or TimeofDay.

If the comparison evaluates to true, the record is suppressed, and vice-versa. If the compared values are not the same type, a warning is given and the condition evaluates to false.

Send feedback about...

Data Loss Prevention API