ایجاد درخواست استاندارد میانای برنامه‌سازی کاربردی

این صفحه نحوه ارائه درخواست‌های استاندارد میانای برنامه‌سازی کاربردی برای حکم‌های یکپارچگی را شرح می‌دهد که در Android 5.0 (سطح میانای برنامه‌سازی کاربردی ۲۱) یا بالاتر پشتیبانی می‌شود. هرزمان برنامه‌تان برای بررسی واقعی بودن تعامل با سرور تماس می‌گیرد، می‌توانید درخواست استاندارد API برای حکم تمامیت ارائه دهید.

نمای کلی

شکل ۱. نمودار توالی که طراحی سطح بالای Play Integrity API را نشان می‌دهد.

درخواست استاندارد از دو بخش تشکیل شده است:

  • آماده‌سازی ارائه‌دهنده کد یکپارچگی (یک‌باره): باید Integrity API را فراخوانی کنید تا ارائه‌دهنده کد یکپارچگی را پیش‌از نیاز به دریافت حکم یکپارچگی آماده کنید. برای مثال، می‌توانید این کار را هنگام راه‌اندازی برنامه یا در پس‌زمینه قبل‌از اینکه به حکم یکپارچگی نیاز باشد انجام دهید.
  • درخواست کردن یک توکن تمامیت (درصورت درخواست): هرگاه برنامه‌تان درخواست سروری را ایجاد می‌کند که می‌خواهید واقعی بودن آن را بررسی کنید، یک توکن تمامیت درخواست می‌کنید و آن را برای رمزگشایی و درستی‌سنجی به سرور زیرینه برنامه‌تان ارسال می‌کنید. سپس سرور زیرینه شما می‌تواند تعیین کند که چگونه عمل کند.

ارائه‌دهنده کد یکپارچگی را آماده کنید (یک‌بار):

  1. برنامه شما با شماره پروژه Google Cloud شما با ارائه‌دهنده کد تمامیت تماس می‌گیرد.
  2. برنامه شما ارائه‌دهنده توکن تمامیت را برای فراخوانی‌های بررسی گواهی بیشتر در حافظه نگه می‌دارد.

درخواست کردن یک توکن تمامیت (درصورت درخواست):

  1. برای کنش کاربر که باید محافظت شود، برنامه‌تان درهم‌سازی درخواست را که باید انجام شود محاسبه می‌کند (بااستفاده از هر الگوریتم درهم‌سازی مناسبی مثل SHA256).
  2. برنامه شما کد تمامیت را درخواست می‌کند و درهم‌سازی درخواست را ارسال می‌کند.
  3. برنامه شما از Play Integrity API، کد یکپارچگی امضاشده و رمزگذاری‌شده را دریافت می‌کند.
  4. برنامه شما توکن تمامیت را به زیرینه برنامه‌تان ارسال می‌کند.
  5. زیرینه برنامه‌تان این توکن را به سرور Google Play ارسال می‌کند. سرور Google Play رأی را رمزگشایی و درستی‌سنجی می‌کند و نتایج را به باطن برنامه شما برمی‌گرداند.
  6. زیرینه برنامه‌تان براساس سیگنال‌های موجود در بار داده‌های کد تصمیم می‌گیرد که چگونه ادامه دهد.
  7. بخش پشتی برنامه شما نتایج تصمیم را به برنامه‌تان ارسال می‌کند.

آماده‌سازی ارائه‌دهنده توکن یکپارچگی (یک‌باره)

قبل‌از اینکه درخواست استانداردی برای حکم یکپارچگی از Google Play ارسال کنید، باید ارائه‌دهنده توکن یکپارچگی را آماده کنید (یا «گرم کنید»). این کار به Google Play اجازه می‌دهد اطلاعات گواهی جزئی را به‌طور هوشمند در حافظه نهان دستگاه ذخیره کند تا هنگام درخواست حکم یکپارچگی، تأخیر در مسیر بحرانی کاهش یابد. آماده‌سازی مجدد ارائه‌دهنده کد روشی برای تکرار بررسی‌های تمامیت با مصرف منابع کمتر است که باعث می‌شود حکم تمامیت بعدی که درخواست می‌کنید به‌روزتر باشد.

می‌توانید ارائه‌دهنده توکن یکپارچگی را آماده کنید:

  • وقتی برنامه‌تان راه‌اندازی می‌شود (یعنی هنگام راه‌اندازی سرد). آماده‌سازی ارائه‌دهنده کد ناهمگام است و بنابراین بر زمان راه‌اندازی تأثیر نخواهد گذاشت. این گزینه زمانی به‌خوبی کار می‌کند که قصد داشته باشید کمی پس‌از راه‌اندازی برنامه، درخواست حکم تمامیت کنید، مثلاً وقتی کاربر به سیستم وارد می‌شود یا بازیکن به بازی می‌پیوندد.
  • وقتی برنامه‌تان باز می‌شود (یعنی در راه‌اندازی گرم). بااین‌حال، توجه داشته باشید که هر نمونه برنامه فقط می‌تواند تا ۵ بار در دقیقه کد یکپارچگی را آماده کند.
  • هرزمان در پس‌زمینه که می‌خواهید توکن را پیش‌از درخواست حکم تمامیت آماده کنید.

برای آماده کردن ارائه‌دهنده توکن تمامیت، کارهای زیر را انجام دهید:

  1. همان‌طور که در مثال‌های زیر نشان داده شده است، StandardIntegrityManager ایجاد کنید.
  2. PrepareIntegrityTokenRequest را بسازید و شماره پروژه Google Cloud را ازطریق روش setCloudProjectNumber() ارائه دهید.
  3. از مدیر برای تماس با prepareIntegrityToken() استفاده کنید و PrepareIntegrityTokenRequest را ارائه دهید.

جاوا

import com.google.android.gms.tasks.Task;

// Create an instance of a manager.
StandardIntegrityManager standardIntegrityManager =
    IntegrityManagerFactory.createStandard(applicationContext);

StandardIntegrityTokenProvider integrityTokenProvider;
long cloudProjectNumber = ...;

// Prepare integrity token. Can be called once in a while to keep internal
// state fresh.
standardIntegrityManager.prepareIntegrityToken(
    PrepareIntegrityTokenRequest.builder()
        .setCloudProjectNumber(cloudProjectNumber)
        .build())
    .addOnSuccessListener(tokenProvider -> {
        integrityTokenProvider = tokenProvider;
    })
    .addOnFailureListener(exception -> handleError(exception));

Unity

IEnumerator PrepareIntegrityTokenCoroutine() {
    long cloudProjectNumber = ...;

    // Initialize the V2 manager. Requires Play Integrity Unity Plugin v2.0.0
    // or higher.
    var standardIntegrityManager = new StandardIntegrityManagerV2();

    // Request the token provider.
    var integrityTokenProviderOperation =
      standardIntegrityManager.PrepareIntegrityToken(
        new PrepareIntegrityTokenRequest(cloudProjectNumber));

    // Wait for PlayAsyncOperation to complete.
    yield return integrityTokenProviderOperation;

    // Check the resulting error code.
    if (integrityTokenProviderOperation.Error != null &&
        integrityTokenProviderOperation.Error.ErrorCode != StandardIntegrityErrorCode.NoError)
    {
        AppendStatusLog("StandardIntegrityAsyncOperation failed with error: " +
                integrityTokenProviderOperation.Error.ErrorCode);
        yield break;
    }

    // Get the response.
    var integrityTokenProvider = integrityTokenProviderOperation.GetResult();
}

Unreal Engine

// .h
void MyClass::OnPrepareIntegrityTokenCompleted(
  EStandardIntegrityErrorCode ErrorCode,
  UStandardIntegrityTokenProvider* Provider)
{
  // Check the resulting error code.
  if (ErrorCode == EStandardIntegrityErrorCode::StandardIntegrity_NO_ERROR)
  {
    // ...
  }
}

// .cpp
void MyClass::PrepareIntegrityToken()
{
  int64 CloudProjectNumber = ...

  // Create the Integrity Token Request.
  FPrepareIntegrityTokenRequest Request = { CloudProjectNumber };

  // Create a delegate to bind the callback function.
  FPrepareIntegrityOperationCompletedDelegate Delegate;

  // Bind the completion handler (OnPrepareIntegrityTokenCompleted) to the delegate.
  Delegate.BindDynamic(this, &MyClass::OnPrepareIntegrityTokenCompleted);

  // Initiate the prepare integrity token operation, passing the delegate to handle the result.
  GetGameInstance()
    ->GetSubsystem<UStandardIntegrityManager>()
    ->PrepareIntegrityToken(Request, Delegate);
}

بومی

/// Initialize StandardIntegrityManager
StandardIntegrityManager_init(/* app's java vm */, /* an android context */);
/// Create a PrepareIntegrityTokenRequest opaque object.
int64_t cloudProjectNumber = ...;
PrepareIntegrityTokenRequest* tokenProviderRequest;
PrepareIntegrityTokenRequest_create(&tokenProviderRequest);
PrepareIntegrityTokenRequest_setCloudProjectNumber(tokenProviderRequest, cloudProjectNumber);

/// Prepare a StandardIntegrityTokenProvider opaque type pointer and call
/// StandardIntegrityManager_prepareIntegrityToken().
StandardIntegrityTokenProvider* tokenProvider;
StandardIntegrityErrorCode error_code =
        StandardIntegrityManager_prepareIntegrityToken(tokenProviderRequest, &tokenProvider);

/// ...
/// Proceed to polling iff error_code == STANDARD_INTEGRITY_NO_ERROR
if (error_code != STANDARD_INTEGRITY_NO_ERROR)
{
    /// Remember to call the *_destroy() functions.
    return;
}
/// ...
/// Use polling to wait for the async operation to complete.

IntegrityResponseStatus token_provider_status;

/// Check for error codes.
StandardIntegrityErrorCode error_code =
        StandardIntegrityTokenProvider_getStatus(tokenProvider, &token_provider_status);
if (error_code == STANDARD_INTEGRITY_NO_ERROR
    && token_provider_status == INTEGRITY_RESPONSE_COMPLETED)
{
    /// continue to request token from the token provider
}
/// ...
/// Remember to free up resources.
PrepareIntegrityTokenRequest_destroy(tokenProviderRequest);

محافظت از درخواست‌ها دربرابر دست‌کاری (توصیه می‌شود)

وقتی کنش کاربر را در برنامه‌تان با Play Integrity API بررسی می‌کنید، می‌توانید از فیلد requestHash برای کاهش حملات دستکاری استفاده کنید. برای مثال، ممکن است بازی بخواهد امتیاز بازیکن را به سرور زیرینه بازی گزارش دهد و سرور شما بخواهد مطمئن شود که این امتیاز توسط پراکسی سرور دستکاری نشده است. Play Integrity API مقداری را که در فیلد requestHash تنظیم کرده‌اید در پاسخ یکپارچگی امضاشده برمی‌گرداند. بدون requestHash، کد یکپارچگی فقط به دستگاه محدود می‌شود، نه به درخواست خاص، که این امر امکان حمله را فراهم می‌کند. دستورالعمل‌های زیر نحوه استفاده مؤثر از فیلد requestHash را شرح می‌دهد:

وقتی حکم تمامیت درخواست می‌کنید:

  • خلاصه‌ای از همه پارامترهای درخواست مربوطه (مثلاً SHA256 از سریال‌سازی درخواست پایدار) را از کنش کاربر یا درخواست سرور که درحال وقوع است محاسبه کنید. مقدار تنظیم‌شده در فیلد requestHash حداکثر طول ۵۰۰ بایت دارد. هرگونه داده درخواست برنامه را که برای کنشی که درحال بررسی یا محافظت از آن هستید مهم یا مرتبط است در requestHash بگنجانید. فیلد requestHash به‌صورت کلمه‌به‌کلمه در توکن یکپارچگی گنجانده شده است، بنابراین مقادیر طولانی ممکن است اندازه درخواست را افزایش دهد.
  • خلاصه را به‌عنوان فیلد requestHash به Play Integrity API ارائه دهید و نشان درست‌کرداری را دریافت کنید.

وقتی حکم تمامیت دریافت می‌کنید:

  • رمزگشایی کردن کد یکپارچگی و استخراج فیلد requestHash.
  • خلاصه‌ای از درخواست را به همان روشی که در برنامه انجام می‌شود محاسبه کنید (مثلاً SHA256 از سریال‌سازی درخواست پایدار).
  • خلاصه‌های سمت برنامه و سمت سرور را مقایسه کنید. اگر مطابقت نداشته باشند، درخواست قابل‌اعتماد نیست.

درخواست حکم تمامیت (به‌محض درخواست)

پس‌از آماده کردن ارائه‌دهنده کد یکپارچگی، می‌توانید درخواست کردن حکم‌های یکپارچگی را از Google Play شروع کنید. برای انجام این کار، مراحل زیر را تکمیل کنید:

  1. StandardIntegrityTokenProvider دریافت کنید.
  2. یک StandardIntegrityTokenRequest بسازید و درهم‌سازی درخواست کنش کاربر را که می‌خواهید ازطریق روش setRequestHash محافظت شود ارائه دهید.
  3. از ارائه‌دهنده کد یکپارچگی برای فراخوانی request() استفاده کنید و StandardIntegrityTokenRequest را ارائه دهید.

جاوا

import com.google.android.gms.tasks.Task;

StandardIntegrityTokenProvider integrityTokenProvider;

// See above how to prepare integrityTokenProvider.

// Request integrity token by providing a user action request hash. Can be called
// several times for different user actions.
String requestHash = "2cp24z...";
Task<StandardIntegrityToken> integrityTokenResponse =
    integrityTokenProvider.request(
        StandardIntegrityTokenRequest.builder()
            .setRequestHash(requestHash)
            .build());
integrityTokenResponse
    .addOnSuccessListener(response -> sendToServer(response.token()))
    .addOnFailureListener(exception -> handleError(exception));

Unity

IEnumerator RequestIntegrityTokenCoroutine() {
    StandardIntegrityManagerV2.StandardIntegrityTokenProvider integrityTokenProvider;

    // See above how to prepare integrityTokenProvider.

    // Request integrity token by providing a user action request hash. Can be called
    // several times for different user actions.
    String requestHash = "2cp24z...";
    var integrityTokenOperation = integrityTokenProvider.Request(
      new StandardIntegrityTokenRequest(requestHash)
    );

    // Wait for PlayAsyncOperation to complete.
    yield return integrityTokenOperation;

    // Check the resulting error code.
    if (integrityTokenOperation.Error != null &&
        integrityTokenOperation.Error.ErrorCode != StandardIntegrityErrorCode.NoError)
    {
        AppendStatusLog("StandardIntegrityAsyncOperation failed with error: " +
                integrityTokenOperation.Error.ErrorCode);
        yield break;
    }

    // Get the response.
    var integrityToken = integrityTokenOperation.GetResult();
}

Unreal Engine

// .h
void MyClass::OnRequestIntegrityTokenCompleted(
  EStandardIntegrityErrorCode ErrorCode,
  UStandardIntegrityToken* Response)
{
  // Check the resulting error code.
  if (ErrorCode == EStandardIntegrityErrorCode::StandardIntegrity_NO_ERROR)
  {
    // Get the token.
    FString Token = Response->Token;
  }
}

// .cpp
void MyClass::RequestIntegrityToken()
{
  UStandardIntegrityTokenProvider* Provider = ...

  // Prepare the UStandardIntegrityTokenProvider.

  // Request integrity token by providing a user action request hash. Can be called
  // several times for different user actions.
  FString RequestHash = ...;
  FStandardIntegrityTokenRequest Request = { RequestHash };

  // Create a delegate to bind the callback function.
  FStandardIntegrityOperationCompletedDelegate Delegate;

  // Bind the completion handler (OnRequestIntegrityTokenCompleted) to the delegate.
  Delegate.BindDynamic(this, &MyClass::OnRequestIntegrityTokenCompleted);

  // Initiate the standard integrity token request, passing the delegate to handle the result.
  Provider->Request(Request, Delegate);
}

بومی

/// Create a StandardIntegrityTokenRequest opaque object.
const char* requestHash = ...;
StandardIntegrityTokenRequest* tokenRequest;
StandardIntegrityTokenRequest_create(&tokenRequest);
StandardIntegrityTokenRequest_setRequestHash(tokenRequest, requestHash);

/// Prepare a StandardIntegrityToken opaque type pointer and call
/// StandardIntegrityTokenProvider_request(). Can be called several times for
/// different user actions. See above how to prepare token provider.
StandardIntegrityToken* token;
StandardIntegrityErrorCode error_code =
        StandardIntegrityTokenProvider_request(tokenProvider, tokenRequest, &token);

/// ...
/// Proceed to polling iff error_code == STANDARD_INTEGRITY_NO_ERROR
if (error_code != STANDARD_INTEGRITY_NO_ERROR)
{
    /// Remember to call the *_destroy() functions.
    return;
}
/// ...
/// Use polling to wait for the async operation to complete.

IntegrityResponseStatus token_status;

/// Check for error codes.
StandardIntegrityErrorCode error_code =
        StandardIntegrityToken_getStatus(token, &token_status);
if (error_code == STANDARD_INTEGRITY_NO_ERROR
    && token_status == INTEGRITY_RESPONSE_COMPLETED)
{
    const char* integrityToken = StandardIntegrityToken_getToken(token);
}
/// ...
/// Remember to free up resources.
StandardIntegrityTokenRequest_destroy(tokenRequest);
StandardIntegrityToken_destroy(token);
StandardIntegrityTokenProvider_destroy(tokenProvider);
StandardIntegrityManager_destroy();

اگر برنامه‌تان برای مدت طولانی از یک ارائه‌دهنده توکن استفاده کند، ممکن است ارائه‌دهنده توکن منقضی شود که این امر منجر به خطای INTEGRITY_TOKEN_PROVIDER_INVALID در درخواست توکن بعدی می‌شود. باید این خطا را با درخواست ارائه‌دهنده جدید برطرف کنید.

رمزگشایی و درستی‌سنجی حکم تمامیت

پس‌از درخواست حکم یکپارچگی، Play Integrity API یک کد پاسخ رمزگذاری‌شده ارائه می‌دهد. برای دریافت حکم‌های تمامیت دستگاه، باید رمز نشانه یکپارچگی را در سرورهای Google رمزگشایی کنید. برای انجام این کار، این مراحل را تکمیل کنید:

  1. حساب سرویسی در پروژه Google Cloud که به برنامه‌تان پیوند داده شده است ایجاد کنید.
  2. در سرور برنامه‌تان، کد دسترسی را از اعتبارنامه حساب سرویس خود بااستفاده از محدوده playintegrity واکشی کنید و درخواست زیر را ارائه دهید:

    playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \
    '{ "integrity_token": "INTEGRITY_TOKEN" }'
  3. پاسخ JSON را بخوانید.

بار داده حاصل یک توکن نوشتار ساده است که حاوی حکم‌های تمامیت است.

محافظت خودکار دربرابر بازپخش

برای کاهش حملات بازپخش، Google Play به‌طور خودکار از استفاده مجدد چندباره از کدهای یکپارچگی جلوگیری می‌کند. تلاش برای رمزگشایی مکرر یک کد یکسان منجر به پاک شدن حکم‌ها به‌صورت زیر می‌شود:

  • رأی تشخیص دستگاه خالی خواهد بود.
  • حکم تشخیص برنامه و حکم صدور پروانه برنامه روی UNEVALUATED تنظیم خواهد شد.
  • هرکدام از رأی‌های اختیاری که بااستفاده از «کنسول Play» فعال شده‌اند روی UNEVALUATED (یا روی رأی خالی اگر رأی چندمقداری باشد) تنظیم خواهند شد.

اصلاح مشکلات حکم با پیام‌واره Google Play (اختیاری)

پس‌از اینکه سرورتان حکم تمامیت را دریافت کرد، می‌تواند تعیین کند که چگونه ادامه دهد. اگر حکم نشان دهد که مشکلی وجود دارد (مثلاً برنامه بدون پروانه است، دستکاری شده است، یا دستگاه درمعرض خطر قرار گرفته است)، می‌توانید به کاربران فرصت دهید تا خودشان مشکل را برطرف کنند.

Play Integrity API گزینه‌ای برای نمایش کادر گفتگوی Google Play ارائه می‌دهد که از کاربر می‌خواهد اقدام کند، برای مثال، نسخه رسمی برنامه‌تان را از Google Play دریافت کند.

برای آشنایی با نحوه راه‌اندازی این کادرهای گفتگو از برنامه‌تان براساس پاسخ سرور، کادرهای گفتگوی اصلاح را ببینید.