Сделать классический запрос API

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

Соображения

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

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

В следующей таблице показаны основные различия между двумя типами запросов:

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

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

Делайте классические запросы нечасто

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

Избегайте кэширования вердиктов

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

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

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

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

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

Обзор

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

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

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

Сгенерировать одноразовый код

Защищая действие в приложении с помощью API Play Integrity, вы можете использовать поле nonce для предотвращения определённых типов атак, таких как атаки с подменой данных типа «человек посередине» (PITM) и атаки с повторным воспроизведением. API Play Integrity возвращает значение, заданное в этом поле, в подписанном ответе проверки целостности.

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

  • String
  • URL-безопасность
  • Кодируется как Base64 и не переносится
  • Минимум 16 символов
  • Максимум 500 символов

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

Включать хеш запроса для защиты от подделки

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

При запросе вердикта о добросовестности:

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

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

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

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

Чтобы предотвратить повторное использование предыдущих ответов API Play Integrity злоумышленниками, можно использовать поле 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 связаны с проектом Cloud в Play Console и не требуют указания номера проекта Cloud в запросе.
  3. Используйте менеджер для вызова requestIntegrityToken() , предоставляя IntegrityTokenRequest .

Котлин

// 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())

Ява

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

Единство

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

    // Create an instance of a manager.
    var integrityManager = new IntegrityManager();

    // 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 != IntegrityErrorCode.NoError)
    {
        AppendStatusLog("IntegrityAsyncOperation failed with error: " +
                requestIntegrityTokenOperation.Error);
        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();

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

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

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

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

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

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

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

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

API Play Integrity позволяет расшифровать и проверить вердикт целостности на серверах 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 с использованием флагов по умолчанию.

Котлин

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

Ява

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

Котлин

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

Ява

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 (необязательно)

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

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

Чтобы узнать, как вызывать эти диалоги из вашего приложения на основе ответа сервера, см. раздел Диалоги исправления .