The first time you hear about Android APK signing schemes, it sounds like a plumbing detail. Four versions. A block ID here, a Merkle tree there, a sidecar file over there. But every one of those schemes exists because something specific broke — a CVE, a lost signing key, a streaming-install use case, a malware campaign. The signing story is the security history of Android in miniature.
v1: inherited wholesale from Java (2008–2016)
When Android 1.0 shipped in September 2008, signing came for free — Google reused Java 2's JAR signing, which had been around since JDK 1.2 in 1998. Three files went into META-INF/: MANIFEST.MF (a list of every file in the APK with a base64 digest of its uncompressed contents), CERT.SF (a digest of the manifest and of each manifest entry), and CERT.RSA (a PKCS#7 blob containing the actual RSA/DSA/EC signature and the developer's certificate chain). Verification walked the chain: CERT.RSA signs CERT.SF, CERT.SF attests to MANIFEST.MF, MANIFEST.MF attests to each file. Break any link and verification fails.
The quiet catch: v1 signs file contents, not the container around them. Anything outside the listed ZIP entries — the ZIP central directory metadata, ZIP comment bytes, arbitrary prefix bytes before the first entry — is unsigned. For most of a decade, nobody really minded. Then Janus showed up.
Janus, CVE-2017-13156: the attack that made v2 mandatory
In December 2017, GuardSquare's Eric Lafortune publicly disclosed CVE-2017-13156, which he'd reported to Google five months earlier. The bug exploits a neat quirk: a file can be a valid DEX file when read from the start and a valid ZIP file when read from the end, because the DEX magic sits in the first bytes and the ZIP End-of-Central-Directory record sits in the last bytes. An attacker could prepend a malicious DEX file onto a legitimately v1-signed APK. Android's ART runtime loaded DEX from the front — the attacker's code. The signature verifier walked MANIFEST.MF entries from the back — untouched. The malicious APK installed as the victim app, inherited the victim app's signing identity, and inherited the victim app's permissions.
Every Android version from 5.0 through 8.0 was affected, as long as the target APK was v1-signed only. Google had quietly shipped the answer sixteen months earlier.
v2: sign the whole file (Android 7.0 Nougat, August 2016)
APK Signature Scheme v2 landed in Android 7.0 Nougat on August 22, 2016. It inserts a new "APK Signing Block" immediately before the ZIP central directory, containing a signature over the raw bytes of the entire file — not per-entry digests over decompressed content. The ZIP is split into 1 MB chunks, each chunk is hashed, and the chunk digests are hashed together. The algorithm is recognizably Merkle-tree-adjacent.
Crucially, when an APK is both v1- and v2-signed, the v1 CERT.SF carries an X-Android-APK-Signed: 2 header. A v2-aware verifier sees that header, refuses any fallback to v1, and dies if the v2 block is missing. This is the anti-rollback tripwire that closed the Janus attack on Android 7.0 and later.
The other win was speed. v1 verification requires decompressing every entry to check the digest. v2 hashes the file as a byte stream. On a large APK install, the difference is minutes versus seconds.
v3: key rotation for grown-up developers (Android 9 Pie, August 2018)
For ten years, an Android developer who lost their signing key was, in the most literal professional sense, stuck. A new key meant a new package from the user's point of view — INSTALL_FAILED_UPDATE_INCOMPATIBLE, no upgrade path, users would have to uninstall and lose their data. Companies merged, developers died, laptops got stolen. The Android team had seen every version of this story.
APK Signature Scheme v3, introduced in Android 9 Pie on August 6, 2018, added a proof-of-rotation lineage inside the APK Signing Block: a linked list of X.509 certificates, oldest at the root, each one signed by its predecessor. The cryptographic statement is "I, the holder of the old key, authorize this new key to sign updates to this app." The platform verifies the chain at install, updates the recorded signer, and the user's installed app continues to upgrade smoothly across the key change.
v3 had rough edges — shared-user-id handling was inconsistent, rollback after rotation could break, signature-level permissions between rotated-key peers didn't always work — so v3.1 arrived in Android 13 (August 2022), adding per-SDK rotation targeting so the old signer stays authoritative on Android 12 and older while the rotated signer takes effect on 13+.
v4: streaming installs and the fs-verity hash tree (Android 11, September 2020)
v4 exists for one specific reason: modern games are enormous and developers want users playing before the download is done. On Android 11, pairing the new Incremental File System (IncFS) kernel module with an APK Signature Scheme v4 sidecar — a separate .apk.idsig file — lets Android start an install with an empty placeholder file and stream content in as it's requested. Pages that haven't arrived yet fault in on demand, each page cryptographically verified against the Merkle tree in the sidecar.
The hash tree structure is deliberately identical to Linux's fs-verity — same salt padding, same block alignment — because IncFS delegates per-page verification to the same kernel machinery. v4 never stands alone: the APK must also carry a v2 or v3 signature for the non-streaming install path. You see v4 most visibly in adb install --incremental from Android Studio and in Play's own streaming install of large games.
The managed-key era: Play App Signing (2017–2021)
Parallel to the signing schemes, Google slowly centralized the keys themselves. Google Play App Signing launched as an opt-in in September 2017: developers signed their upload with an upload key, Google stripped that signature and re-signed every delivered APK with a Google-held signing key. Lost your upload key? Identity check and Google resets it — the app signing key is unaffected, users keep getting updates.
In August 2021, when AAB became mandatory for new apps on Play, Play App Signing became effectively mandatory too — Google couldn't generate per-device split APKs from an AAB without holding a key. Existing apps with pre-2021 APK upload flows kept their self-custody; new apps handed custody to Google. That structural change is the part F-Droid and Mark Murphy objected to, and it's still the unresolved tension at the heart of the AAB era.
The Samsung key leak (December 2022)
The stakes got vivid in late 2022, when Google's Łukasz Siewierski (via the Android Partner Vulnerability Initiative) disclosed that platform signing keys — the ones OEMs use to sign the android system app, conferring android.uid.system — had leaked from Samsung, LG, and MediaTek. Malware samples signed with Samsung's platform certificate had been in the wild since 2016. When the news broke, Samsung was reportedly still using the leaked cert to sign production apps on APKMirror. A key signing scheme cannot protect you from one the attacker already possesses.
What this looks like from the install dialog
"Install from unknown sources" is ultimately a signing question. The OS can verify an APK hasn't been modified since it was signed. It has no way to verify who signed it unless the cert is already pinned somewhere — Play Protect, Play Integrity, an OEM allowlist. When you install an APK from outside the Play Store, you are trusting the signing key to be the developer's. That's why INSTALL_FAILED_UPDATE_INCOMPATIBLE is such a useful error: it catches the exact case where the certificate doesn't match what the device already trusts.
For APK20, this is the center of the job. We publish the SHA-256 of the signing certificate on every variant page. We refuse to publish a new version of an app if the signing key has changed from the prior version. We don't re-sign — what Google delivered is what we mirror, byte for byte. Signing schemes are the one real technical guarantee Android gives a sideloader. The least we can do is make them legible.
v1: inherited wholesale from Java (2008–2016)
When Android 1.0 shipped in September 2008, signing came for free — Google reused Java 2's JAR signing, which had been around since JDK 1.2 in 1998. Three files went into META-INF/: MANIFEST.MF (a list of every file in the APK with a base64 digest of its uncompressed contents), CERT.SF (a digest of the manifest and of each manifest entry), and CERT.RSA (a PKCS#7 blob containing the actual RSA/DSA/EC signature and the developer's certificate chain). Verification walked the chain: CERT.RSA signs CERT.SF, CERT.SF attests to MANIFEST.MF, MANIFEST.MF attests to each file. Break any link and verification fails.
The quiet catch: v1 signs file contents, not the container around them. Anything outside the listed ZIP entries — the ZIP central directory metadata, ZIP comment bytes, arbitrary prefix bytes before the first entry — is unsigned. For most of a decade, nobody really minded. Then Janus showed up.
Janus, CVE-2017-13156: the attack that made v2 mandatory
In December 2017, GuardSquare's Eric Lafortune publicly disclosed CVE-2017-13156, which he'd reported to Google five months earlier. The bug exploits a neat quirk: a file can be a valid DEX file when read from the start and a valid ZIP file when read from the end, because the DEX magic sits in the first bytes and the ZIP End-of-Central-Directory record sits in the last bytes. An attacker could prepend a malicious DEX file onto a legitimately v1-signed APK. Android's ART runtime loaded DEX from the front — the attacker's code. The signature verifier walked MANIFEST.MF entries from the back — untouched. The malicious APK installed as the victim app, inherited the victim app's signing identity, and inherited the victim app's permissions.
Every Android version from 5.0 through 8.0 was affected, as long as the target APK was v1-signed only. Google had quietly shipped the answer sixteen months earlier.
v2: sign the whole file (Android 7.0 Nougat, August 2016)
APK Signature Scheme v2 landed in Android 7.0 Nougat on August 22, 2016. It inserts a new "APK Signing Block" immediately before the ZIP central directory, containing a signature over the raw bytes of the entire file — not per-entry digests over decompressed content. The ZIP is split into 1 MB chunks, each chunk is hashed, and the chunk digests are hashed together. The algorithm is recognizably Merkle-tree-adjacent.
Crucially, when an APK is both v1- and v2-signed, the v1 CERT.SF carries an X-Android-APK-Signed: 2 header. A v2-aware verifier sees that header, refuses any fallback to v1, and dies if the v2 block is missing. This is the anti-rollback tripwire that closed the Janus attack on Android 7.0 and later.
The other win was speed. v1 verification requires decompressing every entry to check the digest. v2 hashes the file as a byte stream. On a large APK install, the difference is minutes versus seconds.
v3: key rotation for grown-up developers (Android 9 Pie, August 2018)
For ten years, an Android developer who lost their signing key was, in the most literal professional sense, stuck. A new key meant a new package from the user's point of view — INSTALL_FAILED_UPDATE_INCOMPATIBLE, no upgrade path, users would have to uninstall and lose their data. Companies merged, developers died, laptops got stolen. The Android team had seen every version of this story.
APK Signature Scheme v3, introduced in Android 9 Pie on August 6, 2018, added a proof-of-rotation lineage inside the APK Signing Block: a linked list of X.509 certificates, oldest at the root, each one signed by its predecessor. The cryptographic statement is "I, the holder of the old key, authorize this new key to sign updates to this app." The platform verifies the chain at install, updates the recorded signer, and the user's installed app continues to upgrade smoothly across the key change.
v3 had rough edges — shared-user-id handling was inconsistent, rollback after rotation could break, signature-level permissions between rotated-key peers didn't always work — so v3.1 arrived in Android 13 (August 2022), adding per-SDK rotation targeting so the old signer stays authoritative on Android 12 and older while the rotated signer takes effect on 13+.
v4: streaming installs and the fs-verity hash tree (Android 11, September 2020)
v4 exists for one specific reason: modern games are enormous and developers want users playing before the download is done. On Android 11, pairing the new Incremental File System (IncFS) kernel module with an APK Signature Scheme v4 sidecar — a separate .apk.idsig file — lets Android start an install with an empty placeholder file and stream content in as it's requested. Pages that haven't arrived yet fault in on demand, each page cryptographically verified against the Merkle tree in the sidecar.
The hash tree structure is deliberately identical to Linux's fs-verity — same salt padding, same block alignment — because IncFS delegates per-page verification to the same kernel machinery. v4 never stands alone: the APK must also carry a v2 or v3 signature for the non-streaming install path. You see v4 most visibly in adb install --incremental from Android Studio and in Play's own streaming install of large games.
The managed-key era: Play App Signing (2017–2021)
Parallel to the signing schemes, Google slowly centralized the keys themselves. Google Play App Signing launched as an opt-in in September 2017: developers signed their upload with an upload key, Google stripped that signature and re-signed every delivered APK with a Google-held signing key. Lost your upload key? Identity check and Google resets it — the app signing key is unaffected, users keep getting updates.
In August 2021, when AAB became mandatory for new apps on Play, Play App Signing became effectively mandatory too — Google couldn't generate per-device split APKs from an AAB without holding a key. Existing apps with pre-2021 APK upload flows kept their self-custody; new apps handed custody to Google. That structural change is the part F-Droid and Mark Murphy objected to, and it's still the unresolved tension at the heart of the AAB era.
The Samsung key leak (December 2022)
The stakes got vivid in late 2022, when Google's Łukasz Siewierski (via the Android Partner Vulnerability Initiative) disclosed that platform signing keys — the ones OEMs use to sign the android system app, conferring android.uid.system — had leaked from Samsung, LG, and MediaTek. Malware samples signed with Samsung's platform certificate had been in the wild since 2016. When the news broke, Samsung was reportedly still using the leaked cert to sign production apps on APKMirror. A key signing scheme cannot protect you from one the attacker already possesses.
What this looks like from the install dialog
"Install from unknown sources" is ultimately a signing question. The OS can verify an APK hasn't been modified since it was signed. It has no way to verify who signed it unless the cert is already pinned somewhere — Play Protect, Play Integrity, an OEM allowlist. When you install an APK from outside the Play Store, you are trusting the signing key to be the developer's. That's why INSTALL_FAILED_UPDATE_INCOMPATIBLE is such a useful error: it catches the exact case where the certificate doesn't match what the device already trusts.
For APK20, this is the center of the job. We publish the SHA-256 of the signing certificate on every variant page. We refuse to publish a new version of an app if the signing key has changed from the prior version. We don't re-sign — what Google delivered is what we mirror, byte for byte. Signing schemes are the one real technical guarantee Android gives a sideloader. The least we can do is make them legible.