Коротко. В зависимости от устройства Android проверяет один из нескольких форматов подписи: v1 (подпись JAR, отдельно для каждого файла, для Android 6 и более старых версий), v2 (подпись всего файла в блоке подписи APK, Android 7.0), v3 (v2 с цепочкой ротации ключей proof-of-rotation, Android 9), v3.1 (ротация, рассчитанная только на Android 13 и новее), v3.2 (гибридная подпись: классическая плюс ML-DSA, Android 17) и v4 (отдельный файл .idsig для инкрементальной установки, Android 11). Для приложений с целевым уровнем API 30 и выше подпись v2 или более новая обязательна. Обновления должны быть подписаны тем же ключом или его преемником из цепочки ротации приложения, поэтому ключ подписи служит удостоверением приложения на весь срок его жизни: пусть его хранит Google Play (Play App Signing), у себя держите только ключ загрузки, а релизы проверяйте командой apksigner verify -v --print-certs.
В эксперименте использовался apksigner из Android SDK build-tools 37.0.0 (сам он называет себя версией 0.9) на Java 25.0.4.1 в eclipse-temurin:25.0.4.1_1-jdk-noble. Образец — APK клиента F-Droid, скачанный с f-droid.org 22 октября 2026 года; все остальные ключи сгенерированы для этой статьи.
Схемы подписи
| Схема | С версии | Где | Что добавляет |
|---|---|---|---|
| v1 | Android 1.0 | META-INF/*.SF, *.RSA/*.EC | Подпись JAR: хеш каждой записи ZIP; метаданные ZIP подписью не покрываются |
| v2 | Android 7.0 (API 24) | Блок подписи APK, ID 0x7109871a | Подпись всего файла, кроме самого блока |
| v3 | Android 9 (API 28) | ID 0xf05368c0 | v2 плюс диапазон поддерживаемых SDK и цепочка ротации ключей (proof-of-rotation) |
| v3.1 | Android 13 (API 33) | Новый ID блока | Ротация, которая применяется только на Android 13 и новее |
| v3.2 | Android 17 (API 37) | ID 0x70e1c89f | Ровно два подписанта, классический и постквантовый; проверяются оба |
| v4 | Android 11 (API 30) | app.apk.idsig рядом с APK | Дерево Меркла по APK для adb install --incremental; требует v2 или v3 |
Схема подписи APK v2 «рассматривает файл APK как единый двоичный объект (blob) и проверяет подпись по всему файлу», поэтому «любое изменение APK, включая изменение метаданных ZIP, делает подпись APK недействительной» (AOSP). Кроме того, такая проверка быстрее: устройству больше не нужно распаковывать каждую запись, чтобы её проверить. Схема v1 нужна только устройствам с версиями Android до 7.0: если minSdkVersion у APK равен 24 или больше, она не нужна, и apksigner её не добавляет. Если подпись v2 удалили, скрыть это, сохранив v1, не получится: APK, подписанные и по v2, и по v1, содержат в своих файлах .SF атрибут X-Android-APK-Signed, который покрыт подписью v1, а проверяющая сторона должна отклонить APK, в котором нет подписи по объявленной в нём схеме.
Начиная с Android 11 «приложения с целевой версией Android 11 (уровень API 30), которые сейчас подписаны только по схеме подписи APK v1, теперь должны быть подписаны также по схеме подписи APK v2 или выше» (изменения в поведении).
Как читать вывод apksigner verify
apksigner verify -v --print-certs проверяет каждую схему подписи из APK для того диапазона SDK, который поддерживает этот APK, и выводит подписантов. Для опубликованного клиента F-Droid (в выводе оставлены только строки о схемах и подписантах):
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 для v1 не означает ошибку: минимальная версия SDK приложения равна 24, поэтому v1 не нужна. Подписанта приложения идентифицирует именно дайджест сертификата: F-Droid публикует для своего клиента ровно 43238d51…a9c9ccab на странице ключей подписи, а Android сверяет подписанта при каждом обновлении. Цепочки доверия здесь нет: сертификаты APK обычно самоподписанные, а правила проверки v2 требуют, чтобы SubjectPublicKeyInfo первого сертификата совпадал с ключом подписи, но не предусматривают никакой проверки издателей или сроков действия (v2). Этот сертификат выпущен в 2010 году.
Если переподписать копию собственным ключом P-256, а затем изменить в файле один байт, видны обе стороны этой проверки:
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>
Переподписанная копия безупречно проходит проверку, только сертификат у неё другой. Поэтому о том, что APK выпустил нужный разработчик, говорит дайджест подписанта, а не строка «Verifies». Если бы такую копию установили поверх оригинального F-Droid, Android отклонил бы её как обновление, подписанное другим ключом. Дайджест CHUNKED_SHA256 вычисляется по последовательным фрагментам размером 1 МБ, на которые разбиты разделы файла, благодаря чему v2 и более новые схемы проверяют файл быстро и параллельно.
Ротация ключа: v3 и v3.1
Сменить ключ подписи приложения простой переподписью нельзя: такое обновление будет отклонено. Схема v3 добавляет структуру proof-of-rotation (доказательство ротации) — «односвязный список, каждый узел которого содержит сертификат подписи, использованный для подписи предыдущих версий приложения», причём каждый сертификат подписывает следующий (v3). Устройство, которому известен старый ключ, примет APK, подписанный новым, если цепочка ротации связывает эти ключи.
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
По умолчанию apksigner помещает новый ключ в блок v3.1 для Android 13 и новее, а в блоке v3.0 для Android 7–12 оставляет старый ключ. Это сделано намеренно: AOSP пишет, что «ротация ключей APK не рекомендуется для Android 12 (уровень API 31) и более ранних версий», потому что только начиная с Android 13 checkSignatures распознаёт цепочку ротации и «возвращает самую новую подпись». Флаг --rotation-min-sdk-version 28 принудительно включает ротацию для Android 9 и новее; документация apksigner оставляет этот вариант для приложений, которые уже провели ротацию на более старых платформах (apksigner).
Возможности (capabilities) показывают, что ещё разрешено старому ключу: устанавливать обновления поверх данных, подписанных им, использовать общий UID, владеть разрешениями уровня signature и (здесь false) допускать откат обратно к нему. Если старый ключ скомпрометирован, отзовите их командой apksigner lineage.
Постквантовая подпись: гибридная схема v3.2
В Android 17 появилась схема v3.2 — «гибридная схема подписи, призванная поддержать переход экосистемы Android на постквантовую криптографию»: APK подписывается «одновременно классическим алгоритмом, например RSA или ECDSA, и алгоритмом PQC» (на первом этапе это ML-DSA-65 или ML-DSA-87) в блоке с ID 0x70e1c89f, который «должен содержать ровно двух подписантов» (v3.2). Android 17 проверяет обе подписи; более старые версии пропускают этот блок и используют v3.0 или v3.1, которые по-прежнему обязательны. Платформа считает ключ PQC результатом ротации классического ключа, а у двух подписантов должны быть «идентичные предшествующие истории подписи».
keytool из Java 25 создаёт ключи ML-DSA (-keyalg ML-DSA-65), а build-tools 37.0.0 умеют ими подписывать. Получилось это не с первой попытки: apksigner падал с NullPointerException, пока цепочка ротации не появилась у обоих гибридных подписантов, причём цепочка PQC должна была описывать ротацию с того же предыдущего ключа, что и классическая:
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
Теперь в одном APK три поколения ключей: key1 для Android 7–12, key2 для Android 13 и новее, а key2 вместе с ключом ML-DSA-65 — для платформы с поддержкой v3.2. Гибридный APK на 20 480 байт больше APK с одной подписью. Даже с --hybrid-min-sdk-version 37 эта версия apksigner вывела диапазон v3.2 как «36 (dev release=true)»: такое кодирование она использует для платформы, которая ещё находится в разработке. Проверьте вывод той версии build-tools, с которой выпускаете приложение. Большинству разработчиков эти команды никогда не понадобятся: Play сам генерирует гибридные ключи и управляет ими (см. следующий раздел).
v4 и инкрементальная установка
Флаг --v4-signing-enabled true записывает рядом с APK файл .idsig. В нём хранятся дерево Меркла по APK в формате fs-verity и подпись корня этого дерева, поэтому adb install --incremental может запустить приложение до того, как придут все байты. AOSP говорит прямо: «Подпись v4 требует дополнительной подписи v2 или v3». В эксперименте для APK размером 12,6 МБ файл подписи занял 103 041 байт, а apksigner verify --v4-signature-file app-v4.apk.idsig вывел Verified using v4 scheme (APK Signature Scheme v4): true. Схема v4 нужна для разработки и потоковой установки; в магазины приложений она не загружается.
Play App Signing и проверка разработчиков
В Google Play ключ, которым подписано то, что устанавливают пользователи, обычно принадлежит не вам. При использовании Play App Signing (подписания приложений в Google Play) вы подписываете набор App Bundle ключом загрузки («ключ RSA длиной не менее 2048 бит»), а Google переподписывает доставляемые APK ключом подписи приложения, который хранит у себя. Отсюда два следствия:
- Если ключ загрузки потерян или утёк, это поправимо: «доступ к приложению вы не теряете»; вы запрашиваете в Play Console сброс ключа загрузки и указываете новый сертификат.
- Ротация ключа подписи приложения («обновление ключа», key upgrade) выполняется в Play Console. Теперь Play подключает новые приложения «к гибридной подписи, готовой к квантовой эпохе, с ключами, которые генерирует Google»: это «классический 4096-битный ключ RSA и постквантовый ключ ML-DSA-65», а также классический ключ для старых устройств, «в результате чего ваше приложение использует три разных ключа». Play отмечает, что гибридная подпись пока несовместима с v4, и предлагает ежегодное обновление ключа для установок на Android 17 и новее.
За пределами Play ключ подписи ещё важнее. Проверка разработчиков в Android регистрирует разработчиков и названия их пакетов вместе с ключами подписи для приложений, которые устанавливаются на сертифицированные устройства: защитные меры начали действовать 30 сентября 2026 года в Бразилии, Индонезии, Сингапуре и Таиланде, а в 2027 году Google планирует распространить проверку на весь мир. В разделе частых вопросов сказано прямо: «Если вы потеряете ключ подписи, вы не сможете зарегистрировать свои пакеты».
Типичные ошибки
- Изменение APK после подписи. При использовании
apksigner«zipalignдолжен применяться до того, как файл APK будет подписан» (zipalign); то же относится к любому инструменту, который перезаписывает ZIP. Любое изменение после подписи приводит к тому несовпадению дайджеста, что показано выше. - Доверие к строке «Verifies». Любой ключ даёт верную подпись. Сравнивайте дайджест SHA-256 сертификата подписанта с ожидаемым (свой вы найдёте в Play Console, а F-Droid и многие проекты с открытым исходным кодом публикуют свои).
- Ротация через v3 на Android 12 и более ранних версиях без причины. Пусть
apksignerвыполняет её через v3.1. - Единственная копия ключа подписи. У приложений вне Play сброса нет: ключ и есть приложение.
- Ключи в репозитории. Храните ключ загрузки в аппаратном или облачном хранилище ключей и выдавайте CI право на подпись, а не файл ключа.
- Закрепление сертификата подписи приложения в своём бэкенде (списки разрешённых клиентов API, регистрации клиентов OAuth, ключи Firebase или Maps с ограничением по сертификату) без плана на случай обновления ключа: документация Play просит зарегистрировать новые отпечатки у поставщиков API, а при гибридной подписи новых ключей сразу два.
Источники
- AOSP: обзор подписи APK, v2, v3, v3.1, v3.2, v4
- Android Developers: apksigner, изменения в поведении Android 11, проверка разработчиков и её частые вопросы
- Google Play: Как использовать подписание приложений в Google Play
- Инструменты: Android SDK build-tools 37.0.0 (
apksigner), JDK 25.0.4.1 (keytool), APK клиента F-Droid