Android APK signature scheme v3: context and new opportunities
Android 9.0 ("Pie") introduced a lot of new functionality. While updates in the user interface catch the most attention, some security improvements under the hood are just as interesting. We zoom in on the new APK signature scheme v3, putting it into context and discussing the new opportunities it offers.
A bit of history: from ZIP over JAR to APK
In the 1980s, Phil Katz created the ZIP format. It combines individual files and some metadata in a single file for convenient transfer and archival. The format provided practical solutions for problems with compression, checksums and redundant headers. It also enabled the storage of large (more than 720 KB!) files on multiple floppy disks (multi-ZIP).
Sun used the ZIP format as the foundation for the JAR format it introduced in the 1990s for distributing Java code. A JAR file is essentially a ZIP archive that includes additional metadata in the form of META-INF/* files. The META-INF/MANIFEST.MF file contains some basic headers, for example with the application name and with version numbers. It is also the basis of the JAR signature scheme: it contains a cryptographic hash for each of the files in the archive. A file like META-INF/CERT.SF contains further derived hashes and a file like META-INF/CERT.RSA contains the signing certificate and the actual cryptographic signature for the hashes.
When Android came along, its APK format for distributing apps in turn built on the JAR format. It adds more standardized structure with files like classes.dex for Dalvik bytecode and resources.arsc for resources. At the time, Android relied entirely on the original JAR signature scheme to ensure the authenticity of the app. (Note that Android is based on the principle of self-signing. Any developer can create his or her own certificate to sign applications. The signature is only relevant to ensure the authenticity of subsequent versions of an app or to identify related apps.)
Years later, Android Nougat added a new signing format, APK signature scheme v2. This optional signature ensures a stronger verification of the APK file. Instead of checking the individual files inside the archive, it checks the archive as a whole. It inserts an out-of-band signature block in the ZIP file, covering the remainder of the ZIP file. This proved to be a sound conservative approach from a security perspective. The pretty horrid redundancy in the ZIP format results in ambiguities. This had already given rise to inconsistent implementations in different parts of Android. For example, the Janus attack we discussed last year inserts code in the empty spaces between the regular files in the ZIP file. The inserted code escapes all checks, but an unrelated part of Android mistakenly interprets it as the actual code. By checking the entire APK file, Android has an additional line of defense against such changes to signed APK files. Of course, the old JAR signature scheme is still allowed for backward compatibility, so apps are only protected if they are properly signed with signature scheme v2 and run on devices with sufficiently recent updates.
All bytes in an APK file are covered against changes now! All bytes in an APK file? Well, some bytes are left unchecked. The signature block covers the main body of the APK file and some elements of the signature block itself. The block remains extensible though. Developers can still add additional signatures after the APK has been signed, without invalidating previous signatures. For example, the Google Play store makes use of this feature by adding a custom signature to all apps that are uploaded, after their developers have already signed them.
APK key rotation with APK signature scheme v3
Android Pie introduces yet another signing format, APK signature scheme v3. Signature scheme v3 adds a new block inside the APK file, in addition to the v2 block. Its format is very similar, but it has a few extensions. The most obvious one is the new signature lineage to support certificate rotation or APK key rotation. This means that you can now change the signing certificate when you release newer versions of your app. If you sign the initial version with a given key, but for some reason need to switch to a different key, you now can. The reason may be that the structure of your company has changed or you just want to reorganize your own key stores. Signature scheme v3 supports it by including a list of certificates, in which a signature of each older certificate vouches for the subsequent newer certificate in line.
You can create a lineage file with a sequence of certificates with 'apksigner rotate' and apply it when you sign the application with 'apksigner sign'. For example, you can create a certificate lineage file that migrates an initial key to a new key with:
apksigner rotate --out debug.lineage \
--old-signer --ks debug1.keystore --ks-key-alias androiddebugkey \
--new-signer --ks debug2.keystore --ks-key-alias androiddebugkey
You can apply this file in the v3 block with:
apksigner sign \
--ks debug1.keystore --next-signer \
--ks debug2.keystore \
--lineage debug.lineage \
application.apk
As always, only sufficiently recent versions of Android recognize signature scheme v3. Older versions of Android only recognize v2 or even v1. You don't get certificate rotation there: the signing tool always needs to sign the older blocks with the oldest key, so old devices still accept updates of the app.
Signature scheme v3 also adds a few less visible new features. It distinguishes between target versions of Android, so it can specify different digest algorithms (SHA1, SHA256, etc.) and signature algorithms (RSA, EC, etc.) for different versions of Android. It also supports the more efficient digest algorithm `fsverity` which Android also uses at boot-time, to check the integrity of the file system.
Signature schemes stand united
With all these redundant signature blocks, an attacker could conceivably delete a recent (strong) signature to attack an older (weaker) signature. The signature schemes are prepared for such attacks: the older signature blocks have attributes to indicate that newer signature blocks are present. If an attacker removes the newer blocks, Android falls back on the older blocks but notices that the signature has been tampered with. Older versions of Android unavoidably only recognize the older signing schemes.
Application signing with the apksigner tool
Apksigner is the tool that performs all the signing magic. Build tools version 28.0.3 doesn't support APK signature scheme v3 yet, but you can find the development source code online and compile it yourself. You can see all the available options with 'apksigner sign', 'apksigner rotate', and 'apksigner verify'. Be warned that this version still has a few issues. For example, the tool sometimes prints "VERIFICATION FAILED", swallowing helpful messages about the root cause of the verification problem.
Is the APK signature scheme v3 supported already?
Devices with Android Pie fully support APK signature scheme v3. However, at the time of writing, Android Studio, the Android Gradle plugin and even Google Play don't handle it yet. We'll have to wait for support to ripple through the development chain.
Our own software DexGuard supports all APK signature schemes: v1, v2, and v3. (APK signature scheme v3 is supported from DexGuard 8.3 onwards). Notably, you can pass a certificate lineage file or multiple keys to rotate certificates, in any build environment.