SReverseby Simpa Labs

Signature integrity

PairIP and Android signature verification

Two signature checks run against a PairIP build. One belongs to Android and happens when the package is installed. The other belongs to the wrapper and happens every time the app starts. They fail for different reasons.

Two checks that are easy to confuse

Android verifies signatures at install and update time. Application code can also read its own signing certificate and compare it against a value the author recorded. PairIP uses the second kind, and it sits beside the first. Keeping them apart decides which failure you are actually looking at, and whether the fix belongs to your tooling or to the protection.

The platform check

PackageManager verifies the APK signature when a package is installed or updated. Older signing was a JAR signature over the archive entries, stored under META-INF. Later schemes sign the archive as a whole, which means a change to any byte in the file invalidates the signature. You cannot edit a signed APK and install it. Any modification requires re-signing, and re-signing produces a new certificate that no previous check has seen.

Updates add a second constraint. An update normally must be signed with the same key as the installed version, so replacing the key means uninstalling the old package first. Android does provide a key-rotation path that carries a signed lineage from the previous key, and a build that uses it behaves differently. Whatever the app stored under the old install goes with the uninstall, including any registration the backend recognised.

Play App Signing sits in the middle

When a developer uses Play App Signing, Google holds the app signing key and signs the artifact that reaches devices, while the developer uploads with an upload key. A locally built release carries the upload key certificate and the Play build carries the app signing key certificate. Both are legitimate, and they are not the same value.

Anything that compares certificates has to account for that. A check written against the Play certificate rejects a correct local build, and a check written against a developer's debug key behaves differently again. Before treating a signature mismatch as tampering, establish which certificate the build was distributed with.

The in-app check

Application code reads the package signing certificate through PackageManager and compares its digest against a constant. Sometimes the comparison is direct. Sometimes the digest is folded into a larger hash that also covers dex or native files, so the failure says nothing about which input was wrong. Sometimes the result only changes which branch runs later, and the visible symptom appears somewhere else entirely.

The PairIP stub and its native core perform this comparison before the developer's application code starts. The expected value was recorded when Google wrapped the build, so it describes the Play artifact. A repackaged build fails by construction. That is the check doing exactly what it was written to do, and no build tool will produce a passing result without the original signing identity.

Reading the check rather than fighting it

Locate the routine that reads the certificate and the constant it compares against. In a protected build the expected values are often embedded in the native library rather than left readable in dex, so the comparison may only appear as a call into native code with a byte array argument. Follow the callers to see what the result controls.

  • The manifest application class, which names the stub that runs first.
  • The native library for each ABI, which holds the comparison and its expected values.
  • The code that reads package info and passes a digest across the JNI boundary.
  • The branches that consume the result, including any flag set for later use.

In some builds a failed check exits immediately, which makes the cause obvious. In others it sets a flag that a later condition reads, and the app keeps running in a reduced state. A build that appears to launch normally can still be on the second path.

What changes for a reconstruction

We recover the requests the app makes while a valid install runs, and reproduce the derived values from that observation. A certificate the wrapper rejects on a modified build does not need to be spoofed. Where a certificate digest is itself an input to a request, the value is captured from the running app and its role documented. Where it only gates local startup, it stays inside the wrapper and out of the delivered client.

That boundary is the useful output of the investigation. It says which checks affect the protocol and which only affect the process, and it keeps the final client free of dependencies it never needed. The anti-tamper analysis view of the same build covers the checks that watch memory and code rather than certificates.

Reviewed 28 September 2026 · SReverse research desk

Start your full APK reconstruction

Send the full APK

Projects start at $120. Choose WhatsApp or email, then attach the APK in the app that opens. We reply within one hour with the next step and send the fixed quote after review.

Want us to contact you?