Short answer. Android checks one of several signature formats depending on the device: v1 (JAR signing, per file, for Android 6 and older), v2 (whole-file signature in the APK Signing Block, Android 7.0), v3 (v2 plus a proof-of-rotation lineage, Android 9), v3.1 (rotation that targets Android 13 and later only), v3.2 (a hybrid classical plus ML-DSA signature, Android 17) and v4 (a separate .idsig file for incremental installs, Android 11). Apps targeting API level 30 or higher must have v2 or newer. Updates must be signed by the same key or by a successor in the app's rotation lineage, so the signing key is the app's identity for its whole life: let Google Play hold it (Play App Signing), keep only the upload key yourself, and check releases with apksigner verify -v --print-certs.
The lab used apksigner from Android SDK build-tools 37.0.0 (it reports itself as version 0.9) on Java 25.0.4.1 in eclipse-temurin:25.0.4.1_1-jdk-noble. The sample is the F-Droid client APK as downloaded from f-droid.org on 22 October 2026; all other keys were generated for this article.
The schemes
| Scheme | Since | Where | What it adds |
|---|---|---|---|
| v1 | Android 1.0 | META-INF/*.SF, *.RSA/*.EC | JAR signing: a hash per ZIP entry; ZIP metadata is not covered |
| v2 | Android 7.0 (API 24) | APK Signing Block, ID 0x7109871a | Signature over the whole file except the block itself |
| v3 | Android 9 (API 28) | ID 0xf05368c0 | v2 plus supported SDK range and a proof-of-rotation lineage |
| v3.1 | Android 13 (API 33) | a new block ID | Rotation applied only on Android 13 and later |
| v3.2 | Android 17 (API 37) | ID 0x70e1c89f | Exactly two signers, one classical and one post-quantum, both verified |
| v4 | Android 11 (API 30) | app.apk.idsig next to the APK | Merkle tree over the APK for adb install --incremental; needs v2 or v3 |
APK Signature Scheme v2 "treats the APK file as a blob and performs signature checking across the entire file", so "any modification to the APK, including ZIP metadata modifications, invalidates the APK signature" (AOSP). It is also faster: the device no longer decompresses every entry to check it. v1 remains only for devices older than Android 7.0; an APK whose minSdkVersion is 24 or higher does not need it, and apksigner leaves it out. A stripped v2 signature cannot be hidden by keeping v1: v2-signed APKs that are also v1-signed carry an X-Android-APK-Signed attribute in their .SF files, which the v1 signature covers, and verifiers must reject an APK that lacks a signature for the scheme it announces.
Since Android 11, "Apps that target Android 11 (API level 30) that are currently only signed using APK Signature Scheme v1 must now also be signed using APK Signature Scheme v2 or higher" (behavior changes).
Reading apksigner verify
apksigner verify -v --print-certs checks every scheme the APK carries for the SDK range it supports and prints the signers. For the published F-Droid client (output filtered to the scheme and signer lines):
Verifies
Verified using v1 scheme (JAR signing): false
Verified using v2 scheme (APK Signature Scheme v2): true
Verified using v3 scheme (APK Signature Scheme v3): true
Verified using v3.1 scheme (APK Signature Scheme v3.1): false
Verified using v3.2 scheme (APK Signature Scheme v3.2): false
Verified using v4 scheme (APK Signature Scheme v4): false
Verified for SourceStamp: false
Number of signers: 1
V3.0 Signer: certificate DN: CN=Ciaran Gultnieks, OU=Unknown, O=Unknown, L=Wetherby, ST=Unknown, C=UK
V3.0 Signer: certificate SHA-256 digest: 43238d512c1e5eb2d6569f4a3afbf5523418b82e0a3ed1552770abb9a9c9ccab
V3.0 Signer: key algorithm: RSA
false for v1 is not an error: the app's minimum SDK is 24, so v1 is unnecessary. The certificate digest is the value that identifies the app's signer: F-Droid publishes exactly 43238d51…a9c9ccab for its client on its signing keys page, and Android compares the signer on every update. There is no chain of trust: APK certificates are usually self-signed, and the v2 verification rules check that the first certificate's SubjectPublicKeyInfo matches the signing key, with no step for issuers or validity dates (v2). This certificate was issued in 2010.
Re-signing a copy with our own P-256 key and then changing a single byte of the file shows both halves of that check:
keytool -genkeypair -keystore key1.p12 -storetype PKCS12 -storepass changeit -alias app \
-dname "CN=Example App signing key 2026" -validity 10000 -keyalg EC -groupname secp256r1 -sigalg SHA256withECDSA
apksigner sign --ks key1.p12 --ks-pass pass:changeit --out app-key1.apk F-Droid.apk
apksigner verify -v --print-certs app-key1.apk
cp app-key1.apk tampered.apk
printf '\x00' | dd of=tampered.apk bs=1 seek=4000000 conv=notrunc status=none
apksigner verify -v tampered.apk
Verifies
Verified using v2 scheme (APK Signature Scheme v2): true
Verified using v3 scheme (APK Signature Scheme v3): true
V3.0 Signer: certificate DN: CN=Example App signing key 2026
V3.0 Signer: certificate SHA-256 digest: a93143a06f88894887d593f080c05b4ffd3aa9839c52378cee687987162d6d72
V3.0 Signer: key algorithm: EC
DOES NOT VERIFY
ERROR: APK Signature Scheme v3 signer #1: APK integrity check failed. CHUNKED_SHA256 digest mismatch. Expected: <66b1ea5c6952adc0c70f086346a76da06a5ab35dac770d104ff14d5ec09a9c97>, actual: <a5b1f3e628e868c150ade8cf1f3280f54ff4ae3771f20620cafbe97ad72ced57>
The re-signed copy verifies perfectly, with a different certificate. That is why the signer digest, not "Verifies", is what tells you an APK comes from the right publisher. Installed over the original F-Droid, Android would refuse it as an update signed by another key. The CHUNKED_SHA256 digest is computed over consecutive 1 MB chunks of the file sections, which is what lets v2 and later verify quickly and in parallel.
Rotating the key: v3 and v3.1
An app cannot change its signing key by simply re-signing: the update would be rejected. v3 adds a proof-of-rotation, "a singly-linked list with each node containing a signing certificate used to sign previous versions of the app", where each certificate signs the next (v3). A device that knows the old key accepts an APK signed by the new one if the lineage connects them.
apksigner rotate --out lineage.bin --old-signer --ks key1.p12 --ks-pass pass:changeit \
--new-signer --ks key2.p12 --ks-pass pass:changeit
apksigner sign --ks key1.p12 --ks-pass pass:changeit --next-signer --ks key2.p12 --ks-pass pass:changeit \
--lineage lineage.bin --out app-rotated.apk F-Droid.apk
apksigner verify -v --print-certs app-rotated.apk
apksigner lineage --in app-rotated.apk --print-certs
Verified using v3 scheme (APK Signature Scheme v3): true
Verified using v3.1 scheme (APK Signature Scheme v3.1): true
V3.1 Signer: (minSdkVersion=33, maxSdkVersion=2147483647) certificate DN: CN=Example App signing key 2027
V3.1 Signer: (minSdkVersion=33, maxSdkVersion=2147483647) key algorithm: EC
V3.0 Signer: (minSdkVersion=24, maxSdkVersion=32) certificate DN: CN=Example App signing key 2026
V3.0 Signer: (minSdkVersion=24, maxSdkVersion=32) key algorithm: EC
Signer #1 in lineage certificate DN: CN=Example App signing key 2026
Has installed data capability: true
Has shared UID capability : true
Has permission capability : true
Has rollback capability : false
Has auth capability : true
Signer #2 in lineage certificate DN: CN=Example App signing key 2027
Has installed data capability: true
Has shared UID capability : true
Has permission capability : true
Has rollback capability : false
Has auth capability : true
By default apksigner puts the new key in a v3.1 block for Android 13 and later and keeps the old key in the v3.0 block for Android 7 to 12. That is deliberate: AOSP says "APK key rotation is not recommended for Android 12 (API level 31) and earlier", because only from Android 13 does checkSignatures recognize the lineage and "return the newest signature". --rotation-min-sdk-version 28 forces rotation onto Android 9 and later; the apksigner documentation reserves that for apps that already rotated on older platforms (apksigner).
The capabilities say what the old key may still do: install updates over data signed by it, share a UID, hold signature permissions, and (here false) roll back to it. Revoke them with apksigner lineage if the old key was compromised.
Post-quantum: hybrid v3.2
Android 17 adds v3.2, "a hybrid signature scheme designed to support the Android ecosystem's transition to post-quantum cryptography": an APK is signed "with both a classical algorithm, like RSA or ECDSA, and a PQC algorithm", initially ML-DSA-65 or ML-DSA-87, in a block with ID 0x70e1c89f that "must contain exactly two signers" (v3.2). Android 17 verifies both; older versions skip the block and use v3.0 or v3.1, which must still be present. The platform treats the PQC key as a rotation of the classical one, and the two signers must have "identical preceding signing histories".
Java 25's keytool creates ML-DSA keys (-keyalg ML-DSA-65), and build-tools 37.0.0 signs with them. It took some trying: apksigner failed with a NullPointerException until both hybrid signers had a lineage, and the PQC lineage had to be a rotation from the same previous key as the classical one:
keytool -genkeypair -keystore pq.p12 -storetype PKCS12 -storepass changeit -alias app \
-dname "CN=Example App PQ signing key" -validity 10000 -keyalg ML-DSA-65
apksigner rotate --out lineage-pq.bin --old-signer --ks key1.p12 --ks-pass pass:changeit \
--new-signer --ks pq.p12 --ks-pass pass:changeit
apksigner sign --ks key1.p12 --ks-pass pass:changeit --next-signer --ks key2.p12 --ks-pass pass:changeit --lineage lineage.bin \
--next-signer --ks key2.p12 --ks-pass pass:changeit --hybrid-signer-role classical --signer-lineage lineage.bin \
--next-signer --ks pq.p12 --ks-pass pass:changeit --hybrid-signer-role pqc --signer-lineage lineage-pq.bin \
--hybrid-min-sdk-version 37 --out app-hybrid.apk F-Droid.apk
apksigner verify -v --print-certs app-hybrid.apk
Verifies
Verified using v2 scheme (APK Signature Scheme v2): true
Verified using v3 scheme (APK Signature Scheme v3): true
Verified using v3.1 scheme (APK Signature Scheme v3.1): true
Verified using v3.2 scheme (APK Signature Scheme v3.2): true
V3.2 Hybrid Classical Signer: (minSdkVersion=36 (dev release=true), maxSdkVersion=2147483647) certificate DN: CN=Example App signing key 2027
V3.2 Hybrid Classical Signer: (minSdkVersion=36 (dev release=true), maxSdkVersion=2147483647) key algorithm: EC
V3.2 Hybrid PQC Signer: (minSdkVersion=36 (dev release=true), maxSdkVersion=2147483647) certificate DN: CN=Example App PQ signing key
V3.2 Hybrid PQC Signer: (minSdkVersion=36 (dev release=true), maxSdkVersion=2147483647) key algorithm: ML-DSA
V3.1 Signer: (minSdkVersion=33, maxSdkVersion=2147483647) certificate DN: CN=Example App signing key 2027
V3.0 Signer: (minSdkVersion=24, maxSdkVersion=32) certificate DN: CN=Example App signing key 2026
One APK now carries three generations: key1 for Android 7 to 12, key2 for Android 13 and later, and key2 plus the ML-DSA-65 key for the v3.2-capable platform. The hybrid APK grew by 20,480 bytes over the singly-signed one. Even with --hybrid-min-sdk-version 37, this apksigner printed the v3.2 range as "36 (dev release=true)", the encoding it uses for a platform still under development; check the output of the build-tools version you ship with. Most developers will never run these commands: Play generates and manages the hybrid keys (next section).
v4 and incremental installs
--v4-signing-enabled true writes a .idsig file next to the APK. It holds a Merkle tree over the APK in the fs-verity format and a signature over its root, so adb install --incremental can start the app before all bytes have arrived. AOSP is explicit that "A v4 signature requires a complementary v2 or v3 signature." In the lab the signature file was 103,041 bytes for the 12.6 MB APK, and apksigner verify --v4-signature-file app-v4.apk.idsig reported Verified using v4 scheme (APK Signature Scheme v4): true. v4 is a development and streaming feature; it is not uploaded to stores.
Play App Signing and developer verification
On Google Play the key that signs what users install is usually not yours. With Play App Signing you sign the app bundle with an upload key ("an RSA key of 2048 bits or more") and Google re-signs the APKs it delivers with the app signing key it keeps. Two consequences:
- A lost or leaked upload key is recoverable: "you are not locked out of your app"; you request an upload key reset in the Play Console with a new certificate.
- Rotation of the app signing key ("key upgrade") is a Play Console operation. Play now enrolls new apps "in quantum-ready, hybrid signing with Google-generated keys": "a classical RSA 4096-bit key and a post-quantum ML-DSA-65 key", plus a classical key for older devices, "resulting in your app using three distinct keys". Play notes that hybrid signing is currently incompatible with v4, and offers an annual key upgrade for installs on Android 17 and later.
Outside Play, the signing key matters even more. Android's developer verification registers developers and their package names with signing keys for apps installed on certified devices: protections began on 30 September 2026 in Brazil, Indonesia, Singapore and Thailand, and Google plans to expand verification globally in 2027. Its FAQ is blunt: "If you lose your signing key you won't be able to register your packages."
Common mistakes
- Changing the APK after signing. With
apksigner, "zipalignmust be used before the APK file has been signed" (zipalign); the same holds for any tool that rewrites the ZIP. Anything after signing produces the digest mismatch above. - Trusting "Verifies". Any key produces a valid signature. Compare the signer certificate's SHA-256 digest with the one you expect (the Play Console shows yours; F-Droid and many open-source projects publish theirs).
- Rotating with v3 on Android 12 and earlier without a reason. Let
apksignertarget v3.1. - A single copy of the signing key. For apps outside Play there is no reset: the key is the app.
- Keys in the repository. Keep the upload key in a hardware-backed or cloud key store and give CI a signing permission rather than the key file.
- Pinning the app signing certificate in your backend (API allowlists, OAuth client registrations, Firebase or Maps keys restricted by certificate) without planning for a key upgrade: Play's documentation asks you to register the new fingerprints with your API providers, two new keys with hybrid signing.
Sources
- AOSP: APK signing overview, v2, v3, v3.1, v3.2, v4
- Android developers: apksigner, Android 11 behavior changes, developer verification and its FAQ
- Google Play: Use Play App Signing
- Tools: Android SDK build-tools 37.0.0 (
apksigner), JDK 25.0.4.1 (keytool), F-Droid client APK