Google Play 인라인 설치 (SDK)

이 페이지에서는 타사 SDK가 Google Play 앱 제품 세부 정보를 반쪽 시트 인터페이스로 표시하는 Google Play의 새로운 테스트 기능인 인라인 설치를 통합하는 방법을 설명합니다. 인라인 설치를 통해 사용자는 앱의 컨텍스트를 벗어나지 않고도 원활한 앱 설치 흐름을 경험할 수 있습니다.

타사 SDK 개발자는 인라인 설치 기능을 자신의 SDK에 통합하여 해당 SDK를 사용하는 앱 개발자가 앱에 대한 인라인 설치에 액세스할 수 있도록 할 수 있습니다.

요구사항

앱에 인라인 설치 반쪽 시트 인터페이스가 나타나도록 하려면 다음을 수행합니다.

  • 최소 Google Play 버전은 40.4여야 합니다.
  • Android API 레벨은 23 이상이어야 합니다.

프로세스 아키텍처

인라인 설치 프로세스 아키텍처는 다음 그림과 같습니다.

그림 1: 인라인 설치 프로세스 아키텍처 개요
  1. Google Play 서버는 연결된 데이터(AEAD) 암호화 키를 사용하여 인증된 암호화를 생성하고 Google Cloud Platform (GCP) Secret Manager 인스턴스에 키를 수집합니다.
  2. 타사 통합자는 GCP Secret Manager에서 AEAD 키를 검색합니다.
  3. 서드 파티 통합자는 인라인 설치 Intent 데이터를 암호화하고, 인라인 설치 인텐트를 호출하는 데 사용되는 딥 링크에서 전달되는 암호문을 생성하고, 응답에서 클라이언트로 딥 링크를 전송합니다.
  4. 딥 링크를 따라가면 Google Play 앱이 인텐트를 처리합니다.

타사 SDK가 인라인 설치 프로세스를 사용하도록 구성하려면 다음 단계를 완료하세요.

Google Cloud Project에서 서비스 계정 만들기

이 단계에서는 Google Cloud Console을 사용하여 서비스 계정을 설정합니다.

  1. Google Cloud 프로젝트 설정:
    • Google Cloud 조직을 만듭니다. Google Workspace 또는 Cloud ID 계정을 만들어 도메인 이름과 연결하면 조직 리소스가 자동으로 생성됩니다. 자세한 내용은 조직 리소스 만들기 및 관리를 참고하세요.
    • 이전 단계에서 만든 Google Cloud 계정을 사용하여 GCP 콘솔에 로그인한 다음 Google Cloud 프로젝트를 만듭니다. 자세한 내용은 Google Cloud 프로젝트 만들기를 참조하세요.
  2. 생성된 Google Cloud 프로젝트에서 서비스 계정을 만듭니다. 서비스 계정은 서버를 대신하여 대칭 키에 액세스하는 Google Cloud ID로 사용됩니다. 자세한 내용은 서비스 계정 만들기를 참조하세요.
  3. 관심 양식에 입력한 것과 동일한 Google Workspace 고객 ID (GWCID) / Dasher ID를 사용하세요.
  4. 해당 서비스 계정의 개인 키를 생성하고 다운로드합니다.
  5. 해당 서비스 계정의 키를 만듭니다. 자세한 내용은 서비스 계정 키 만들기를 참조하세요.
  6. 서비스 계정 키를 다운로드하여 서버에서 접근할 수 있도록 보관하세요. 이 키는 대칭 키에 대한 Google Cloud 리소스 액세스를 인증하는 데 사용됩니다. 자세한 내용은 서비스 계정 키 가져오기를 참조하세요.

자격 증명 검색

이 단계에서는 Secret Manager에서 대칭 키를 검색하여 자체 서버 저장소에 안전하게 저장합니다 (예: JSON 파일). 이 키는 인라인 설치 데이터 암호문을 생성하는 데 사용됩니다.

secret_id/secretId 값은 Secret Manager 내부의 비밀 이름을 참조합니다. 이 이름은 Play에서 제공하는 값 sdk_idhsdp-3p-key-를 추가하여 생성됩니다. 예를 들어, sdk_idabc이면 비밀번호 이름은 hsdp-3p-key-abc입니다.

비밀 버전은 매주 화요일 오후 2시 UTC에 업데이트됩니다. 두 번째로 새로운 키는 다음 교체 시점까지 계속 작동하며, 키 관련 자료는 매주 새로 가져와서 보관해야 합니다.

파이썬 예제

다음 코드 예시에서는 JSON 파일에 저장된 액세스 토큰을 사용하여 GCP Secret Manager의 키 자료에 액세스하고 콘솔에 출력합니다.

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

자바 예제

다음 코드 예제에서는 JSON 파일에 저장된 액세스 토큰을 사용하여 GCP Secret Manager의 키 자료에 액세스하고 이를 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();
      }
    }
  }
}

애플리케이션 기본 자격 증명 설정

Java 구현에서 CredentialsProvider를 사용하여 개인 키를 JSON 파일에 전달하지 않으려면 ADC (애플리케이션 기본 자격 증명)를 설정하여 구현을 수정할 수 있습니다.

  1. 클라이언트 라이브러리에 서비스 계정 키를 찾을 수 있는 위치를 알려주세요.
  2. Java 프로젝트에 Maven 종속성을 추가합니다.
  3. 1단계로 인해 인증을 자동으로 받는 SecretManagerServiceClient.create()를 호출합니다.

이러한 단계에서는 다음과 같이 Java 구현을 수정합니다.

  • CredentialsProviderSecretManagerServiceSettings 객체를 생성할 필요성이 없어졌습니다.
  • SecretManagerServiceClient.create()에 대한 호출을 변경하여 인수를 포함하지 않도록 합니다.

암호문을 생성하고 딥링크를 생성합니다.

이 단계에서는 Tink 암호화 라이브러리를 사용하여 InlineInstallData protobuf 객체에서 enifd(InlineInstallData 암호문)을 생성합니다. InlineInstallData 프로토는 다음과 같이 정의됩니다.

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

이 단계에서는 다음 매개변수를 사용하여 딥 링크 URL도 구성합니다.

필드 설명 필수
id 설치할 앱의 패키지 이름입니다.
인라인 인라인 설치 절반 시트가 요청되면 true로 설정합니다. false이면 의도가 Google Play로 딥 링크됩니다.
에니프드 3P SDK의 암호화된 식별자입니다.
왼쪽 내부 식별자.
3pAuthCallerId SDK 식별자.
등록정보 맞춤 스토어 등록정보의 타겟을 지정하는 선택적 매개변수입니다. 아니요
추천인 선택 사항인 참조자 추적 문자열입니다. 아니요

파이썬 예제

다음 명령은 InlineInstallData.proto에서 Python 코드를 생성합니다.

protoc InlineInstallData.proto --python_out=.

다음 Python 샘플 코드는 InlineInstallData를 구성하고 대칭 키로 암호화하여 암호문을 생성합니다.

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

다음 명령어를 실행하여 Python 스크립트를 실행합니다.

python <file_name>.py

자바 예제

다음 명령어는 InlineInstallData.proto에서 Java 코드를 생성합니다.

protoc InlineInstallData.proto --java_out=.

다음 Java 샘플 코드는 InlineInstallData를 구성하고 대칭 키로 암호화하여 암호문을 생성합니다.

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

마지막으로, 다음 코드를 사용하여 Java 프로그램을 바이너리로 빌드하고 호출합니다.

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>
  • secret_filename 플래그는 비밀 자료가 포함된 JSON 파일의 경로를 지정합니다.
  • package_name 플래그는 대상 앱의 문서 ID입니다.
  • third_party_id 플래그는 타사 호출자 인증 ID(즉, <sdk_id>)를 지정하는 데 사용됩니다.

인라인 설치 인텐트 실행

이전 단계에서 생성된 딥 링크를 테스트하려면 Android 기기를 연결하고 (USB 디버깅이 활성화되어 있는지 확인) ADB가 설치된 워크스테이션에 다음 명령을 실행합니다.

adb shell am start "<output_from_the_previous_python_or_java_code>"

클라이언트 코드에서 다음 메서드 중 하나 (Kotlin 또는 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.
}

자바

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

부록

다음 섹션에서는 특정 사용 사례에 관한 추가 안내를 제공합니다.

Python 환경 준비

Python 샘플 코드를 실행하려면 워크스테이션에서 Python 환경을 설정하고 필요한 종속 항목을 설치하세요.

  1. Python 환경 설정:

    1. python3.11을 설치합니다 (이미 설치되어 있는 경우 이 단계를 건너뜁니다):

      sudo apt install python3.11
      
    2. pip 설치:

      sudo apt-get install pip
      
    3. virtualenv 설치:

      sudo apt install python3-virtualenv
      
    4. 가상 환경을 만듭니다 (Tink 종속 항목에 필요).

      virtualenv inlineinstall --python=/usr/bin/python3.11
      
  2. 가상 환경으로 들어가세요:

    source inlineinstall/bin/activate
    
  3. pip 업데이트:

    python -m pip install --upgrade pip
    
  4. 필요한 종속성을 설치하세요:

    1. Tink 설치:

      pip install tink
      
    2. Google crc32c를 설치하세요:

      pip install google-crc32c
      
    3. Secret Manager 설치:

      pip install google-cloud-secret-manager
      
    4. protobuf 컴파일러를 설치하세요:

      sudo apt install protobuf-compiler
      

C++ enifd 생성

다음은 enifd를 생성하기 위해 내부적으로 작성하고 검증한 C++ 예제입니다.

enifd 생성은 다음과 같이 C++ 코드를 사용하여 수행할 수 있습니다.

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

이 코드는 Tink 문서에서 찾을 수 있는 샘플에서 가져온 것입니다.