Instalacje wbudowane w Google Play (pakiety SDK)

Na tej stronie opisujemy, jak pakiety SDK innych firm mogą integrować instalację bezpośrednią, czyli nową funkcję testową Google Play, która wyświetla szczegóły aplikacji z Google Play w interfejsie z półarkuszem. Instalacja wbudowana umożliwia użytkownikom bezproblemową instalację aplikacji bez opuszczania jej kontekstu.

Twórcy zestawów SDK innych firm mogą zintegrować funkcję instalacji wbudowanej ze swoimi zestawami SDK, aby umożliwić twórcom aplikacji korzystającym z tych zestawów SDK dostęp do instalacji wbudowanych swoich aplikacji.

Wymagania

Aby interfejs instalacji w trybie półarkusza był wyświetlany w aplikacji:

  • Minimalna wersja Google Play to 40.4.
  • Poziom interfejsu API Androida musi wynosić 23 lub więcej.

Architektura procesów

Architektura procesu instalacji wbudowanej jest pokazana na poniższym rysunku:

Rysunek 1: Przegląd architektury procesu instalacji wbudowanej.
  1. Serwery Google Play generują klucze szyfrowania Authenticated Encryption with Associated Data (AEAD) i przesyłają je do instancji Google Cloud Platform (GCP) Secret Manager.
  2. Zewnętrzny integrator pobiera klucz AEAD z GCP Secret Manager.
  3. Zewnętrzny integrator szyfruje dane instalacji wbudowanej Intent, generuje tekst zaszyfrowany przekazywany w głębokim łączu używanym do wywołania intencji instalacji wbudowanej i wysyła głębokie łącza do klienta w odpowiedziach.
  4. Po kliknięciu głębokiego linku intencja jest obsługiwana przez aplikację Google Play.

Aby skonfigurować zestaw SDK innej firmy do korzystania z procesu instalacji wbudowanej, wykonaj następujące kroki.

Utwórz konta usług w projekcie Google Cloud

W tym kroku skonfigurujesz konto usługi za pomocą Konsoli Google Cloud.

  1. Skonfiguruj projekt Google Cloud:
    • Utwórz organizację Google Cloud. Po utworzeniu konta Google Workspace lub Cloud Identity i powiązaniu go z nazwą domeny zasób organizacji zostanie utworzony automatycznie. Aby uzyskać szczegółowe informacje, zapoznaj się z artykułem Tworzenie i zarządzanie zasobami organizacji.
    • Zaloguj się do konsoli GCP przy użyciu konta Google Cloud utworzonego w poprzednim kroku, a następnie utwórz projekt Google Cloud. Aby uzyskać szczegółowe informacje, zobacz Tworzenie projektu Google Cloud.
  2. Utwórz konto usługi w utworzonym projekcie Google Cloud. Konto usługi służy jako tożsamość Google Cloud w celu uzyskania dostępu do klucza symetrycznego w imieniu serwerów. Aby uzyskać szczegółowe informacje, zapoznaj się z artykułem Tworzenie konta usługi.
  3. Użyj tego samego identyfikatora klienta Google Workspace (GWCID) lub identyfikatora Dasher, który został wpisany w formularzu zainteresowania.
  4. Utwórz i pobierz klucz prywatny tego konta usługi.
  5. Utwórz klucz dla tego konta usługi. Aby uzyskać szczegółowe informacje, zobacz Tworzenie klucza konta usługi.
  6. Pobierz klucz konta usługi i przechowuj go na serwerze, ponieważ służy on do uwierzytelniania dostępu do zasobów Google Cloud dla kluczy symetrycznych. Aby uzyskać szczegółowe informacje, zobacz Uzyskiwanie klucza konta usługi.

Pobierz dane uwierzytelniające

W tym kroku pobierasz klucz symetryczny z Secret Manager i bezpiecznie go przechowujesz (na przykład w pliku JSON) w pamięci masowej swojego serwera. Klucz ten służy do generowania szyfrogramu danych instalacyjnych.

Wartości secret_id/secretId odnoszą się do tajnej nazwy w Menedżerze sekretów. Nazwa ta jest generowana przez dodanie hsdp-3p-key- do wartości sdk_id podanej przez Play. Na przykład, jeśli sdk_id to abc, nazwa tajna to hsdp-3p-key-abc.

Wersje tajne są aktualizowane co tydzień, we wtorek o godzinie 14:00 UTC. Drugie najnowsze klucze działają aż do następnej rotacji, a materiał kluczowy powinien być pobierany na nowo i przechowywany co tydzień.

Przykład Pythona

Poniższy przykład kodu wykorzystuje token dostępu zapisany w pliku JSON w celu uzyskania dostępu do klucza w GCP Secret Manager i wydrukowania go na konsoli.

#!/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)

Przykład Java

Poniższy przykład kodu wykorzystuje token dostępu zapisany w pliku JSON w celu uzyskania dostępu do klucza w GCP Secret Manager i zapisania go w pliku JSON.

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();
      }
    }
  }
}

Konfigurowanie domyślnego uwierzytelniania aplikacji

Jeśli nie chcesz używać CredentialsProvider do przekazywania klucza prywatnego do pliku JSON w implementacji Java, możesz zmodyfikować implementację, ustawiając domyślne poświadczenia aplikacji (ADC):

  1. Poinformuj biblioteki klienta, gdzie znaleźć klucz konta usługi.
  2. Dodaj zależności Maven do projektu Java.
  3. Wywołaj SecretManagerServiceClient.create(), który automatycznie przejmie uwierzytelnianie (z powodu kroku 1).

Poniższe kroki modyfikują implementację języka Java poprzez:

  • Eliminuje to konieczność tworzenia obiektów CredentialsProviderSecretManagerServiceSettings.
  • Zmiana wywołania SecretManagerServiceClient.create() tak, aby nie zawierało żadnych argumentów.

Tworzenie tekstu zaszyfrowanego i generowanie precyzyjnego linku

W tym kroku użyjesz biblioteki kryptograficznej Tink, aby utworzyć enifd (szyfrowany tekst InlineInstallData) z obiektu protobuf InlineInstallData. Prototyp InlineInstallData jest definiowany następująco:

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;
}

W tym kroku tworzysz również adres URL głębokiego linku, korzystając z następujących parametrów:

Fieldsem Opis Wymagane
id Nazwa pakietu aplikacji, która ma zostać zainstalowana. Tak
w treści Ustaw na true, jeśli żądana jest instalacja w trybie inline połowy arkusza; jeśli false, intencja zawiera głębokie linki do Google Play. Tak
enifd Zaszyfrowany identyfikator zestawów SDK 3P. Tak
lew Identyfikator wewnętrzny. Tak
3pAuthCallerId Identyfikator zestawu SDK. Tak
informacje | strona Opcjonalny parametr określający cel niestandardowego wpisu w sklepie. Nie
polecający Opcjonalny ciąg śledzenia referrer. Nie

Przykład Pythona

Poniższe polecenie generuje kod Pythona z InlineInstallData.proto:

protoc InlineInstallData.proto --python_out=.

Poniższy przykładowy kod w Pythonie tworzy InlineInstallData i szyfruje go kluczem symetrycznym, aby utworzyć tekst zaszyfrowany:

#!/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}")

Wykonaj skrypt Pythona, uruchamiając następujące polecenie:

python <file_name>.py

Przykład Java

Poniższe polecenie generuje kod Java z InlineInstallData.proto:

protoc InlineInstallData.proto --java_out=.

Poniższy przykładowy kod Java konstruuje InlineInstallData i szyfruje go kluczem symetrycznym w celu utworzenia tekstu zaszyfrowanego:

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);
  }
}

Na koniec skompiluj program Java do pliku binarnego i wywołaj go za pomocą następującego kodu:

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>
  • Flaga secret_filename określa ścieżkę do pliku JSON zawierającego tajny materiał.
  • Flaga package_name to identyfikator dokumentu aplikacji docelowej.
  • Flaga third_party_id służy do określenia identyfikatora uwierzytelnienia osoby trzeciej wywołującej (czyli <sdk_id>).

Uruchom intencję instalacji wbudowanej

Aby przetestować głęboki link wygenerowany w poprzednim kroku, podłącz urządzenie z systemem Android (upewnij się, że debugowanie USB jest włączone) do stacji roboczej, na której zainstalowano ADB, i uruchom następujące polecenie:

adb shell am start "<output_from_the_previous_python_or_java_code>"

W kodzie klienta wyślij intencję, korzystając z jednej z następujących metod (Kotlin lub 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.
}

Dodatek

Poniższe sekcje zawierają dodatkowe wskazówki dotyczące niektórych przypadków użycia.

Przygotuj środowisko Pythona

Aby uruchomić przykładowy kod Pythona, skonfiguruj środowisko Pythona na swojej stacji roboczej i zainstaluj wymagane zależności.

  1. Skonfiguruj środowisko Pythona:

    1. Zainstaluj python 3.11 (jeśli jest już zainstalowany, pomiń ten krok):

      sudo apt install python3.11
      
    2. Zainstaluj pip:

      sudo apt-get install pip
      
    3. Zainstaluj virtualenv:

      sudo apt install python3-virtualenv
      
    4. Utwórz środowisko wirtualne (wymagane w przypadku zależności Tink):

      virtualenv inlineinstall --python=/usr/bin/python3.11
      
  2. Wejdź do wirtualnego środowiska:

    source inlineinstall/bin/activate
    
  3. Aktualizacja pip:

    python -m pip install --upgrade pip
    
  4. Zainstaluj wymagane zależności:

    1. Zainstaluj Tink:

      pip install tink
      
    2. Zainstaluj Google crc32c:

      pip install google-crc32c
      
    3. Zainstaluj Secret Manager:

      pip install google-cloud-secret-manager
      
    4. Zainstaluj kompilator protobuf:

      sudo apt install protobuf-compiler
      

Generowanie enifd w C++

Poniżej znajduje się przykład C++, który napisaliśmy i sprawdziliśmy wewnętrznie w celu wygenerowania enifd.

Generowanie enifd można przeprowadzić przy użyciu kodu C++ w następujący sposób:

// 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;
}

Kod ten został zaadaptowany z przykładu, który można znaleźć w dokumentacji Tink.