Detect a repackaged Android app by checking who signed it, where it came from and what Google says about it. At runtime, compare the signing certificate with your own release certificate and read the installer source. On your server, verify a Play Integrity token and require PLAY_RECOGNIZED for flows that need the genuine Play build. Then test these checks against an unchanged signed baseline, an approved key rotation and authorised modified copies, recording install, execution, detection and response as separate results. Otukan detects repackaged Android apps through app integrity checks. This guide covers Android only.
Detection signals
Repackaging means unpacking your APK, changing code or resources and signing the result again. The attacker does not have your signing key, so the signer changes. Most detection starts there.
Runtime signer check
From Android 9 (API level 28), call PackageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES) and read packageInfo.signingInfo.
- If
signingInfo.hasMultipleSigners()is false,getSigningCertificateHistory()returns the current certificate and any earlier ones in its rotation lineage. Otherwise usegetApkContentsSigners(). - Compute the SHA-256 digest of the current certificate and compare it with your approved list.
PackageManager.hasSigningCertificate(packageName, certBytes, PackageManager.CERT_INPUT_SHA256)performs the same comparison directly. - With Play App Signing, the expected certificate is the app signing key shown in Play Console, not your upload key.
A determined attacker can patch this check out of the app. Treat it as one signal, protect the code that performs it and confirm the result on the server.
Installer source
From Android 11 (API level 30), PackageManager.getInstallSourceInfo(packageName) returns an InstallSourceInfo. getInstallingPackageName() is com.android.vending for Google Play. getInitiatingPackageName() shows which app started the install. An unexpected installer is a hint, not proof: enterprise distribution, other app stores and pre-installed builds are legitimate. Keep an approved-installer list per channel.
Play Integrity app recognition
Request a Play Integrity token for sensitive actions and decode it on your server. In appIntegrity:
appRecognitionVerdictisPLAY_RECOGNIZEDwhen the binary and certificate match a version Google Play distributes for your app.UNRECOGNIZED_VERSIONmeans the binary or its certificate is not one Play knows. A repackaged copy typically lands here.UNEVALUATEDmeans Google could not assess it. That is missing evidence, not a pass.
The verdict also carries packageName, certificateSha256Digest and versionCode. Check them against your release record. Because Google signs the verdict outside the device, a modified client cannot fake it. Apps distributed outside Play need a different server-side policy for this verdict. For server verification steps, see how to verify mobile app attestation.
Key rotation lineage
APK Signature Scheme v3 (Android 9 and higher) supports key rotation: the new APK carries a proof-of-rotation lineage linking the old certificate to the new one. Android 13 added v3.1, which lets you target rotation at newer platform releases while older ones keep the original signer. Your runtime check must therefore accept every certificate a legitimate install can present: the current one, and the original one on releases the rotation does not target. Never accept “any certificate in the lineage” as a blanket rule; list the approved digests explicitly. Update that list before you upgrade an app signing key in Play App Signing.
Establish the baseline
Use your own authorised lab app and a release candidate, with test accounts and isolated backend resources. Record source revision, build configuration, package name, channel and expected signer.
- Install the unchanged candidate from the intended channel on a supported physical device. Verify launch, sign-in and the sensitive action.
- Record the expected signer digest, installer and Play Integrity verdict for that channel.
- Pick one integrity boundary per test: packaged code, resources, signing identity or runtime state.
- Prepare one modified variant in the lab. Record what changed and which lab key signed it, without exposing signing material.
- Confirm the variant reaches the code you want to test. If Android refuses the install or the app stops earlier, record that before assessing detection.
- Compare the observed signal and response with the prediction you wrote before the run.
Change one thing at a time. A copy that changes both code and signer may trigger a control, but that run alone does not tell you which check caught it.
| Variant | Question it answers | Result to record |
|---|---|---|
| Unchanged signed release | Does the legitimate journey work? | Install, launch, sensitive flow |
| Altered code or resource, lab signer | Do signer and content checks fire? | Executed change, signal, response |
| Same APK, lab signer only | Is the signer change recognised? | Install outcome, then runtime signal |
| Installed outside the approved channel | Does the installer check behave as intended? | Installer value, response |
| Approved update with rotated key | Is the rotated signer accepted? | Update continuity, normal flow |
| Integrity verdict unavailable | Is missing evidence handled? | Unknown state and fallback |
Keep failed and inconclusive cases in the record.
Keep legitimate changes working
Maintain an approved-change register per channel: package variants, framework and native library changes, signing-key upgrades and test builds. Give every exception an owner and an end date. An exception that accepts any signer removes the boundary.
Install the previous approved release, create a session and keep representative app data. Apply the update through the intended channel. Repeat sign-in, session renewal and the sensitive action. Run this on the Android versions and device makers in your support matrix; an emulator run does not replace physical devices.
Connect the signal to a response
Otukan supplies runtime threat signals for application response policies. Decide before testing which signal justifies recording, warning or restricting a sensitive action, and who owns each rule. Enforce access to sensitive resources on the backend; hiding a button does not stop a modified client.
Combine the in-app signer result with the server-side Play Integrity verdict. They answer related questions from different vantage points; neither replaces the other. Define how long evidence stays valid for an action, and test delayed, repeated and missing signals.
- Each modified variant has independent proof that the change executed.
- Installation refusal is recorded apart from runtime detection.
- Approved signers, including rotated keys, are listed and tested.
- App response and backend enforcement are recorded separately.
- Repeated signals do not repeat user-facing penalties.
- A legitimate user can follow the recovery path.
For the control overview, see repackaging and re-signing detection and Android app security. For iOS, see how to prevent mods and hacks that require re-signing iOS apps.
Further reading
- Sign your app — Android Developers
- APK Signature Scheme v3 — Android Open Source Project
- SigningInfo — Android Developers
- InstallSourceInfo — Android Developers
- Integrity verdicts — Android Developers