Yalnızca geliştiricilerin çoğu için uygun olan standart API istekleri yapmayı planlıyorsanız bütünlük kararları bölümüne atlayabilirsiniz. Bu sayfada, Android 4.4 (API düzeyi 19) veya sonraki sürümlerde desteklenen, bütünlük kararları için klasik API isteklerinde bulunma hakkında bilgi verilmektedir.
Dikkat edilmesi gereken noktalar
Standart ve klasik istekleri karşılaştırma
Uygulamanızın güvenlik ve kötüye kullanım karşıtı ihtiyaçlarına bağlı olarak standart istekler, klasik istekler veya bu ikisinin bir kombinasyonunu gönderebilirsiniz. Standart istekler tüm uygulamalar ve oyunlar için uygundur ve herhangi bir işlemin veya sunucu çağrısının gerçek olup olmadığını kontrol etmek için kullanılabilir. Aynı zamanda tekrar oynanabilirlik ve veri hırsızlığına karşı bir miktar koruma için Google Play'e yetki verir. Klasik isteklerin oluşturulması daha pahalıdır ve hırsızlığa ve belirli saldırı türlerine karşı koruma sağlamak için bunları doğru şekilde uygulamak sizin sorumluluğunuzdadır. Klasik istekler, standart isteklerden daha seyrek gönderilmelidir. Örneğin, son derece değerli veya hassas bir işlemin gerçek olup olmadığını kontrol etmek için ara sıra bir kerelik bir istek yapılması gerekir.
Aşağıdaki tabloda, iki istek türü arasındaki temel farklılıklar vurgulanmaktadır:
Standart API isteği | Klasik API isteği | |
---|---|---|
Ön koşullar | ||
Minimum Android SDK sürümü gerekli | Android 5.0 (API düzeyi 21) veya sonraki sürümler | Android 4.4 (API düzeyi 19) veya sonraki sürümler |
Google Play gereksinimleri | Google Play Store ve Google Play Hizmetleri | Google Play Store ve Google Play Hizmetleri |
Entegrasyon ayrıntıları | ||
API ısınması gerekiyor | ✔️ (birkaç saniye) | ❌ |
Tipik istek gecikmesi | Birkaç yüz milisaniye | Birkaç saniye |
Potansiyel istek sıklığı | Sık (herhangi bir işlem veya istek için isteğe bağlı kontrol) | Nadir (en yüksek değere sahip işlemler veya en hassas istekler için tek seferlik kontrol) |
Engelleme | Çoğu ısınma süresi 10 saniyeden kısa ancak bir sunucu araması içerdiğinden uzun zaman aşımı önerilir (ör. 1 dakika). Karar istekleri istemci tarafında gerçekleşir | İsteklerin çoğu 10 saniyeden kısa ancak bir sunucu çağrısı içerdiğinden uzun zaman aşımı önerilir (ör. 1 dakika) |
Bütünlük kararı jetonu | ||
Cihaz, uygulama ve hesap ayrıntılarını içerir | ✔️ | ✔️ |
Jetonu önbelleğe alma | Google Play tarafından korumalı cihaz üzerinde önbelleğe alma | Önerilmez |
Jetonun şifresini Google Play sunucusu üzerinden çözün ve doğrulayın | ✔️ | ✔️ |
Tipik şifre çözme işlemi sunucudan sunucuya istek gecikmesi | 10 saniyelik milisaniye (üç dokuzlu kullanılabilirlik) | 10 saniyelik milisaniye (üç dokuzlu kullanılabilirlik) |
Güvenli bir sunucu ortamında jetonların şifresini çözün ve yerel olarak doğrulayın | ❌ | ✔️ |
Jeton istemci taraflı şifre çözme ve doğrulama | ❌ | ❌ |
Bütünlük kararı güncelliği | Google Play tarafından yapılan bazı otomatik önbelleğe alma ve yenileme | Her istekte tüm kararlar yeniden hesaplandı |
Sınırlar | ||
Uygulama başına günlük istek sayısı | Varsayılan olarak 10.000 (artış istenebilir) | Varsayılan olarak 10.000 (artış istenebilir) |
Dakikada uygulama örneği başına istek sayısı | Hazırlamalar: Dakikada 5 Bütünlük jetonları: Herkese açık sınır yok* |
Bütünlük jetonları: Dakikada 5 |
Koruma | ||
Hileli değişikliklere ve benzer saldırılara karşı hafifletin | requestHash alanını kullan |
İstek verilerine göre içerik bağlamayla nonce alanını kullan |
Tekrar oynatmayı ve benzer saldırılara karşı hafifletin | Google Play tarafından otomatik etki azaltma | nonce alanını sunucu tarafı mantığıyla kullan |
* Kamu sınırları dışındaki talepler de dahil olmak üzere tüm talepler, yüksek değerlerde kamu dışı savunma sınırlarına tabidir
Nadiren klasik istekler gönderme
Bütünlük jetonu oluşturmak zaman, veri ve pil kullanır. Ayrıca her uygulamanın günlük olarak gönderebileceği maksimum klasik istek sayısı vardır. Bu nedenle, yalnızca standart bir isteğe ek garanti istediğinizde en yüksek değere sahip veya en hassas işlemlerin gerçek olup olmadığını kontrol etmek için klasik isteklerde bulunmanız gerekir. Yüksek sıklık veya düşük değerli işlemler için klasik istek göndermemeniz gerekir. Uygulama her ön plana gittiğinde veya arka planda birkaç dakikada bir klasik istek göndermeyin ve aynı anda çok sayıda cihazdan çağrı yapmayın. Çok fazla klasik istek çağrısı yapan bir uygulama, kullanıcıları yanlış uygulamalara karşı korumak için kısıtlanabilir.
Kararları önbelleğe almaktan kaçınma
Bir sonucun önbelleğe alınması, güvenilmeyen bir ortamdan alınan iyi bir kararın yeniden kullanıldığı saldırı ve tekrar gibi saldırı riskini artırır. Klasik bir istek oluşturmayı ve daha sonra kullanmak üzere bunu önbelleğe almayı düşünüyorsanız, isteğe bağlı olarak standart bir istek gerçekleştirmeniz önerilir. Standart istekler cihazda bir miktar önbelleğe alma işlemi içerir ancak Google Play, tekrar oynatma saldırıları ve hırsızlık riskini azaltmak için ek koruma teknikleri kullanır.
Klasik istekleri korumak için tek seferlik rastgele alanını kullanın
Play Integrity API, nonce
adlı bir alan sunar. Bu alan, uygulamanızı tekrar oynatma ve değişiklik saldırıları gibi belirli saldırılara karşı daha iyi korumak için kullanılabilir. Play Integrity API, bu alanda ayarladığınız değeri imzalı bütünlük yanıtının içinde döndürür. Uygulamanızı saldırılardan korumak için tek seferlik rastgele kod oluşturma ile ilgili kılavuzu dikkatli bir şekilde uygulayın.
Klasik istekleri eksponansiyel geri yüklemeyle yeniden deneme
Kararsız internet bağlantısı veya aşırı yüklü cihaz gibi ortam koşulları, cihaz bütünlüğü kontrollerinin başarısız olmasına neden olabilir. Bu durum, güvenilir olmayan bir cihaz için etiket oluşturulmasına yol açabilir. Bu senaryoları azaltmak için eksponansiyel geri yüklemeli bir yeniden deneme seçeneği ekleyin.
Genel bakış
Kullanıcı, uygulamanızda bütünlük kontrolüyle korumak istediğiniz yüksek değerli bir işlem gerçekleştirdiğinde aşağıdaki adımları tamamlayın:
- Uygulamanızın sunucu tarafı arka ucu, istemci tarafı mantığına benzersiz bir değer oluşturur ve gönderir. Geri kalan adımlarda bu mantığa "uygulamanız" denir.
- Uygulamanız, yüksek değere sahip işleminizin benzersiz değerinden ve içeriğinden
nonce
oluşturur. Ardından,nonce
iletken Play Integrity API'yi çağırır. - Uygulamanız, Play Integrity API'den imzalı ve şifrelenmiş bir karar alır.
- Uygulamanız, imzalı ve şifrelenmiş kararı uygulamanızın arka ucuna iletir.
- Uygulamanızın arka ucu, kararı bir Google Play sunucusuna gönderir. Google Play sunucusu sonucu çözer ve doğrular, sonuçları uygulamanızın arka ucuna döndürür.
- Uygulamanızın arka ucu, jeton yükünde yer alan sinyallere göre nasıl devam edeceğine karar verir.
- Uygulamanızın arka ucu, karar sonuçlarını uygulamanıza gönderir.
Tek seferlik rastgele sayı oluştur
Uygulamanızdaki bir işlemi Play Integrity API ile koruduğunuzda, ortadaki kişi (PITM) müdahale ve tekrar oynama saldırıları gibi belirli saldırı türlerini hafifletmek için nonce
alanından yararlanabilirsiniz. Play Integrity API, bu alanda ayarladığınız değeri, imzalı bütünlük yanıtının içinde döndürür.
nonce
alanında ayarlanan değer doğru biçimlendirilmiş olmalıdır:
String
- URL güvenli
- Base64 olarak kodlanır ve sarmalanmaz
- En az 16 karakter
- Maksimum 500 karakter
Play Integrity API'de nonce
alanını kullanmanın yaygın yollarından bazıları aşağıda verilmiştir. nonce
'den en güçlü korumayı elde etmek için aşağıdaki yöntemleri birleştirebilirsiniz.
İzinsiz değişikliklere karşı koruma sağlamak için istek karması ekleyin
Bir isteğin içeriğini izinsiz değişikliklere karşı korumak için klasik bir API isteğindeki nonce
parametresini, standart API isteğindeki requestHash
parametresine benzer şekilde kullanabilirsiniz.
Entegrasyon kararı talep ettiğinizde:
- Gerçekleşen kullanıcı işlemi veya sunucu isteğinden tüm kritik istek parametrelerinin (ör. kararlı bir istek serileştirmesinin SHA256'sı) özetini hesaplayın.
nonce
alanını hesaplanan özetin değerine ayarlamak içinsetNonce
değerini kullanın.
Entegrasyon kararı aldığınızda:
- Bütünlük jetonunun kodunu çözüp doğrulayın ve özeti
nonce
alanından alın. - İsteğin özetini, uygulamadakiyle aynı şekilde hesaplayın (ör. kararlı bir istek serileştirmesinin SHA256'sı).
- Uygulama tarafı ve sunucu tarafı özetlerini karşılaştırın. Eşleşmiyorlarsa istek güvenilir değildir.
Tekrar oynatma saldırılarına karşı korumak için benzersiz değerler ekleyin
Kötü amaçlı kullanıcıların Play Integrity API'den önceki yanıtları tekrar kullanmasını önlemek için her mesajı benzersiz şekilde tanımlamak amacıyla nonce
alanını kullanabilirsiniz.
Entegrasyon kararı talep ettiğinizde:
- Kötü amaçlı kullanıcıların tahmin edemeyeceği şekilde genel olarak benzersiz bir değer elde etme. Örneğin, sunucu tarafında oluşturulan kriptografik olarak güvenli rastgele bir sayı, böyle bir değer veya oturum ya da işlem kimliği gibi önceden var olan bir kimlik olabilir. Daha basit ve daha az güvenli bir varyant, cihazda rastgele bir sayı oluşturmaktır. 128 bit veya daha büyük değerler oluşturmanızı öneririz.
nonce
alanını 1. adımdaki benzersiz değere ayarlamak içinsetNonce()
öğesini çağırın.
Entegrasyon kararı aldığınızda:
- Bütünlük jetonunun kodunu çözüp doğrulayın ve
nonce
alanından benzersiz değeri edinin. - 1. adımdaki değer sunucuda oluşturulduysa alınan benzersiz değerin oluşturulan değerlerden biri olduğundan ve bu değerin ilk kez kullanıldığından emin olun (sunucunuzun, oluşturulan değerlerin kaydını uygun bir süre boyunca saklaması gerekir). Alınan benzersiz değer zaten kullanılmışsa veya kayıtta görünmüyorsa isteği reddedin
- Aksi takdirde, cihazda benzersiz değer oluşturulmuşsa alınan değerin ilk kez kullanılıp kullanılmadığını kontrol edin (sunucunuzun uygun bir süre boyunca zaten görülmüş değerlerin kaydını tutması gerekir). Alınan benzersiz değer zaten kullanılmışsa isteği reddedin.
İzinsiz değişiklik yapma ve tekrar oynama saldırılarına karşı her iki korumayı birlikte kullanın (önerilir)
nonce
alanını, aynı anda hem izinsiz hem de tekrar oynatma saldırılarına karşı koruma sağlamak için kullanılabilir. Bunu yapmak için yukarıda açıklandığı gibi benzersiz değeri oluşturun ve isteğinize ekleyin. Ardından, benzersiz değeri karmanın bir parçası olarak eklediğinizden emin olarak istek karmasını hesaplayın. Aşağıdaki iki yaklaşımı birleştiren bir uygulama:
Entegrasyon kararı talep ettiğinizde:
- Yüksek değerli işlemi kullanıcı başlatır.
- Tekrar oynatma saldırılarına karşı korumak için benzersiz değerler ekleme bölümünde açıklandığı şekilde bu işlem için benzersiz bir değer elde edin.
- Korumak istediğiniz bir iletiyi hazırlayın. 2. adımdaki benzersiz değeri iletiye ekleyin.
- Uygulamanız, korumak istediği mesajın özetini hesaplar. Bu işlem, Değişikliğe karşı koruma sağlamak için istek karması ekleme bölümünde açıklanmıştır. Mesaj benzersiz değeri içerdiğinden, benzersiz değer karmanın bir parçasıdır.
nonce
alanını önceki adımdan hesaplanan özete ayarlamak içinsetNonce()
kullanın.
Entegrasyon kararı aldığınızda:
- İstekten benzersiz değeri elde etme
- Bütünlük jetonunun kodunu çözüp doğrulayın ve özeti
nonce
alanından alın. - Değişikliğe karşı koruma sağlamak için istek karması ekleme bölümünde açıklandığı gibi özeti sunucu tarafında yeniden hesaplayın ve bütünlük jetonundan alınan özetle eşleşip eşleşmediğini kontrol edin.
- Tekrar oynatma saldırılarına karşı korumak için benzersiz değerler ekleyin bölümünde açıklandığı gibi benzersiz değerin geçerliliğini kontrol edin.
Aşağıdaki sıra şemasında, bu adımlar sunucu tarafı bir nonce
ile gösterilmektedir:
Entegrasyon kararı isteyin
nonce
oluşturduktan sonra, Google Play'den entegrasyon kararı isteyebilirsiniz. Bunun için aşağıdaki adımları uygulayın:
- Aşağıdaki örneklerde gösterildiği gibi bir
IntegrityManager
oluşturun. - İlişkili derleyicideki
setNonce()
yöntemiylenonce
sağlayarak birIntegrityTokenRequest
oluşturun. Yalnızca Google Play dışında dağıtılan uygulamalar ve SDK'lar, Google Cloud proje numaralarınısetCloudProjectNumber()
yöntemi aracılığıyla belirtmek zorundadır. Google Play'deki uygulamalar, Play Console'daki bir Cloud projesine bağlıdır ve istekte Cloud proje numarasını belirtmeleri gerekmez. requestIntegrityToken()
numarasını aramak için yöneticiyi kullanın veIntegrityTokenRequest
öğesini sağlayın.
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());
Üçlü
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(); }
Yerel
/// 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();
Entegrasyon kararının şifresini çözün ve doğrulayın
Entegrasyon kararı istediğinizde Play Integrity API imzalı bir yanıt jetonu sağlar. İsteğinize eklediğiniz nonce
, yanıt jetonunun bir parçası olur.
Jeton biçimi
Jeton, JSON Web Signature'ın (JWS) JSON Web Encryption (JWE) protokolü olan, iç içe yerleştirilmiş bir JSON Web Token (JWT) türüdür. JWE ve JWS bileşenleri, kompakt serileştirme kullanılarak temsil edilir.
Şifreleme / imzalama algoritmaları, çeşitli JWT uygulamalarında iyi bir şekilde desteklenir:
Google sunucularında şifre çözme ve doğrulama (önerilir)
Play Integrity API, Google'ın sunucularında entegrasyon kararının şifresini çözmenizi ve bu kararı doğrulamanıza olanak tanıyarak uygulamanızın güvenliğini artırır. Bunun için şu adımları uygulayın:
- Google Cloud projesinde uygulamanıza bağlı bir hizmet hesabı oluşturun. Bu hesap oluşturma işlemi sırasında hizmet hesabınıza Hizmet Hesabı Kullanıcısı ve Hizmet Kullanımı Tüketicisi rollerini vermeniz gerekir.
Uygulamanızın sunucusunda,
playintegrity
kapsamını kullanarak hizmet hesabı kimlik bilgilerinizden erişim jetonunu getirin ve aşağıdaki isteği yapın:playintegrity.googleapis.com/v1/PACKAGE_NAME:decodeIntegrityToken -d \ '{ "integrity_token": "INTEGRITY_TOKEN" }'
JSON yanıtını okuyun.
Şifresini çözün ve yerel olarak doğrulayın
Yanıt şifreleme anahtarlarınızı yönetmeyi ve indirmeyi seçerseniz döndürülen jetonun şifresini çözebilir ve kendi güvenli sunucu ortamınızda doğrulayabilirsiniz.
Döndürülen jetonu IntegrityTokenResponse#token()
yöntemini kullanarak alabilirsiniz.
Aşağıdaki örnekte, imza doğrulaması için Play Console'dan imza doğrulaması amacıyla AES anahtarının ve DER kodlamalı ortak EC anahtarının, uygulamanın arka ucundaki dile özgü (bizim durumumuzda Java programlama dili) anahtarlara nasıl çözüleceği gösterilmektedir. Anahtarların, varsayılan işaretler kullanılarak base64 olarak kodlandığını unutmayın.
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));
Ardından, ilk olarak bütünlük jetonunun (JWE parçası) şifresini çözmek için bu anahtarları kullanın, ardından iç içe yerleştirilmiş JWS parçasını doğrulayıp çıkarın.
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();
Ortaya çıkan yük bütünlük kararları içeren düz metin biçiminde bir jetondur.