Как выполнить классический запрос к API

Если вы планируете отправлять только стандартные запросы к API, которые подходят большинству разработчиков, перейдите к разделу Оценка целостности. На этой странице описано, как отправлять классические запросы к API для получения оценки целостности. Они поддерживаются в Android 4.4 (уровень API 19) и более поздних версиях.

Что нужно учесть

Сравнение стандартных и классических запросов

В зависимости от требований вашего приложения к безопасности и защите от злоупотреблений вы можете использовать стандартные и классические запросы, а также их комбинацию. Такие запросы подходят для любых приложений и игр. Они позволяют проверять, действительно ли действия или серверные вызовы исходят из вашего приложения, а также делегировать платформе Google Play некоторые функции по защите от кражи данных и атак повторного воспроизведения. Классические запросы дороже, и вы должны реализовать их так, чтобы не допустить кражи данных и определенных видов атак. Классические запросы следует отправлять реже, чем стандартные, например в качестве разовой проверки, чтобы убедиться, что ценное или конфиденциальное действие является подлинным.

В таблице ниже приводятся некоторые основные различия между этими типами запросов.

Стандартные запросы к API Классические запросы к API
Требования
Минимальная версия Android SDK* Android 6.0 (уровень API 23) или более поздней версии; Android 6.0 (уровень API 23) или более поздней версии;
Требования Google Play Google Play и сервисы Google Play Google Play и сервисы Google Play
Сведения об интеграции
Необходимость подготовки API ✔️ (несколько секунд) ❌
Типичная задержка при обработке запроса Несколько сотен миллисекунд Несколько секунд
Возможная частота запросов Часто (проверка любых действий или запросов по запросу) Низкая (разовая проверка наиболее важных действий или запросов доступа к наиболее конфиденциальным данным)
тайм-ауты; Большинство разогревов занимают менее 10 секунд, но они включают вызов сервера, поэтому рекомендуется установить длительное время ожидания (например, 1 минуту). Запросы на оценку выполняются на стороне клиента. Большинство запросов обрабатывается менее чем за 10 секунд, но поскольку они включают вызов сервера, рекомендуется установить длительное время ожидания (например, 1 минуту).
Токен оценки целостности
Содержит сведения об устройстве, приложении и аккаунте. ✔️ ✔️
Кеширование токенов Защищенное кеширование на устройстве от Google Play Не рекомендуется.
Расшифровка и проверка токена на сервере Google Play ✔️ ✔️
Типичная задержка при обработке межсерверного запроса на расшифровку Десятки миллисекунд с доступностью 99,9 % Десятки миллисекунд с доступностью 99,9 %
Расшифровка и проверка токена локально в безопасной серверной среде ❌ ✔️
Расшифровка и проверка токена на стороне клиента ❌ ❌
Актуальность оценки целостности Автоматическое кеширование и обновление в Google Play. Все заключения пересчитываются при каждом запросе
Лимиты
Запросы на приложение в день 10 000 по умолчанию (можно запросить увеличение) 10 000 по умолчанию (можно запросить увеличение)
Количество запросов на экземпляр приложения в минуту Прогрев: 5 в минуту.
Токены целостности: нет публичных ограничений.**
Токены целостности: 5 в минуту.
Защита
Защита от фальсификации и аналогичных злоупотреблений Использовать поле "requestHash" Используйте поле nonce с привязкой контента на основе данных запроса
Защита от атак повторного воспроизведения и аналогичных злоупотреблений Автоматическая защита Google Play Использование поля nonce в логике сервера

* Для Play Integrity API версии 1.4.0 и более поздних минимальная поддерживаемая версия Android SDK одинакова для обоих типов запросов и определяется значением minSdkVersion библиотеки. Для версии 1.3.0 и более ранних выпусков минимальная версия Android SDK – Android 5.0 (уровень API 21) для запросов к Standard API и Android 4.4 (уровень API 19) для запросов к Classic API.

** Все запросы, в том числе без общедоступных ограничений, подпадают под непубличные защитные ограничения при высоких значениях.

Как отправлять классические запросы нечасто

Генерация токена целостности требует времени, трафика и заряда батареи. Кроме того, для каждого приложения установлено максимальное количество классических запросов в день. Поэтому классические запросы следует использовать только для проверки наиболее важных действий или запросов доступа к наиболее конфиденциальным данным, когда вам нужна дополнительная гарантия. Не следует отправлять классические запросы для часто выполняемых или малозначимых действий. Не отправляйте классические запросы каждый раз, когда приложение переходит на передний план, или каждые несколько минут в фоновом режиме. Также не вызывайте API с большого количества устройств одновременно. Если приложение отправляет слишком много классических запросов, его работа может быть ограничена, чтобы защитить пользователей от неправильных реализаций.

Не кешируйте результаты

Кеширование оценки повышает риск атак, таких как эксфильтрация и повторное воспроизведение, когда положительная оценка повторно используется из ненадежной среды. Если вы хотите выполнить классический запрос, а затем кешировать его для последующего использования, рекомендуется вместо этого выполнить стандартный запрос по требованию. При стандартных запросах на устройстве выполняется кеширование, но Google Play использует дополнительные методы защиты, чтобы снизить риск атак повторного воспроизведения и кражи данных.

Как использовать поле nonce для защиты классических запросов

В Play Integrity API есть поле nonce, которое можно использовать для дополнительной защиты приложения от определенных атак, например атак с повторной передачей пакетов и вмешательством. Play Integrity API возвращает значение, заданное в этом поле, в подписанном ответе о целостности. Чтобы защитить приложение от атак, внимательно следуйте инструкциям по созданию одноразовых кодов.

Повторите классические запросы с экспоненциальной выдержкой

Проверка целостности устройства может завершиться неудачно из-за внешних факторов, например нестабильного подключения к интернету или перегрузки устройства. В результате для надежного устройства не будут созданы метки. Чтобы избежать этого, используйте экспоненциальную выдержку.

Обзор

Рисунок 1. Диаграмма последовательности, на которой показана общая схема работы Play Integrity API.

Когда пользователь выполняет в приложении ценное действие, которое вы хотите защитить с помощью проверки целостности, выполните следующие действия:

  1. Серверная часть приложения генерирует и отправляет уникальное значение клиентской части. В дальнейших инструкциях эта логика называется "приложением".
  2. Приложение создает nonce на основе уникального значения и контента ценного действия. Затем оно вызывает Play Integrity API, передавая nonce.
  3. Приложение получает подписанную и зашифрованную оценку от Play Integrity API.
  4. Приложение передает подписанную и зашифрованную оценку на свой внутренний сервер.
  5. Бэкенд вашего приложения отправляет оценку на сервер Google Play. Сервер Google Play расшифровывает и проверяет оценку, а затем возвращает результаты на внутренний сервер вашего приложения.
  6. Бэкенд приложения определяет, что делать дальше, на основе сигналов, содержащихся в полезной нагрузке токена.
  7. Бэкенд приложения отправляет результаты в приложение.

Как сгенерировать однократно используемое число

Если вы защищаете действие в приложении с помощью Play Integrity API, то можете использовать поле nonce, чтобы предотвратить определенные типы атак, например атаки посредника и атаки воспроизведения. Play Integrity API возвращает значение, которое вы задали в этом поле, в подписанном ответе о целостности.

Значение, заданное в поле nonce, должно быть правильно отформатировано:

  • String
  • Безопасный URL
  • Закодировано в формате Base64 без переноса строк.
  • Минимум 16 символов.
  • Строка длиной до 500 символов.

Ниже приведены распространенные способы использования поля nonce в Play Integrity API. Чтобы обеспечить максимальную защиту от nonce, вы можете комбинировать перечисленные ниже методы.

Добавьте хеш запроса для защиты от вмешательства

Параметр nonce в классическом запросе к API можно использовать так же, как параметр requestHash в стандартном запросе к API, чтобы защитить содержимое запроса от фальсификации.

Когда вы запрашиваете оценку целостности:

  1. Вычислите дайджест всех критически важных параметров запроса (например, SHA256 стабильной сериализации запроса) из выполняемого действия пользователя или запроса сервера.
  2. Используйте setNonce, чтобы задать для поля nonce значение вычисленного дайджеста.

Когда вы получаете оценку целостности:

  1. Декодируйте и проверьте токен целостности, а затем получите дайджест из поля nonce.
  2. Вычислите дайджест запроса так же, как в приложении (например, SHA256 стабильной сериализации запроса).
  3. Сравните дайджесты на стороне приложения и сервера. Если они не совпадают, запрос не заслуживает доверия.

Добавьте уникальные значения для защиты от атак повторного воспроизведения.

Чтобы злоумышленники не могли повторно использовать предыдущие ответы Play Integrity API, вы можете использовать поле nonce для уникальной идентификации каждого сообщения.

Когда вы запрашиваете оценку целостности:

  1. Получите глобально уникальное значение, которое злоумышленники не смогут угадать. Например, это может быть криптографически защищенное случайное число, сгенерированное на стороне сервера, или уже существующий идентификатор, например идентификатор сеанса или идентификатор транзакции. Более простой и менее безопасный вариант – сгенерировать случайное число на устройстве. Рекомендуем создавать значения размером не менее 128 бит.
  2. Вызовите setNonce(), чтобы задать для поля nonce уникальное значение, полученное на шаге 1.

Когда вы получаете оценку целостности:

  1. Декодируйте и проверьте токен целостности, а затем получите уникальное значение из поля nonce.
  2. Если значение из шага 1 было сгенерировано на сервере, убедитесь, что полученное уникальное значение было одним из сгенерированных значений и что оно используется впервые (вашему серверу потребуется вести учет сгенерированных значений в течение подходящего периода времени). Если полученное уникальное значение уже использовалось или не указано в записи, отклоните запрос.
  3. Если же уникальное значение было сгенерировано на устройстве, убедитесь, что полученное значение используется впервые (ваш сервер должен хранить записи о ранее использованных значениях в течение подходящего периода времени). Если полученное уникальное значение уже использовалось, отклоните запрос.

Комбинируйте защиту от вмешательства и атак повторного воспроизведения (рекомендуется)

Поле nonce можно использовать для защиты как от фальсификации, так и от атак повторного воспроизведения. Для этого сгенерируйте уникальное значение, как описано выше, и включите его в запрос. Затем вычислите хеш запроса, включив в него уникальное значение. Ниже приведен пример реализации, в которой сочетаются оба подхода.

Когда вы запрашиваете оценку целостности:

  1. Пользователь инициирует ценное действие.
  2. Получите уникальное значение для этого действия, как описано в разделе Добавьте уникальные значения для защиты от атак повторного воспроизведения.
  3. Подготовьте сообщение, которое хотите защитить. Включите в сообщение уникальное значение, полученное на шаге 2.
  4. Приложение вычисляет дайджест сообщения, которое нужно защитить, как описано в разделе Добавьте хеш запроса для защиты от вмешательства. Поскольку письмо содержит уникальное значение, оно является частью хеша.
  5. Используйте setNonce(), чтобы задать для поля nonce вычисленный на предыдущем шаге дайджест.

Когда вы получаете оценку целостности:

  1. Как получить уникальное значение из запроса
  2. Декодируйте и проверьте токен целостности, а затем получите дайджест из поля nonce.
  3. Как описано в разделе Добавьте хеш запроса для защиты от вмешательства, пересчитайте дайджест на стороне сервера и проверьте, совпадает ли он с дайджестом, полученным из токена целостности.
  4. Проверьте уникальное значение, как описано в разделе Добавьте уникальные значения для защиты от атак повторного воспроизведения.

На следующей схеме показаны эти шаги с серверным nonce:

Рисунок 2. Диаграмма последовательности, на которой показано, как защититься от фальсификации и атак с повторной передачей.

Как запросить оценку целостности

После создания nonce вы можете запросить оценку целостности в Google Play. Для этого выполните следующие действия:

  1. Создайте IntegrityManager, как показано в примерах ниже.
  2. Создайте объект IntegrityTokenRequest, передав nonce с помощью метода setNonce() в связанном конструкторе. Приложения, распространяемые исключительно за пределами Google Play, и SDK также должны указывать номер проекта Google Cloud с помощью метода setCloudProjectNumber(). Приложения в Google Play связаны с облачным проектом в Play Console, поэтому указывать номер облачного проекта в запросе не нужно.
  3. Используйте менеджер, чтобы вызвать requestIntegrityToken(), указав IntegrityTokenRequest.

Kotlin

// Receive the nonce from the secure server.
val nonce: String = ...

// Create an instance of a manager.
val integrityManager =
    IntegrityManagerFactory.create(applicationContext)

// Request the integrity token by providing a nonce.
val integrityTokenResponse: Task<IntegrityTokenResponse> =
    integrityManager.requestIntegrityToken(
        IntegrityTokenRequest.builder()
             .setNonce(nonce)
             .build())

Java

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

// Receive the nonce from the secure server.
String nonce = ...

// Create an instance of a manager.
IntegrityManager integrityManager =
    IntegrityManagerFactory.create(getApplicationContext());

// Request the integrity token by providing a nonce.
Task<IntegrityTokenResponse> integrityTokenResponse =
    integrityManager
        .requestIntegrityToken(
            IntegrityTokenRequest.builder().setNonce(nonce).build());

Unity

IEnumerator RequestIntegrityTokenCoroutine() {
    // Receive the nonce from the secure server.
    var nonce = ...

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

    // Request the integrity token by providing a nonce.
    var tokenRequest = new IntegrityTokenRequest(nonce);
    var requestIntegrityTokenOperation =
        integrityManager.RequestIntegrityToken(tokenRequest);

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

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

    // Get the response.
    var tokenResponse = requestIntegrityTokenOperation.GetResult();
}

Unreal Engine

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

// .cpp
void MyClass::RequestIntegrityToken()
{
  // Receive the nonce from the secure server.
  FString Nonce = ...

  // Create the Integrity Token Request.
  FIntegrityTokenRequest Request = { Nonce };

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

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

  // Initiate the integrity token request, passing the delegate to handle the result.
  GetGameInstance()
    ->GetSubsystem<UIntegrityManager>()
    ->RequestIntegrityToken(Request, Delegate);
}

Нативный код

/// Create an IntegrityTokenRequest opaque object.
const char* nonce = RequestNonceFromServer();
IntegrityTokenRequest* request;
IntegrityTokenRequest_create(&request);
IntegrityTokenRequest_setNonce(request, nonce);

/// Prepare an IntegrityTokenResponse opaque type pointer and call
/// IntegerityManager_requestIntegrityToken().
IntegrityTokenResponse* response;
IntegrityErrorCode error_code =
        IntegrityManager_requestIntegrityToken(request, &response);

/// ...
/// Proceed to polling iff error_code == INTEGRITY_NO_ERROR
if (error_code != INTEGRITY_NO_ERROR)
{
    /// Remember to call the *_destroy() functions.
    return;
}
/// ...
/// Use polling to wait for the async operation to complete.
/// Note, the polling shouldn't block the thread where the IntegrityManager
/// is running.

IntegrityResponseStatus response_status;

/// Check for error codes.
IntegrityErrorCode error_code =
        IntegrityTokenResponse_getStatus(response, &response_status);
if (error_code == INTEGRITY_NO_ERROR
    && response_status == INTEGRITY_RESPONSE_COMPLETED)
{
    const char* integrity_token = IntegrityTokenResponse_getToken(response);
    SendTokenToServer(integrity_token);
}
/// ...
/// Remember to free up resources.
IntegrityTokenRequest_destroy(request);
IntegrityTokenResponse_destroy(response);
IntegrityManager_destroy();

Как расшифровать и проверить оценку целостности

Когда вы запрашиваете оценку целостности, Play Integrity API предоставляет подписанный токен ответа. Значение nonce, которое вы указываете в запросе, становится частью токена ответа.

Формат токена

Токен представляет собой вложенный веб-токен JSON (JWT), то есть веб-шифрование JSON (JWE) веб-подписи JSON (JWS). Компоненты JWE и JWS представлены с помощью компактной сериализации.

Алгоритмы шифрования и подписи поддерживаются в различных реализациях JWT:

  • JWE использует A256KW для alg и A256GCM для enc.

  • JWS использует ES256.

Расшифровка и проверка на серверах Google (рекомендуется)

Play Integrity API позволяет расшифровывать и проверять оценку целостности на серверах Google, что повышает безопасность приложения. Для этого выполните следующие действия:

  1. Создайте сервисный аккаунт в проекте Google Cloud, связанном с вашим приложением.
  2. На сервере своего приложения получите токен доступа, созданный на основе ваших учетных данных для сервисного аккаунта, указав область действия playintegrity, и отправьте следующий запрос:

    playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \
    '{ "integrity_token": "INTEGRITY_TOKEN" }'
  3. Прочитайте ответ JSON.

Расшифровка и проверка на локальном устройстве

Если вы решите самостоятельно управлять ключами шифрования ответов и скачивать их, то сможете расшифровывать и проверять возвращенные токены в собственной защищенной серверной среде. Получить возвращенный токен можно с помощью метода IntegrityTokenResponse#token().

В примере ниже показано, как декодировать ключ AES и открытый ключ EC в кодировке DER для проверки подписи из Play Console в ключи, зависящие от языка (в нашем случае – языка программирования Java), в серверной части приложения. Обратите внимание, что ключи закодированы в формате Base64 с использованием флагов по умолчанию.

Kotlin

// base64OfEncodedDecryptionKey is provided through Play Console.
var decryptionKeyBytes: ByteArray =
    Base64.decode(base64OfEncodedDecryptionKey, Base64.DEFAULT)

// Deserialized encryption (symmetric) key.
var decryptionKey: SecretKey = SecretKeySpec(
    decryptionKeyBytes,
    /* offset= */ 0,
    AES_KEY_SIZE_BYTES,
    AES_KEY_TYPE
)

// base64OfEncodedVerificationKey is provided through Play Console.
var encodedVerificationKey: ByteArray =
    Base64.decode(base64OfEncodedVerificationKey, Base64.DEFAULT)

// Deserialized verification (public) key.
var verificationKey: PublicKey = KeyFactory.getInstance(EC_KEY_TYPE)
    .generatePublic(X509EncodedKeySpec(encodedVerificationKey))

Java

// base64OfEncodedDecryptionKey is provided through Play Console.
byte[] decryptionKeyBytes =
    Base64.decode(base64OfEncodedDecryptionKey, Base64.DEFAULT);

// Deserialized encryption (symmetric) key.
SecretKey decryptionKey =
    new SecretKeySpec(
        decryptionKeyBytes,
        /* offset= */ 0,
        AES_KEY_SIZE_BYTES,
        AES_KEY_TYPE);

// base64OfEncodedVerificationKey is provided through Play Console.
byte[] encodedVerificationKey =
    Base64.decode(base64OfEncodedVerificationKey, Base64.DEFAULT);
// Deserialized verification (public) key.
PublicKey verificationKey =
    KeyFactory.getInstance(EC_KEY_TYPE)
        .generatePublic(new X509EncodedKeySpec(encodedVerificationKey));

Затем используйте эти ключи, чтобы расшифровать токен целостности (часть JWE), а затем проверить и извлечь вложенную часть JWS.

Kotlin

val jwe: JsonWebEncryption =
    JsonWebStructure.fromCompactSerialization(integrityToken) as JsonWebEncryption
jwe.setKey(decryptionKey)

// This also decrypts the JWE token.
val compactJws: String = jwe.getPayload()

val jws: JsonWebSignature =
    JsonWebStructure.fromCompactSerialization(compactJws) as JsonWebSignature
jws.setKey(verificationKey)

// This also verifies the signature.
val payload: String = jws.getPayload()

Java

JsonWebEncryption jwe =
    (JsonWebEncryption)JsonWebStructure
        .fromCompactSerialization(integrityToken);
jwe.setKey(decryptionKey);

// This also decrypts the JWE token.
String compactJws = jwe.getPayload();

JsonWebSignature jws =
    (JsonWebSignature) JsonWebStructure.fromCompactSerialization(compactJws);
jws.setKey(verificationKey);

// This also verifies the signature.
String payload = jws.getPayload();

В результате вы получите токен в виде обычного текста, содержащий оценки целостности.

Устранение проблем с оценкой с помощью подсказки Google Play (необязательно)

Получив оценку целостности, сервер может определить, что делать дальше. Если оценка указывает на проблему, например приложение не лицензировано, в него внесены изменения или устройство взломано, вы можете дать пользователям возможность устранить проблему самостоятельно.

Play Integrity API позволяет показывать диалоговое окно Google Play, в котором пользователю предлагается выполнить определенное действие, например скачать официальную версию приложения из Google Play.

Подробнее о том, как вызывать эти диалоговые окна из приложения на основе ответа сервера…