Technical guide

How to detect repackaging in Android apps

Detect repackaged Android apps with signer, installer and Play Integrity checks, then test them against signed baselines and approved key rotations.

  • Check the signer

    A repackaged copy must be re-signed with someone else's key. Comparing the runtime signing certificate with your own is the first signal.

  • Confirm on the server

    In-app checks can be patched out. A Play Integrity verdict verified on your backend gives a second opinion the client cannot forge.

  • Keep legitimate updates working

    Record Play App Signing, key rotation and every approved channel, so a planned change never looks like an attack.

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 use getApkContentsSigners().
  • 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:

  • appRecognitionVerdict is PLAY_RECOGNIZED when the binary and certificate match a version Google Play distributes for your app.
  • UNRECOGNIZED_VERSION means the binary or its certificate is not one Play knows. A repackaged copy typically lands here.
  • UNEVALUATED means 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.

  1. Install the unchanged candidate from the intended channel on a supported physical device. Verify launch, sign-in and the sensitive action.
  2. Record the expected signer digest, installer and Play Integrity verdict for that channel.
  3. Pick one integrity boundary per test: packaged code, resources, signing identity or runtime state.
  4. Prepare one modified variant in the lab. Record what changed and which lab key signed it, without exposing signing material.
  5. 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.
  6. 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.

VariantQuestion it answersResult to record
Unchanged signed releaseDoes the legitimate journey work?Install, launch, sensitive flow
Altered code or resource, lab signerDo signer and content checks fire?Executed change, signal, response
Same APK, lab signer onlyIs the signer change recognised?Install outcome, then runtime signal
Installed outside the approved channelDoes the installer check behave as intended?Installer value, response
Approved update with rotated keyIs the rotated signer accepted?Update continuity, normal flow
Integrity verdict unavailableIs 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

Common questions

  • How do I check the signing certificate of a running Android app?

    From Android 9 (API level 28), call PackageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES) and read packageInfo.signingInfo. Compare the SHA-256 digest of the current certificate with your approved list, or use PackageManager.hasSigningCertificate(packageName, certBytes, PackageManager.CERT_INPUT_SHA256). With Play App Signing, expect the app signing key, not your upload key.

  • Is an unexpected installer proof of repackaging?

    No, it is a hint. From Android 11 (API level 30), PackageManager.getInstallSourceInfo(packageName) shows the installing and initiating packages, with com.android.vending for Google Play. Enterprise distribution, other app stores and pre-installed builds are legitimate, so keep an approved-installer list per channel.

  • Which Play Integrity verdict does a repackaged copy usually get?

    Typically UNRECOGNIZED_VERSION, meaning the binary or its certificate is not one Play knows. PLAY_RECOGNIZED means they match a version Google Play distributes for your app, and UNEVALUATED is missing evidence, not a pass. Apps distributed outside Play need a different server-side policy for this verdict.

  • How should the signer check handle key rotation?

    Accept every certificate a legitimate install can present: the current one, and the original one on releases a v3.1 rotation does not target. Never accept any certificate in the lineage as a blanket rule. List approved digests explicitly, and update that list before you upgrade an app signing key in Play App Signing.

  • Is the in-app signer check enough on its own?

    No. A determined attacker can patch the check out of the app, so treat it as one signal, protect the code that performs it and confirm the result on the server. The in-app signer result and the server-side Play Integrity verdict answer related questions from different vantage points; neither replaces the other.

Verify your Android integrity boundary

Tell us your Android distribution channels, framework and integrity requirement. We do not ask for binaries, signing keys, credentials or production data.

Or request a technical demo