This article describes how to customize Play anti-tamper protection for your apps and games. To use the customization features described on this page, you must have automatic protection with anti-tamper protection turned on in the Play Console.
About customized anti-tamper protection
Customized anti-tamper protection allows you to identify specific Java and Kotlin methods within your app that require stronger security. When your release is uploaded to Play, Google Play prioritizes enhanced protection for these specific methods.
By identifying methods for enhanced protection, you can improve your app's defense against tampering. You can also use method protection to guard against the exfiltration of client secrets from your binary (for example, sensitive keys or URLs that must be included in the client).
Set up customized anti-tamper protection
Follow these steps to implement customized anti-tamper protection in your app's codebase.
Step 1: Add the automatic protection interface to your codebase
First, add the protection interface to your app or game.
Kotlin:
package com.google.android.play.protections.annotations/**
* Describes that a method is a candidate for enhanced protection.
*
* <p>This annotation will be removed automatically from the dex code of your
* bundle at the time when Google Play protects your bundle. Only use it as
* a method level annotation. Any other usage (e.g. accessing the class
* reflectively at runtime) is unsupported.
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
public annotation class PlayAutomaticIntegrityProtection() {}
Java:
package com.google.android.play.protections.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Describes that a method is a candidate for enhanced protection.
*
* <p>This annotation will be removed automatically from the dex code of your
* bundle at the time when Google Play protects your bundle. Only use it as
* a method level annotation. Any other usage (e.g. accessing the class
* reflectively at runtime) is unsupported.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PlayAutomaticIntegrityProtection {}
# Keep the annotation class and its members.
-keep class com.google.android.play.protections.annotations.PlayAutomaticIntegrityProtection* { *; }
# By default, ProGuard treats annotation attributes as optional, and removes
# them in the obfuscation step. This rule will ensure they are kept.
-keepattributes RuntimeVisibleAnnotations, AnnotationDefault
# Keep any methods with the annotation, but allow them to be obfuscated.
-keepclassmembers,allowobfuscation class * {
@com.google.android.play.protections.annotations.PlayAutomaticIntegrityProtection *;
}
Step 2: Annotate methods for custom protection
import com.google.android.play.protections.annotations.PlayAutomaticIntegrityProtection
@PlayAutomaticIntegrityProtection()
fun myMethod() {
// Method body...
}
Java
import com.google.android.play.protections.annotations.PlayAutomaticIntegrityProtection;
@PlayAutomaticIntegrityProtection()
public void myMethod() {
// Method body...
}Step 3: (Optional) Protect client-side secrets
Custom method protection obfuscates (hides) the code directly inside the annotated method body. You can use this to protect sensitive API keys.
Kotlin:
import com.google.android.play.protections.annotations.PlayAutomaticIntegrityProtection
@PlayAutomaticIntegrityProtection()
fun callApi() {
api.connect("YOUR_API_KEY")
}
Java:
import com.google.android.play.protections.annotations.PlayAutomaticIntegrityProtection;
@PlayAutomaticIntegrityProtection()
public static void callApi() {
api.connect("YOUR_API_KEY");
}
Step 4: (Optional) Manage protection performance overhead
By default, calling a protected method adds an overhead of several milliseconds. Therefore, it must not be called in performance-sensitive scenarios or on the main thread.
If you need to protect a method where fast execution is necessary, a lighter protection mode is available. You can use an annotation attribute to opt for faster access (loaded at startup), though this corresponding protection is not as strong.
Kotlin:
@PlayAutomaticIntegrityProtection(loadAtStartup = true)
fun callApi() {
api.connect("YOUR_API_KEY")
}
Java:
@PlayAutomaticIntegrityProtection(loadAtStartup = true)
public static void callApi() {
api.connect("YOUR_API_KEY");
}
To use this, add the interface attribute to the body of your original annotation definition from Step 1:
// Kotlin Annotation Definition Update
public annotation class PlayAutomaticIntegrityProtection(
/**
* If true, the annotated method will be loaded at startup. If false, it will
* be loaded on demand.
*
* <p>For on demand loading, calling the method will incur a performance
* penalty. For startup loading, the method will remain deobfuscated in memory
* for the lifetime of the app.
*/
val loadAtStartup: Boolean = false
) {}
Step 5: Test your app
Best practices for selecting methods
A thoughtful selection of methods leads to stronger overall protection. If each new release has a newly protected method, that version of the app or game will be more resilient to attacks.
As a general rule to avoid impacting your app's performance, select cold methods rather than hot methods. For maximum protection, select methods that:
- Are critical to the application (the app would fail if removed).
- Are not executed in the main/UI thread.
- Run more than once during the lifetime of the app, but don't run in a hot loop.
- Are non-trivial (methods containing non-trivial code or data).
- Are not executed during startup, if possible.
- Are newly introduced or heavily refactored in the release.
- Are not abstract methods, interface methods, or constructors.
- Do not use reflection and do not directly load a native library.
Remove customized protection
If you no longer want to protect a specific method, you can simply remove the @PlayAutomaticIntegrityProtection() annotation.