Tutorials

APK Signature Schemes v1, v2, v3, and v4 Explained (and How to Sign with apksigner)

Springmusk Springmusk 9 min read 1 comment
Abstract illustration of layered file sheets forming a shield with a seal, representing Android APK signature schemes

TL;DR: Every APK Android installs must be signed. Four signature schemes exist: v1 (JAR signing, from Android 1.0) signs files inside the ZIP and survives only for very old devices; v2 (Android 7.0) signs the whole file and closes the Janus hole; v3 (Android 9) adds key rotation so a signing key can be changed without breaking updates; v4 (Android 11) writes a separate .idsig file to support incremental installs. In practice: sign with apksigner, run zipalign before signing, and verify with apksigner verify -v.

Why Android signs APKs at all

An APK signature is not about a certificate authority vouching for the developer. Android performs no CA verification on app certificates, and self-signed certificates are the norm. The signature does three concrete jobs instead.

First, it gives the Package Manager an identity to check at install time. An update must be signed with the same key as the installed package, or the update is rejected. Second, it feeds the application sandbox: two apps signed with the same certificate can declare a shared UID and access each other’s data, while everyone else stays isolated. Third, it backs signature-level permissions, which are granted only to apps signed with the same key as the app that declared them.

That is why signing failures show up as install failures, not warnings. A common one: an APK signed with only the v1 scheme fails to install on a device targeting API 34, with the installer reporting that the target SDK requires a minimum of signature scheme v2. The rest of this post explains what the schemes are, which ones you need, and how to sign correctly.

v1: JAR signing (Android 1.0)

The original scheme is plain JAR signing, the same format Java uses. It signs each file inside the ZIP individually and stores the signatures in META-INF/. The weakness is what it does not sign: ZIP metadata and any bytes outside the ZIP structure are invisible to the signature.

That blind spot became CVE-2017-13156, nicknamed Janus. An attacker could prepend a malicious DEX file to the front of an APK; the v1 signature still verified because it never covered the prepended bytes, while Android’s runtime would see the DEX magic first and execute the attacker’s code instead of the app. Android 7.0 introduced the v2 scheme to close this class of attack.

Today v1 exists only for compatibility with devices older than Android 7.0. If your minSdkVersion is 24 or higher, every device that installs your app verifies v2, and v1 is dead weight.

v2: whole-APK signature (Android 7.0, API 24)

The v2 scheme stops treating the APK as a bag of files and treats it as one binary blob. The entire file is hashed and signed, and the signature is stored in an APK Signing Block inserted into the APK itself. During validation the hash covers everything except the signing block; any modification to the APK, including changes to ZIP metadata, invalidates the signature.

Two properties matter in practice. First, because the signature is bound to the whole file, stripping the v2 block does not quietly downgrade you to v1: the v1 signature records that the APK was v2-signed, so Android 7.0 and newer refuse to fall back to the v1 signature. Second, verification is substantially faster, because the verifier no longer has to decompress every compressed entry just to check signatures, the way the v1 path does.

The important operational rule comes directly from this design: v2 is a whole-APK signature, so any tool that rewrites APK bytes must run before signing. Running zipalign after apksigner produces an aligned APK with an invalid signature.

v3: key rotation (Android 9, API 28)

The v3 scheme works exactly like v2, plus one feature: proof-of-rotation lineage. The lineage is a linked list of certificates in which each certificate signs the next one, forming a trust chain from the original signing key to the current one. With it, a developer can rotate a compromised or expiring signing key and existing installations will still accept updates, because the chain proves the new key descends from the old one.

Rotation is created with apksigner rotate and applied at sign time. The lineage file must be kept alongside the keystore: lose it and future releases lose the chain that ties them to installed apps.

v3.1: rotation done right (Android 13, API 33)

The v3.1 block refines rotation with a --rotation-min-sdk-version setting, which controls the lowest API level the rotated key is used for. By default apksigner targets API 33 and above with the rotated key; the original key keeps signing the v1/v2/v3.0 blocks so older devices keep updating. If you need the rotated key to apply on Android 9 through 12 as well, pass --rotation-min-sdk-version 28, but only with a reason: v3.0-style rotation has known issues on API 28 to 32, which is exactly why v3.1 exists.

v4: incremental installs (Android 11, API 30)

The v4 scheme produces a separate signature file, app.apk.idsig, containing a Merkle hash tree over the APK contents. The tree structure lets the system verify individual blocks of the APK as they stream to the device, which is what enables incremental installation: the app can start installing before the whole file has downloaded.

Two caveats. The .idsig file is a separate artifact that must be distributed alongside the APK. And v4 is not a standalone security scheme: it provides no protection on its own and should always be used together with v2 or v3. Most developers outside the incremental-install path simply leave it disabled.

The four schemes side by side

Scheme Introduced What it signs Key property
v1 (JAR) Android 1.0 Individual ZIP entries Legacy; misses ZIP metadata; vulnerable to Janus (CVE-2017-13156)
v2 Android 7.0 (API 24) Entire APK as one blob Any byte change invalidates it; stripped v2 blocks are rejected, not downgraded
v3 Android 9 (API 28) Entire APK, plus rotation lineage Key rotation via proof-of-rotation certificate chain
v3.1 Android 13 (API 33) Entire APK, refined rotation Rotation targeting with --rotation-min-sdk-version
v4 Android 11 (API 30) Merkle tree in separate .idsig file Streaming verification for incremental installs; not standalone security

At install time the platform tries the newest scheme the device supports and falls back: v4, then v3, then v2, then v1.

Signing an APK by hand with apksigner

apksigner ships in the Android SDK build tools ($ANDROID_HOME/build-tools/<version>/apksigner) and is the tool to use. jarsigner from the JDK only produces v1 signatures, which modern Android rejects for apps with a modern target SDK.

Step one, create a keystore (once):

keytool -genkeypair -v -keystore release.keystore -alias myapp \
  -keyalg RSA -keysize 2048 -validity 10000

The 10000-day validity is the conventional example value; it works out to roughly 27 years, comfortably beyond the certificate lifetime Google Play expects. Store the keystore and its passwords somewhere you will not lose: a lost signing key means no in-place updates ever again.

Step two, align (before signing, always):

zipalign -f -p 4 app-unsigned.apk app-aligned.apk

Step three, sign:

apksigner sign \
  --ks release.keystore --ks-key-alias myapp \
  --ks-pass pass:YOUR_KEYSTORE_PASSWORD \
  --key-pass pass:YOUR_KEY_PASSWORD \
  --v1-signing-enabled true \
  --v2-signing-enabled true \
  --v3-signing-enabled true \
  app-aligned.apk

Omit --out and apksigner signs in place. The flags are optional: by default apksigner reads the APK’s minSdkVersion and enables the schemes that make sense, skipping v1 when every supported device verifies v2. The explicit flags are useful when you want to force a specific set, for example disabling v1 for a minSdk 24 app.

Step four, verify. This is the command to run in CI, because unlike jarsigner -verify it exits non-zero when verification fails:

apksigner verify --verbose --print-certs app-aligned.apk

A healthy signed APK reports something like:

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 v4 scheme (APK Signature Scheme v4): false
Verified for SourceStamp: false
Number of signers: 1

If you build with the Android Gradle Plugin instead, the same schemes are controlled in signingConfigs with enableV3Signing and enableV4Signing. Note one real-world quirk: keytool -printcert -jarfile only understands v1 signatures, so on a modern APK it reports “Not a signed jar file” even when the APK is perfectly signed. Use apksigner verify --print-certs to read the certificate fingerprint.

What goes wrong

Symptom Cause Fix
Install fails: “requires a minimum of signature scheme v2” (seen with targetSdk 34) APK signed with jarsigner, which only produces v1 Sign with apksigner so v2 is present
Install fails right after running zipalign Aligned after signing, which rewrites bytes the v2/v3 signature covers Always run zipalign first, then sign
Update rejected on users’ devices after a rebuild Different signing key than the installed version Updates must use the same key; plan v3 rotation lineage before the key is lost
keytool -printcert -jarfile says “Not a signed jar file” keytool only reads v1 signatures Use apksigner verify --print-certs instead

Which schemes should you ship?

Google’s own guidance is to sign with all schemes for maximum compatibility, v1 first, then v2, then v3, because old devices ignore the newer signatures and new devices ignore v1. In practice the decision is mechanical:

  • minSdk below 24: keep v1 on, so Android 6.x and older can install. Accept that those installs carry the v1 weaknesses; devices that support v2 will verify v2.
  • minSdk 24 or higher: disable v1. Every device that installs the app verifies v2, and v1 adds bytes for nothing.
  • Need to change keys: build a v3 rotation lineage with apksigner rotate and keep the lineage file with the keystore.
  • v4: enable only if you distribute APKs for incremental installs (for example adb install --incremental); otherwise skip it and its extra .idsig file.

Key takeaways

  • Four schemes cover four eras: v1 signs files, v2 signs the whole APK, v3 adds key rotation, v4 enables streaming installs. New devices always prefer the newest one.
  • Never use jarsigner for APKs. It only writes v1, and modern target SDKs require at least v2.
  • Run zipalign before signing, never after. Whole-APK signatures cover every byte.
  • Verify with apksigner verify --verbose in CI; it fails loudly, unlike jarsigner -verify.
  • A signing key is an identity. Back up the keystore, the passwords, and any rotation lineage, because an update signed with a different key is rejected.

References

One comment

Join the discussion

Your email address will not be published. Required fields are marked *