This page describes how third-party SDKs can integrate *inline install*, a new
test feature for Google Play that presents Google Play app product details in a
half sheet interface. Inline install enables users to experience a seamless app
install flow without leaving the context of the app.

Third-party SDK developers can integrate the inline install feature into their
SDKs to enable app developers that use those SDKs to access inline installs for
their apps.

## Requirements

For the inline install half sheet interface to appear in an app:

- The minimum Google Play version must be **40.4**.
- The Android API level must be **23 or higher**.

## Process architecture

The inline install process architecture is shown in the following figure:
![](https://developer.android.com/static/distribute/images/ii-sdk-architecture.png) **Figure 1:** Overview of the inline install process architecture.

1. Google Play servers generate Authenticated Encryption with Associated Data (AEAD) encryption keys and ingest the keys into a Google Cloud Platform (GCP) Secret Manager instance.
2. The third-party integrator retrieves the AEAD key from GCP Secret Manager.
3. The third-party integrator encrypts the inline install [`Intent`](https://developer.android.com/reference/android/content/Intent) data, generates the ciphertext passed in the deep link used to invoke the inline install intent, and sends deep links to the client in responses.
4. When the deep link is followed, the Google Play app handles the intent.

To configure a third-party SDK to use the inline install process, complete the
following steps.

## Create service accounts in Google Cloud Project

In this step, you set up a service account using the [Google Cloud
Console](https://console.cloud.google.com/).

1. Set up a Google Cloud Project:
   - Create a Google Cloud organization. When you create a Google Workspace or Cloud Identity account and associate it with your domain name, the organization resource is automatically created. For details, refer to [Creating and managing organization resources](https://cloud.google.com/resource-manager/docs/creating-managing-organization).
   - Log in to the GCP Console using the Google Cloud account created in the previous step, then create a Google Cloud project. For details, refer to [Create a Google Cloud project](https://developers.google.com/workspace/guides/create-project).
2. Create a service account in the created Google Cloud project. The service account is used as a Google Cloud Identity to access the symmetric key on behalf of your servers. For details, refer to [Create a service
   account](https://cloud.google.com/iam/docs/service-accounts-create#creating).
3. Use the same Google Workspace Customer ID (GWCID) / Dasher ID that was inputted on the [interest form](https://docs.google.com/forms/d/e/1FAIpQLSdq7OVNxAA8NDtMyVWXN5189uwiFi9b_aOJKMeXzOFGTkao1w/viewform).
4. Create and download the private key of that service account.
5. Create a key for that service account. For details, see [Create a service
   account key](https://cloud.google.com/iam/docs/keys-create-delete#creating).
6. Download the service account key and keep it accessible on your server, as it's used for authentication to access Google Cloud resources for the symmetric keys. For details, see [Get a service account key](https://cloud.google.com/iam/docs/keys-list-get#get-key).

## Retrieve credentials

In this step, you retrieve the symmetric key from Secret Manager and store it
securely (for example, in a JSON file) on your own server storage. This key is
used to generate the inline install data ciphertext.
| **Important:** The keyset rotates roughly every month. You must fetch the latest key at least once a month, otherwise the inline install data ciphertext generated by stale keys cannot be validated on Google Play servers.

The `secret_id/secretId` values refer to the secret name inside the Secret
Manager; this name is generated by prepending *hsdp-3p-key-* to the
Play-provided value `sdk_id`. For example, if the `sdk_id` is *abc* , the secret
name is *hsdp-3p-key-abc*.

Secret versions are updated weekly on Tuesday at 2 PM UTC. The second-newest
keys continue to work until the next rotation, and key material should be
freshly fetched and stored on a weekly basis.
| **Note:** The following examples are adapted from Google Cloud's [Access a secret
| version](https://cloud.google.com/secret-manager/docs/access-secret-version) docs. Examples for a number of other languages can be found there.

### Python example

The following code example uses an access token stored in a JSON file to access
key material in GCP Secret Manager and print it to the console.
**Note:** Ensure your workstation is correctly set up for Python. For details, see [Prepare the Python environment](https://developer.android.com/distribute/marketing-tools/inline-installs-sdk#prep-python).  

    #!/usr/bin/env python3
    # Import the Secret Manager client library.
    from google.cloud import secretmanager
    from google.oauth2 import service_account
    import google_crc32c

    # Create a service account key file.
    service_account_key_file = "<json key file of the service account>"
    credentials = service_account.Credentials.from_service_account_file(service_account_key_file)

    # Create the Secret Manager client.
    client = secretmanager.SecretManagerServiceClient(
      credentials=credentials
    )

    # Build the resource name of the secret version.
    name = f"projects/prod-play-hsdp-3p-caller-auth/secrets/<secret_id>/versions/latest"

    # Access the secret version.
    response = client.access_secret_version(request={"name": name})

    # Verify payload checksum.
    crc32c = google_crc32c.Checksum()
    crc32c.update(response.payload.data)
    if response.payload.data_crc32c != int(crc32c.hexdigest(), 16):
        print("Data corruption detected.")

    # A keyset created with "tinkey create-keyset --key-template=AES256_GCM". Note
    # that this keyset has the secret key information in cleartext.
    keyset = response.payload.data.decode("UTF-8")

    # WARNING: Do not print the secret in a production environment. Please store it
    # in a secure storage.
    with open('<key file name>', 'w') as f:
        f.write(keyset)

### Java example

The following code example uses an access token stored in a JSON file to access
key material in GCP Secret Manager and write it to a JSON file.
| **Note:** If you don't want to use `CredentialsProvider` to pass the private key to a JSON file, use the instructions in [Set up Application Default
Credentials](https://cloud.google.com/docs/authentication/provide-credentials-adc) instead.  

    import static java.nio.charset.StandardCharsets.UTF_8;
    import com.google.api.gax.core.CredentialsProvider;
    import com.google.api.gax.core.FixedCredentialsProvider;
    import com.google.auth.oauth2.ServiceAccountCredentials;
    import com.google.cloud.secretmanager.v1.AccessSecretVersionResponse;
    import com.google.cloud.secretmanager.v1.SecretManagerServiceClient;
    import com.google.cloud.secretmanager.v1.SecretManagerServiceSettings;
    import com.google.cloud.secretmanager.v1.SecretVersionName;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.util.zip.CRC32C;
    import java.util.zip.Checksum;

    /** */
    final class ThirdPartySecretAccessGuide {

      private ThirdPartySecretAccessGuide() {}

      public static void main(String[] args) throws IOException {
        accessSecretVersion();
      }

      public static void accessSecretVersion() throws IOException {
        // TODO(developer): Replace these variables before running the sample.
        String projectId = "projectId";
        String secretId = "secretId";
        String versionId = "versionId";
        String accessTokenPrivateKeyPath = "path/to/credentials.json";
        String secretMaterialOutputPath = "path/to/secret.json";
        accessSecretVersion(
            projectId, secretId, versionId, accessTokenPrivateKeyPath, secretMaterialOutputPath);
      }

      // Access the payload for the given secret version if one exists. The version
      // can be a version number as a string (e.g. "5") or an alias (e.g. "latest").
      public static void accessSecretVersion(
          String projectId,
          String secretId,
          String versionId,
          String accessTokenPrivateKeyPath,
          String secretMaterialOutputPath)
          throws IOException {

        // We can explicitly instantiate the SecretManagerServiceClient (below) from a json file if we:
        // 1. Create a CredentialsProvider from a FileInputStream of the JSON file,
        CredentialsProvider credentialsProvider =
            FixedCredentialsProvider.create(
                ServiceAccountCredentials.fromStream(new FileInputStream(accessTokenPrivateKeyPath)));

        // 2. Build a SecretManagerService Settings object from that credentials provider, and
        SecretManagerServiceSettings secretManagerServiceSettings =
            SecretManagerServiceSettings.newBuilder()
                .setCredentialsProvider(credentialsProvider)
                .build();

        // 3. Initialize client that will be used to send requests by passing the settings object to
        // create(). This client only needs to be created once, and can be reused for multiple requests.
        // After completing all of your requests, call the "close" method on the client to safely clean
        // up any remaining background resources.
        try (SecretManagerServiceClient client =
            SecretManagerServiceClient.create(secretManagerServiceSettings)) {
          SecretVersionName secretVersionName = SecretVersionName.of(projectId, secretId, versionId);

          // Access the secret version.
          AccessSecretVersionResponse response = client.accessSecretVersion(secretVersionName);

          // Verify checksum. The used library is available in Java 9+.
          // If using Java 8, you may use the following:
          // https://github.com/google/guava/blob/e62d6a0456420d295089a9c319b7593a3eae4a83/guava/src/com/google/common/hash/Hashing.java#L395
          byte[] data = response.getPayload().getData().toByteArray();
          Checksum checksum = new CRC32C();
          checksum.update(data, 0, data.length);
          if (response.getPayload().getDataCrc32C() != checksum.getValue()) {
            System.out.printf("Data corruption detected.");
            return;
          }

          String payload = response.getPayload().getData().toStringUtf8();
          // Print the secret payload.
          //
          // WARNING: Do not print the secret in a production environment - this
          // snippet is showing how to access the secret material.
          System.out.printf("Plaintext: %s\n", payload);

          // Write the JSON secret material payload to a json file
          try (PrintWriter out =
              new PrintWriter(Files.newBufferedWriter(Paths.get(secretMaterialOutputPath), UTF_8))) {
            out.write(payload);
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
    }

#### Set Application Default Credentials

If you don't want to use `CredentialsProvider` to pass the private key to a JSON
file in the Java implementation, you can modify the implementation by setting
Application Default Credentials (ADC):

1. [Tell the client libraries](https://cloud.google.com/docs/authentication/provide-credentials-adc) where to find the service account key.
2. [Add Maven dependencies](https://cloud.google.com/secret-manager/docs/reference/libraries#client-libraries-install-java) to the Java project.
3. [Call `SecretManagerServiceClient.create()`](https://cloud.google.com/secret-manager/docs/reference/libraries#use), which picks up the authentication automatically (because of step 1).

These steps modify the Java implementation by:

- Eliminating the need to create the `CredentialsProvider` and `SecretManagerServiceSettings` objects.
- Changing the call to `SecretManagerServiceClient.create()` to include no arguments.

## Create ciphertext and generate deep link

In this step, you use the Tink cryptography library to create the `enifd`
(`InlineInstallData` ciphertext) from the `InlineInstallData` protobuf object.
The `InlineInstallData` proto is defined as follows:  

    syntax = "proto2";
    package hsdpexperiments;
    option java_package = "com.google.hsdpexperiments";
    option java_multiple_files = true;

    // InlineInstallData is used by 3p auth callers to generate "encrypted inline
    // flow data" (enifd) which is decrypted in PGS to verify authenticity and
    // freshness.
    message InlineInstallData {
      // The timestamp which indicates the time encrypted data is generated.
      // Used to validate freshness (i.e. generation time in past 4 hours).
      // Required.
      optional int64 timestamp_ms = 1;

      // The docid of the app that we want to open inline install page for.
      // This is the package name.
      // Required.
      optional string target_package_name = 2;

      // This is the name of the app requesting the ad from Google Ad Serving
      // system.
      // Required.
      optional string caller_package_name = 3;

      // This is the advertising id that will be collected by 3P Ad SDKs.
      // Optional.
      optional string advertising_id = 4;

      // This is used to indicate the network from where the inline install was
      // requested.
      // Required.
      optional string ad_network_id = 5;
    }

In this step you also construct the deep link URL using these parameters:

| Fields | Description | Required |
|---|---|---|
| id | The [package name](https://support.google.com/admob/answer/9972781) of the app to be installed. | Yes |
| inline | Set to `true` if inline install half sheet is requested; if `false`, the intent deep links to Google Play. | Yes |
| enifd | The encrypted identifier for 3P SDKs. | Yes |
| lft | An internal identifier. | Yes |
| 3pAuthCallerId | The SDK identifier. | Yes |
| listing | An optional parameter to specify the target for a [custom store listing](https://support.google.com/googleplay/android-developer/answer/9867158). | No |
| referrer | An optional [referrer](https://developer.android.com/google/play/installreferrer) tracking string. | No |

### Python example

| **Note:** Ensure your workstation is correctly set up for Python. For details, see [Prepare the Python environment](https://developer.android.com/distribute/marketing-tools/inline-installs-sdk#prep-python).

The following command generates Python code from `InlineInstallData.proto`:  

    protoc InlineInstallData.proto --python_out=.

The following Python sample code constructs `InlineInstallData` and encrypts it
with the symmetric key to create the ciphertext:  

    #!/usr/bin/env python3

    # Import the Secret Manager client library.
    import base64
    import time
    import inline_install_data_pb2 as InlineInstallData
    import tink
    from tink import aead
    from tink import cleartext_keyset_handle

    # Read the stored symmetric key.
    with open("example3psecret.json", "r") as f:
      keyset = f.read()

    """Encrypt and decrypt using AEAD."""
    # Register the AEAD key managers. This is needed to create an Aead primitive later.
    aead.register()

    # Create a keyset handle from the cleartext keyset in the previous
    # step. The keyset handle provides abstract access to the underlying keyset to
    # limit access of the raw key material. WARNING: In practice, it is unlikely
    # you will want to use a cleartext_keyset_handle, as it implies that your key
    # material is passed in cleartext, which is a security risk.
    keyset_handle = cleartext_keyset_handle.read(tink.JsonKeysetReader(keyset))

    # Retrieve the Aead primitive we want to use from the keyset handle.
    primitive = keyset_handle.primitive(aead.Aead)

    inlineInstallData = InlineInstallData.InlineInstallData()
    inlineInstallData.timestamp_ms = int(time.time() * 1000)
    inlineInstallData.target_package_name = "x.y.z"
    inlineInstallData.caller_package_name = "a.b.c"
    inlineInstallData.ad_network_id = "<sdk_id>"

    # Use the primitive to encrypt a message. In this case the primary key of the
    # keyset will be used (which is also the only key in this example).
    ciphertext = primitive.encrypt(inlineInstallData.SerializeToString(), b'<sdk_id>')
    print(f"InlineInstallData Ciphertext: {ciphertext}")

    # Base64 Encoded InlineInstallData Ciphertext
    enifd = base64.urlsafe_b64encode(ciphertext).decode('utf-8')
    print(enifd)

    # Deeplink
    print(f"https://play.google.com/d?id={inlineInstallData.target_package_name}\&inline=true\&enifd={enifd}\&lft=1\&3pAuthCallerId={inlineInstallData.ad_network_id}")

Execute the Python script by running the following command:  

    python <file_name>.py

### Java example

The following command generates Java code from `InlineInstallData.proto`:  

    protoc InlineInstallData.proto --java_out=.

The following Java sample code constructs `InlineInstallData` and encrypts it
with the symmetric key to create the ciphertext:  

    package com.google.hsdpexperiments;

    import static com.google.common.io.BaseEncoding.base64Url;
    import static java.nio.charset.StandardCharsets.UTF_8;

    import com.google.common.flags.Flag;
    import com.google.common.flags.FlagSpec;
    import com.google.common.flags.Flags;
    import com.google.crypto.tink.Aead;
    import com.google.crypto.tink.InsecureSecretKeyAccess;
    import com.google.crypto.tink.KeysetHandle;
    import com.google.crypto.tink.TinkJsonProtoKeysetFormat;
    import com.google.crypto.tink.aead.AeadConfig;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import java.security.Security;
    import java.time.Duration;
    import org.conscrypt.Conscrypt;

    /** info on encryption in https://github.com/google/tink#learn-more */
    final class ThirdPartyEnifdGuide {

      @FlagSpec(
          name = "third_party_id",
          help = "the identifier associated with the 3p for which to generate the enifd")
      private static final Flag<String> thirdPartyAuthCallerId = Flag.value("");

      @FlagSpec(name = "package_name", help = "the package name of the target app")
      private static final Flag<String> packageName = Flag.value("");


      @FlagSpec(name = "caller_package_name", help = "the package name of the caller app")
      private static final Flag<String> callerPackageName = Flag.value("");

      @FlagSpec(name = "secret_filename", help = "the path to the json file with the secret material")
      private static final Flag<String> secretFilename = Flag.value("");

      private ThirdPartyEnifdGuide() {}

      public static void main(String[] args) throws Exception {
        // parse flags
        Flags.parse(args);

        // File keyFile = new File(args[0]);
        Path keyFile = Paths.get(secretFilename.get());

        // Create structured inline flow data
        InlineInstallData idrp =
            InlineInstallData.newBuilder()
                .setTargetPackageName(packageName.get())
                .setCallerPackageName(callerPackageName.get())
                .setTimestampMs(System.currentTimeMillis())
                .setAdNetworkId(thirdPartyAuthCallerId.get())
                .build();

        // we can print this out here to make sure it's well formatted, this will help debug
        System.out.println(idrp.toString());

        // Register all AEAD key types with the Tink runtime.
        Conscrypt.checkAvailability();
        Security.addProvider(Conscrypt.newProvider());
        AeadConfig.register();

        // Read AEAD key downloaded from secretmanager into keysethandle
        KeysetHandle handle =
            TinkJsonProtoKeysetFormat.parseKeyset(
                new String(Files.readAllBytes(keyFile), UTF_8), InsecureSecretKeyAccess.get());

        // Generate enifd using tink library
        Aead aead = handle.getPrimitive(Aead.class);
        byte[] plaintext = idrp.toByteArray();
        byte[] ciphertext = aead.encrypt(plaintext, thirdPartyAuthCallerId.get().getBytes(UTF_8));
        String enifd = base64Url().omitPadding().encode(ciphertext);

        // Build deeplink, escaping ampersands (TODO: verify this is necessary while testing e2e)
        String deeplink =
            "https://play.google.com/d?id="
                + packageName.get()
                + "\\&inline=true\\&enifd="
                + enifd
                + "\\&lft=1\\&3pAuthCallerId="
                + thirdPartyAuthCallerId.get();

        System.out.println(deeplink);
      }
    }

Finally, build the Java program to a binary and invoke it using the following
code:  

    path/to/binary/ThirdPartyEnifdGuide --secret_filename=path/to/jsonfile/example3psecret.json --package_name=<package_name_of_target_app> --third_party_id=<3p_caller_auth_id>

- The `secret_filename` flag specifies the path to the JSON file containing the secret material.
- The `package_name` flag is the doc ID of the target app.
- The `third_party_id` flag is used to specify the third-party caller auth ID (that is, the `<sdk_id>`).

## Launch the Inline Install intent

To test the deep link that's generated during the previous step, connect an
Android device (ensure USB debugging is enabled) to a workstation that has ADB
installed and run the following command:  

    adb shell am start "<output_from_the_previous_python_or_java_code>"

| **Note:** To ensure `enifd` freshness (and to prevent attackers from compromising it), generate the `enifd` no earlier than four hours prior to invoking the inline install flow.

In the client code, send the intent using one of the following methods (Kotlin
or Java).  

### Kotlin

    val intent = Intent(Intent.ACTION_VIEW)
    val deepLinkUrl = "<output_from_the_previous_python_or_java_code>"
    intent.setPackage("com.android.vending")
    intent.data = Uri.parse(deepLinkUrl)
    val packageManager = context.getPackageManager()
    if (intent.resolveActivity(packageManager) != null) {
      startActivityForResult(intent, 0)
    } else {
      // Fallback to deep linking to full Play Store.
    }

### Java

    Intent intent = new Intent(Intent.ACTION_VIEW);
    String id = "exampleAppToBeInstalledId";
    String deepLinkUrl = "<output_from_the_previous_python_or_java_code>";
    intent.setPackage("com.android.vending");
    intent.setData(Uri.parse(deepLinkUrl));
    PackageManager packageManager = context.getPackageManager();
    if (intent.resolveActivity(packageManager) != null) {
      startActivityForResult(intent, 0);
    } else {
      // Fallback to deep linking to full Play Store.
    }

## Appendix

The following sections provide additional guidance on certain use cases.

### Prepare the Python environment

To run the Python sample code, set up the Python environment on your workstation
and install the required dependencies.

1. Set up the Python environment:

   1. Install python3.11 (if already installed, skip this step):

          sudo apt install python3.11

   2. Install pip:

          sudo apt-get install pip

   3. Install `virtualenv`:

          sudo apt install python3-virtualenv

   4. Create a virtual environment (required for Tink dependency):

          virtualenv inlineinstall --python=/usr/bin/python3.11

2. Enter the virtual environment:

       source inlineinstall/bin/activate

3. Update pip:

       python -m pip install --upgrade pip

4. Install required dependencies:

   1. Install Tink:

          pip install tink

   2. Install Google crc32c:

          pip install google-crc32c

   3. Install Secret Manager:

          pip install google-cloud-secret-manager

   4. Install the protobuf compiler:

          sudo apt install protobuf-compiler

### C++ enifd generation

The following is a C++ example that we have written and validated internally to
generate the `enifd`.
| **Note:** Some of the necessary `#include` directives may vary depending on how you organize your dependencies.

Generation of the `enifd` can be carried out using C++ code as follows:  

    // A command-line example for using Tink AEAD w/ key template aes128gcmsiv to
    // encrypt an InlineInstallData proto.
    #include <chrono>
    #include <iostream>
    #include <memory>
    #include <string>

    #include "<path_to_protoc_output>/inline_install_data.proto.h"
    #include "absl/flags/flag.h"
    #include "absl/flags/parse.h"
    #include "absl/strings/escaping.h"
    #include "absl/strings/string_view.h"
    #include "tink/cc/aead.h"
    #include "tink/cc/aead_config.h"
    #include "tink/cc/aead_key_templates.h"
    #include "tink/cc/config/global_registry.h"
    #include "tink/cc/examples/util/util.h"
    #include "tink/cc/keyset_handle.h"
    #include "tink/cc/util/status.h"
    #include "tink/cc/util/statusor.h"

    ABSL_FLAG(std::string, keyset_filename, "",
              "Keyset file (downloaded from secretmanager) in JSON format");
    ABSL_FLAG(std::string, associated_data, "",
              "Associated data for AEAD (default: empty");

    namespace {

    using ::crypto::tink::Aead;
    using ::crypto::tink::AeadConfig;
    using ::crypto::tink::KeysetHandle;
    using ::crypto::tink::util::Status;
    using ::crypto::tink::util::StatusOr;

    }  // namespace

    namespace tink_cc_examples {

    // AEAD example CLI implementation.
    void AeadCli(const std::string& keyset_filename,
                 absl::string_view associated_data) {
      Status result = AeadConfig::Register();
      if (!result.ok()) {
        std::clog << "Failed to register AeadConfig";
        return;
      }

      // Read the keyset from file.
      StatusOr<std::unique_ptr<KeysetHandle>> keyset_handle =
          ReadJsonCleartextKeyset(keyset_filename);
      if (!keyset_handle.ok()) {
        std::clog << "Failed to read json keyset";
        return;
      }

      // Get the primitive.
      StatusOr<std::unique_ptr<Aead>> aead =
          (*keyset_handle)
              ->GetPrimitive<crypto::tink::Aead>(
                  crypto::tink::ConfigGlobalRegistry());
      if (!aead.ok()) {
        std::clog << "Failed to get primitive";
        return;
      }

      // Instantiate the enifd.
      hsdpexperiments::InlineInstallData iid;

      iid.set_timestamp_ms(std::chrono::duration_cast<std::chrono::milliseconds>(
                               std::chrono::system_clock::now().time_since_epoch())
                               .count());
      iid.set_target_package_name("<TARGET_PACKAGE_NAME>");
      iid.set_caller_package_name("<CALLER_PACKAGE_NAME>");
      iid.set_ad_network_id("<SDK_ID>");

      // Compute the output.
      StatusOr<std::string> encrypt_result =
          (*aead)->Encrypt(iid.SerializeAsString(), associated_data);
      if (!encrypt_result.ok()) {
        std::clog << "Failed to encrypt Inline Install Data";
        return;
      }
      const std::string& output = encrypt_result.value();

      std::string enifd;
      absl::WebSafeBase64Escape(output, &enifd);

      std::clog << "enifd: " << enifd << '\n';
    }

    }  // namespace tink_cc_examples

    int main(int argc, char** argv) {
      absl::ParseCommandLine(argc, argv);

      std::string keyset_filename = absl::GetFlag(FLAGS_keyset_filename);
      std::string associated_data = absl::GetFlag(FLAGS_associated_data);

      std::clog << "Using keyset from file " << keyset_filename
                << " to AEAD-encrypt inline install data with associated data '"
                << associated_data << "'." << '\n';

      tink_cc_examples::AeadCli(keyset_filename, associated_data);
      return 0;
    }

This code has been adapted from a [sample](https://developers.google.com/tink/encrypt-data) which can be found in the
Tink docs.