En bref. Selon l’appareil, Android vérifie l’un de plusieurs formats de signature : v1 (signature JAR, fichier par fichier, pour Android 6 et les versions antérieures), v2 (signature du fichier entier dans le bloc de signature APK, Android 7.0), v3 (v2 plus une lignée de preuve de rotation, Android 9), v3.1 (rotation qui ne cible qu’Android 13 et les versions ultérieures), v3.2 (signature hybride, classique plus ML-DSA, Android 17) et v4 (fichier .idsig séparé pour les installations incrémentielles, Android 11). Les applications qui ciblent le niveau d’API 30 ou supérieur doivent porter une signature v2 ou plus récente. Les mises à jour doivent être signées par la même clé ou par une clé qui lui succède dans la lignée de rotation de l’application, si bien que la clé de signature est l’identité de l’application pendant toute sa durée de vie : confiez-la à Google Play (Play App Signing), ne conservez vous-même que la clé d’importation et vérifiez les versions publiées avec apksigner verify -v --print-certs.
Les essais ont été menés avec l’outil apksigner des build-tools 37.0.0 du SDK Android (il s’annonce lui-même en version 0.9), sur Java 25.0.4.1 dans eclipse-temurin:25.0.4.1_1-jdk-noble. L’échantillon est l’APK du client F-Droid, tel que téléchargé depuis f-droid.org le 22 octobre 2026 ; toutes les autres clés ont été générées pour cet article.
Les schémas
| Schéma | Depuis | Où | Ce qu’il ajoute |
|---|---|---|---|
| v1 | Android 1.0 | META-INF/*.SF, *.RSA/*.EC | Signature JAR : un condensat par entrée ZIP ; les métadonnées ZIP ne sont pas couvertes |
| v2 | Android 7.0 (API 24) | Bloc de signature APK (APK Signing Block), ID 0x7109871a | Signature sur tout le fichier, à l’exception du bloc lui-même |
| v3 | Android 9 (API 28) | ID 0xf05368c0 | v2 plus la plage de SDK prise en charge et une lignée de preuve de rotation |
| v3.1 | Android 13 (API 33) | un nouvel ID de bloc | Rotation appliquée uniquement sur Android 13 et les versions ultérieures |
| v3.2 | Android 17 (API 37) | ID 0x70e1c89f | Exactement deux signataires, un classique et un post-quantique, tous deux vérifiés |
| v4 | Android 11 (API 30) | app.apk.idsig à côté de l’APK | Arbre de Merkle sur l’APK pour adb install --incremental ; nécessite v2 ou v3 |
Le schéma de signature APK v2 « traite le fichier APK comme un blob et vérifie la signature sur l’ensemble du fichier », de sorte que « toute modification de l’APK, y compris des métadonnées ZIP, invalide la signature de l’APK » (AOSP). Il est aussi plus rapide : l’appareil ne décompresse plus chaque entrée pour la vérifier. v1 ne sert plus qu’aux appareils antérieurs à Android 7.0 ; un APK dont le minSdkVersion vaut 24 ou plus n’en a pas besoin, et apksigner l’omet. Conserver v1 ne permet pas de masquer la suppression d’une signature v2 : les APK signés en v2 qui sont aussi signés en v1 portent dans leurs fichiers .SF un attribut X-Android-APK-Signed, couvert par la signature v1, et les vérificateurs doivent rejeter un APK auquel il manque une signature pour le schéma qu’il annonce.
Depuis Android 11, « les applications qui ciblent Android 11 (niveau d’API 30) et qui ne sont actuellement signées qu’avec le schéma de signature APK v1 doivent désormais être signées aussi avec le schéma de signature APK v2 ou supérieur » (changements de comportement).
Lire la sortie d’apksigner verify
apksigner verify -v --print-certs vérifie, pour la plage de SDK prise en charge par l’APK, chacun des schémas qu’il contient, puis affiche les signataires. Pour le client F-Droid publié (sortie filtrée sur les lignes des schémas et des signataires) :
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 pour v1 n’est pas une erreur : le SDK minimal de l’application est 24, v1 est donc inutile. Le condensat du certificat est la valeur qui identifie le signataire de l’application : F-Droid publie exactement 43238d51…a9c9ccab pour son client sur sa page des clés de signature, et Android compare le signataire à chaque mise à jour. Il n’y a pas de chaîne de confiance : les certificats d’APK sont généralement auto-signés, et les règles de vérification v2 contrôlent que le SubjectPublicKeyInfo du premier certificat correspond à la clé de signature, sans aucune étape pour les émetteurs ni pour les dates de validité (v2). Ce certificat a été émis en 2010.
Re-signer une copie avec notre propre clé P-256, puis modifier un seul octet du fichier, montre les deux volets de cette vérification :
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>
La copie re-signée passe la vérification sans le moindre problème, avec un certificat différent. C’est pourquoi c’est le condensat du signataire, et non « Verifies », qui vous indique qu’un APK provient du bon éditeur. Si on l’installait par-dessus le F-Droid d’origine, Android la refuserait, car ce serait une mise à jour signée par une autre clé. Le condensat CHUNKED_SHA256 est calculé sur des segments consécutifs de 1 Mo des sections du fichier, ce qui permet à v2 et aux schémas suivants de vérifier rapidement et en parallèle.
Rotation de la clé : v3 et v3.1
Une application ne peut pas changer de clé de signature simplement en étant re-signée : la mise à jour serait rejetée. v3 ajoute une preuve de rotation (proof-of-rotation), « une liste simplement chaînée dont chaque nœud contient un certificat de signature utilisé pour signer des versions précédentes de l’application », dans laquelle chaque certificat signe le suivant (v3). Un appareil qui connaît l’ancienne clé accepte un APK signé par la nouvelle si la lignée les relie.
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
Par défaut, apksigner place la nouvelle clé dans un bloc v3.1 pour Android 13 et les versions ultérieures, et conserve l’ancienne clé dans le bloc v3.0 pour Android 7 à 12. C’est voulu : l’AOSP indique que « la rotation des clés APK n’est pas recommandée pour Android 12 (niveau d’API 31) et les versions antérieures », car ce n’est qu’à partir d’Android 13 que checkSignatures reconnaît la lignée et « renvoie la signature la plus récente ». --rotation-min-sdk-version 28 force la rotation à partir d’Android 9 ; la documentation d’apksigner réserve cette option aux applications qui ont déjà effectué une rotation sur des plateformes plus anciennes (apksigner).
Les capacités indiquent ce que l’ancienne clé peut encore faire : installer des mises à jour par-dessus des données signées par elle, partager un UID, détenir des autorisations de signature et (ici false) permettre de revenir à elle. Révoquez-les avec apksigner lineage si l’ancienne clé a été compromise.
Post-quantique : v3.2 hybride
Android 17 ajoute v3.2, « un schéma de signature hybride conçu pour accompagner la transition de l’écosystème Android vers la cryptographie post-quantique » : un APK est signé « à la fois avec un algorithme classique, comme RSA ou ECDSA, et avec un algorithme PQC », dans un premier temps ML-DSA-65 ou ML-DSA-87, dans un bloc d’ID 0x70e1c89f qui « doit contenir exactement deux signataires » (v3.2). Android 17 vérifie les deux ; les versions plus anciennes ignorent ce bloc et utilisent v3.0 ou v3.1, qui doivent toujours être présents. La plateforme traite la clé PQC comme une rotation de la clé classique, et les deux signataires doivent avoir « des historiques de signature antérieurs identiques ».
Le keytool de Java 25 crée des clés ML-DSA (-keyalg ML-DSA-65), et les build-tools 37.0.0 signent avec elles. Il a fallu plusieurs essais : apksigner échouait avec une NullPointerException tant que les deux signataires hybrides n’avaient pas chacun une lignée, et la lignée PQC devait être une rotation à partir de la même clé précédente que la lignée classique :
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
Un seul APK porte désormais trois générations : key1 pour Android 7 à 12, key2 pour Android 13 et les versions ultérieures, et key2 plus la clé ML-DSA-65 pour la plateforme compatible avec v3.2. L’APK hybride pèse 20 480 octets de plus que l’APK à signature simple. Même avec --hybrid-min-sdk-version 37, cet apksigner a affiché la plage v3.2 sous la forme « 36 (dev release=true) », l’encodage qu’il utilise pour une plateforme encore en développement ; vérifiez la sortie de la version des build-tools avec laquelle vous publiez. La plupart des développeurs n’exécuteront jamais ces commandes : Play génère et gère les clés hybrides (section suivante).
v4 et installations incrémentielles
--v4-signing-enabled true écrit un fichier .idsig à côté de l’APK. Il contient un arbre de Merkle sur l’APK au format fs-verity et une signature de sa racine, ce qui permet à adb install --incremental de lancer l’application avant que tous les octets soient arrivés. L’AOSP le dit explicitement : « Une signature v4 nécessite une signature v2 ou v3 complémentaire. » Lors de nos essais, le fichier de signature faisait 103 041 octets pour un APK de 12,6 Mo, et apksigner verify --v4-signature-file app-v4.apk.idsig a affiché Verified using v4 scheme (APK Signature Scheme v4): true. v4 est une fonctionnalité de développement et de streaming ; il n’est pas importé dans les boutiques d’applications.
Play App Signing et vérification des développeurs
Sur Google Play, la clé qui signe ce que les utilisateurs installent n’est généralement pas la vôtre. Avec Play App Signing, vous signez l’app bundle avec une clé d’importation (« une clé RSA de 2048 bits ou plus ») et Google re-signe les APK qu’il distribue avec la clé de signature d’application qu’il conserve. Deux conséquences :
- La perte ou la fuite d’une clé d’importation n’est pas irrémédiable : « vous ne perdez pas l’accès à votre application » ; vous demandez la réinitialisation de la clé d’importation dans la Play Console, avec un nouveau certificat.
- La rotation de la clé de signature d’application (« mise à niveau de la clé ») s’effectue dans la Play Console. Play inscrit désormais les nouvelles applications « à la signature hybride prête pour l’ère quantique, avec des clés générées par Google » : « une clé RSA classique de 4096 bits et une clé post-quantique ML-DSA-65 », plus une clé classique pour les appareils plus anciens, « si bien que votre application utilise trois clés distinctes ». Play précise que la signature hybride est actuellement incompatible avec v4, et propose une mise à niveau annuelle de la clé pour les installations sur Android 17 et les versions ultérieures.
Hors de Play, la clé de signature compte encore davantage. La vérification des développeurs d’Android enregistre les développeurs, ainsi que leurs noms de package et leurs clés de signature, pour les applications installées sur des appareils certifiés : les protections sont entrées en vigueur le 30 septembre 2026 au Brésil, en Indonésie, à Singapour et en Thaïlande, et Google prévoit d’étendre la vérification au monde entier en 2027. Sa FAQ est sans détour : « Si vous perdez votre clé de signature, vous ne pourrez pas enregistrer vos packages. »
Erreurs fréquentes
- Modifier l’APK après la signature. Avec
apksigner, «zipaligndoit être utilisé avant que le fichier APK soit signé » (zipalign) ; il en va de même pour tout outil qui réécrit le ZIP. Toute modification après la signature produit la non-concordance des condensats montrée plus haut. - Se fier à « Verifies ». N’importe quelle clé produit une signature valide. Comparez le condensat SHA-256 du certificat du signataire à celui que vous attendez (la Play Console affiche le vôtre ; F-Droid et de nombreux projets open source publient le leur).
- Effectuer la rotation avec v3 sur Android 12 et les versions antérieures sans raison. Laissez
apksignercibler v3.1. - Une seule copie de la clé de signature. Pour les applications distribuées hors de Play, aucune réinitialisation n’est possible : la clé, c’est l’application.
- Des clés dans le dépôt. Conservez la clé d’importation dans un magasin de clés adossé à du matériel ou dans le cloud, et donnez à la CI une autorisation de signer plutôt que le fichier de clé.
- Épingler le certificat de signature d’application dans votre backend (listes d’autorisation d’API, enregistrements de clients OAuth, clés Firebase ou Maps restreintes par certificat) sans prévoir de mise à niveau de la clé : la documentation de Play vous demande d’enregistrer les nouvelles empreintes auprès de vos fournisseurs d’API, soit deux nouvelles clés avec la signature hybride.
Sources
- AOSP : présentation de la signature des APK, v2, v3, v3.1, v3.2, v4
- Android Developers : apksigner, changements de comportement d’Android 11, vérification des développeurs et sa FAQ
- Google Play : Utiliser Play App Signing
- Outils : Android SDK build-tools 37.0.0 (
apksigner), JDK 25.0.4.1 (keytool), APK du client F-Droid