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

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

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

Обзор

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

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

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

Создать одноразовый номер

Когда вы защищаете действие в своем приложении с помощью Play Integrity API, вы можете использовать поле nonce для смягчения определенных типов атак, таких как атаки «человек посередине» (PITM) и атаки повторного воспроизведения. 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 на стороне сервера:

Диаграмма последовательности, показывающая, как защититься как от несанкционированного вмешательства, так и от атак повторного воспроизведения.

Запросить вердикт о целостности

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

  1. Создайте IntegrityManager , как показано в следующих примерах.
  2. Создайте IntegrityTokenRequest , предоставив nonce через метод setNonce() в связанном построителе. Приложения, распространяемые исключительно за пределами Google Play и SDK, также должны указывать номер своего проекта Google Cloud с помощью метода setCloudProjectNumber() . Приложения в Google Play связаны с облачным проектом в Play Console, и им не нужно указывать номер облачного проекта в запросе.
  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();
}

Родной

/// 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 с использованием флагов по умолчанию.

Котлин

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

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