Signing and integrity
An APK's signature is not a safety guarantee and not a certificate from a CA. It is an identity claim, and it is the hinge that permission checks, update acceptance and Play App Signing all turn on.
There is a persistent misconception that a signed app is a safe app. Signing says who built the package, not what it does. A malware author can sign their own app perfectly well; it just will not be signed with your key.
TL;DR#
- Android verifies who signed an APK, not whether it is trustworthy. There is no CA chain.
- v1 signs each ZIP entry. v2 (API 24Android 7.0) signs the whole file via an APK Signing
Block. v3 (API 28Android 9) adds key rotation with a proof-of-rotation lineage.
v4 (API 30Android 11) adds a separate
.idsigfor incremental verification. - Updates must be signed by the same key as the installed version. Changing the key is not an update; it is a new app.
- Play App Signing separates the app signing key (Google holds it) from the upload key (you hold it), which makes a lost upload key recoverable.
- Android 17 (API 37Android 17) adds a v3.2 variant carrying a hybrid classical-plus-ML-DSA post-quantum signature.
Mental model#
Think of a wax seal on a letter.
A seal proves the letter came from whoever holds the seal. It does not prove the letter is true, kind or safe. It does not prove the courier did not lose it and replace it with another letter sealed with the same seal. It proves authorship, and only authorship.
That is what an APK signature is. The wax seal is the certificate; the signet ring is the private key.
Where the analogy breaks#
A seal is applied to a folded letter, so the seal covers the fold. The v1 signature is applied to individual entries inside the archive, which means it does not cover the archive's structure — the gap that made the Janus bug possible. v2 fixed that by sealing the whole file, which is one of the clearest cases in Android of a security property being a consequence of what region the signature covers.
The big picture#
Text description of this diagram
A comparison of the four APK signature schemes. v1 signs each ZIP entry separately through META-INF/MANIFEST.MF and CERT.SF, protecting against modification of an entry but not of the ZIP metadata or unlisted files. v2, valid from API 24, signs the whole file through an APK Signing Block placed before the central directory, protecting against any modification of the archive. v3, valid from API 28, adds a proof-of-rotation structure so the signing key can change while proving descent from the old key. v4, valid from API 30, adds a separate .idsig file containing a Merkle tree so the APK can be verified incrementally as it streams in.
Deep dive#
v1: JAR signing and its gap#
v1 is inherited from Java. The APK contains META-INF/MANIFEST.MF, listing digests of each entry,
META-INF/CERT.SF, signing the manifest, and a signature block signing CERT.SF.
It protects the entries. It does not protect:
- The ZIP metadata (compression method, filenames as stored).
- The central directory.
- Files that are in the archive but not listed in the manifest.
The Janus vulnerability exploited exactly this: an attacker could add a DEX file to the archive in a way that Android's runtime would load but that v1 verification did not cover, while the v1 signature still verified. That is the concrete cost of "signing the contents but not the container".
v2: the whole file#
v2 was introduced in Android 7.0 (API 24Android 7.0).1 It adds an
APK Signing Block immediately before the ZIP Central Directory. The block is a length-prefixed
sequence of ID-value pairs, ending with a size and the magic string APK Sig Block 42. The v2
signature lives under ID 0x7109871a.
The scheme covers four regions of the file:
- The ZIP entries, from offset 0 to the signing block.
- The signing block itself (the signed-data portion).
- The central directory.
- The end-of-central-directory record.
That is the whole archive, which is why v2 closes the Janus gap. The digest construction is Merkle-like: the file is split into 1 MB chunks, each chunk is digested with a prefix byte and a length, and the top-level digest covers the chunk count and the chunk digests. That makes verification streaming and parallelisable.
The EOCD digest has a subtlety: because adding a signing block changes the central directory's offset, the EOCD digest treats the recorded offset as the signing block's offset. That is the kind of detail that only matters when you are implementing a verifier, and it is exactly the kind of thing that produces compatibility bugs when you are not.
v3: key rotation#
v3 was introduced in Android 9 (API 28Android 9).2 It uses the same whole-file digest as v2 but adds a proof-of-rotation structure: a lineage in which each signing key signs a statement that the next key is its successor.
That solves a real problem. Without rotation, an app's signing key is fixed for the life of the package name; if it leaks, the only remedies are a new package name or a Play-side intervention. With v3, the developer can rotate to a new key and devices that trust the old key can verify the chain.
v4: incremental verification#
v4 was introduced in Android 11 (API 30Android 11).3
It is different in kind: it produces a separate
.idsig file alongside the APK, containing a Merkle tree over the APK's bytes.
The purpose is incremental installation. When an APK arrives over the network, the device can verify each chunk against the Merkle tree as it arrives, rather than waiting for the entire file. That is what makes it possible to start installing before the download finishes.
v4 does not replace v2 or v3: it complements them, and the .idsig file is useless without the APK's
own signatures.
| Scheme | Since API | Signs | Solves |
|---|---|---|---|
| v1 | 1 | Each entry | Compatibility with old devices |
| v2 | 24 | The whole file | Modification of container metadata |
| v3 | 28 | Whole file + lineage | Key rotation |
| v4 | 30 | Separate Merkle tree | Incremental verification |
What a signature actually gates#
The signature is not just an install-time check. Four things depend on it:
- Updates. A package update must be signed by the same key as the installed version (or by a key with a v3 lineage). Otherwise the install is refused.
- Signature permissions. A
signaturepermission is granted only when the caller's certificate matches the definer's. - Shared UID.
sharedUserId(deprecated) required the same signature, because it let two apps run as one UID. - Play App Signing. The store verifies that an upload is signed by the developer's upload key before it re-signs with the app signing key.
This is why losing a signing key is a bigger problem than losing a password. It is not an authentication credential; it is the identity of the package.
Play App Signing#
The mechanism: the developer uploads an APK or an App Bundle signed with an upload key. Google verifies it and re-signs the distributed artefact with the app signing key, which it holds.
The benefit is that a lost or compromised upload key can be reset without changing the app's identity. The cost is that the app's identity now depends on Google's infrastructure and on the developer account. For apps distributed outside Play, the developer holds both keys and the old risks apply.
The verification in practice#
apksigner verify --verbose prints which schemes are present and whether each verifies. It also
reports the signer's certificate digest, the key algorithm and the rotation lineage.
apksigner verify --verbose app-release.apk
apksigner verify --print-certs app-release.apk
Example output
Verifies
Verified using v1 scheme (JAR signing): true
Verified using v2 scheme (APK Signature Scheme v2): true
Verified using v3 scheme (APK Signature Scheme v3): true
Number of signers: 1The --print-certs output is the certificate digest, which is what a signature permission is
actually comparing. Two apps share a signature permission if and only if their certificate digests
match.
How it evolved#
| API | Version | What changed |
|---|---|---|
| 18 | 4.3 Jelly Bean |
|
| 23 | 6.0 Marshmallow |
|
| 28 | 9 Pie |
|
| 30 | 11 |
|
| 31 | 12 |
|
| 33 | 13 Tiramisu |
|
| 37 | 17 Cinnamon Bun |
|
The pattern across all four schemes is the same: each one widened what the signature covers, or made verification cheaper. v2 covered the container. v3 covered time (rotation). v4 covered the delivery process (incremental). The next step, v3.2, covers the algorithm's cryptographic lifetime.
Under the hood#
- the reference implementation of APK signature verification, used by apksigner and by the platform
- PackageParser.javawhere the platform parses and checks signatures during install
- platform signing keys and the build-time signing configuration
Curiosity corner#
- The APK Signing Block's magic string is literally "APK Sig Block 42". The 42 is a Hitchhiker's reference, and it is part of a format that will outlive everyone who wrote it.
- v2's Merkle-like digest is chunked at 1 MB. That chunk size is why verification can be streamed and why a corrupted chunk fails locally rather than at the end.
- A debug build is signed with a debug key. That is why debug and release builds cannot update each other, and why the debug keystore is shared across all apps on a machine.
Try it#
Check yourself#
Check yourself
Which layer enforces the app sandbox?
Sources#
Sources
Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.
- docsAPK Signature Scheme v2source.android.comaccessed 2026-10-27↩
- docsAPK Signature Scheme v3source.android.comaccessed 2026-10-27↩
- docsAPK Signature Scheme v4source.android.comaccessed 2026-10-27↩
- docsAPK signature schemesource.android.comaccessed 2026-10-27↩
- docsapksignerdeveloper.android.comaccessed 2026-10-27↩
- docsAbout Android App Bundlesdeveloper.android.comaccessed 2026-10-27↩
- docsBehavior changes: Apps targeting Android 17developer.android.comaccessed 2026-10-27↩
- docsAndroid Verified Boot (AVB)source.android.comaccessed 2026-10-27↩