Pular para o conteúdo
ecdsa.com
← Todos os artigos

Esquemas de assinatura APK v1–v4, rotação de chaves e Play

Verificamos um APK real com o apksigner do build-tools 37.0.0, assinamos cópias de novo com nossas próprias chaves, rotacionamos a chave, adicionamos um signatário pós-quântico ML-DSA-65 e quebramos uma assinatura de propósito. Todas as saídas abaixo são reais.

Resposta curta. O Android verifica um entre vários formatos de assinatura, conforme o dispositivo: v1 (assinatura JAR, por arquivo, para o Android 6 e anteriores), v2 (assinatura do arquivo inteiro no bloco de assinatura do APK, o APK Signing Block, Android 7.0), v3 (v2 mais uma linhagem de prova de rotação, Android 9), v3.1 (rotação voltada apenas para o Android 13 e posteriores), v3.2 (uma assinatura híbrida, clássica mais ML-DSA, Android 17) e v4 (um arquivo .idsig separado para instalações incrementais, Android 11). Apps destinados ao nível de API 30 ou superior precisam ter v2 ou um esquema mais recente. As atualizações precisam ser assinadas pela mesma chave ou por uma sucessora na linhagem de rotação do app, então a chave de assinatura é a identidade do app por toda a vida dele: deixe o Google Play guardá-la (Play App Signing), fique apenas com a chave de upload e verifique as versões que você publica com apksigner verify -v --print-certs.

No laboratório, usamos o apksigner do Android SDK build-tools 37.0.0 (ele se identifica como versão 0.9) no Java 25.0.4.1, em eclipse-temurin:25.0.4.1_1-jdk-noble. A amostra é o APK do cliente F-Droid, baixado de f-droid.org em 22 de outubro de 2026; todas as outras chaves foram geradas para este artigo.

Os esquemas

EsquemaDesdeOndeO que acrescenta
v1Android 1.0META-INF/*.SF, *.RSA/*.ECAssinatura JAR: um hash por entrada do ZIP; os metadados do ZIP não são cobertos
v2Android 7.0 (API 24)Bloco de assinatura do APK, ID 0x7109871aAssinatura sobre o arquivo inteiro, exceto o próprio bloco
v3Android 9 (API 28)ID 0xf05368c0v2 mais a faixa de SDK compatível e uma linhagem de prova de rotação
v3.1Android 13 (API 33)um novo ID de blocoRotação aplicada apenas no Android 13 e posteriores
v3.2Android 17 (API 37)ID 0x70e1c89fExatamente dois signatários, um clássico e um pós-quântico, ambos verificados
v4Android 11 (API 30)app.apk.idsig ao lado do APKÁrvore de Merkle sobre o APK para adb install --incremental; exige v2 ou v3

O esquema de assinatura de APK v2 “trata o arquivo APK como um blob e verifica a assinatura sobre o arquivo inteiro”, de modo que “qualquer modificação no APK, incluindo modificações nos metadados do ZIP, invalida a assinatura do APK” (AOSP). Ele também é mais rápido: o dispositivo não precisa mais descompactar cada entrada para verificá-la. O v1 permanece apenas para dispositivos anteriores ao Android 7.0; um APK cujo minSdkVersion seja 24 ou superior não precisa dele, e o apksigner o omite. Uma assinatura v2 removida não pode ser escondida mantendo-se a assinatura v1: os APKs assinados com v2 que também têm assinatura v1 trazem nos arquivos .SF um atributo X-Android-APK-Signed, coberto pela assinatura v1, e os verificadores devem rejeitar um APK que não tenha a assinatura do esquema que ele anuncia.

Desde o Android 11, “apps destinados ao Android 11 (nível de API 30) que atualmente só são assinados com o esquema de assinatura de APK v1 agora também precisam ser assinados com o esquema de assinatura de APK v2 ou superior” (mudanças de comportamento).

Como ler o apksigner verify

O apksigner verify -v --print-certs verifica cada esquema que o APK traz, para a faixa de SDK compatível com ele, e imprime os signatários. No cliente F-Droid publicado (saída filtrada para as linhas de esquema e de signatário):

apksigner verify -v --print-certs F-Droid.apk
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 para o v1 não é um erro: o SDK mínimo do app é 24, então o v1 é desnecessário. O digest do certificado é o valor que identifica o signatário do app: o F-Droid publica exatamente 43238d51…a9c9ccab para o próprio cliente na página de chaves de assinatura, e o Android compara o signatário a cada atualização. Não há cadeia de confiança: os certificados de APK costumam ser autoassinados, e as regras de verificação do v2 conferem se a SubjectPublicKeyInfo do primeiro certificado corresponde à chave de assinatura, sem nenhuma etapa para emissores ou datas de validade (v2). Este certificado foi emitido em 2010.

Assinar novamente uma cópia com nossa própria chave P-256 e depois alterar um único byte do arquivo mostra as duas metades dessa verificação:

lab.sh (trecho)
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
Saída (filtrada)
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>

A cópia assinada novamente passa na verificação sem problemas, com outro certificado. Por isso é o digest do signatário, e não o “Verifies”, que diz se um APK vem do publicador certo. Se essa cópia fosse instalada sobre o F-Droid original, o Android a recusaria como uma atualização assinada por outra chave. O digest CHUNKED_SHA256 é calculado sobre partes consecutivas de 1 MB das seções do arquivo, e é isso que permite ao v2 e aos esquemas posteriores verificar de forma rápida e em paralelo.

Rotação da chave: v3 e v3.1

Um app não pode trocar a chave de assinatura simplesmente assinando de novo: a atualização seria rejeitada. O v3 acrescenta uma prova de rotação, “uma lista simplesmente encadeada em que cada nó contém um certificado de assinatura usado para assinar versões anteriores do app”, na qual cada certificado assina o seguinte (v3). Um dispositivo que conhece a chave antiga aceita um APK assinado pela nova se a linhagem ligar as duas.

Rotacionar key1 -> key2 e assinar com a linhagem
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
Saída (filtrada; digests SHA-1 e MD5 omitidos)
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

Por padrão, o apksigner coloca a chave nova em um bloco v3.1 para o Android 13 e posteriores e mantém a chave antiga no bloco v3.0 para o Android 7 a 12. Isso é proposital: o AOSP diz que “a rotação de chaves de APK não é recomendada para o Android 12 (nível de API 31) e anteriores”, porque só a partir do Android 13 o checkSignatures reconhece a linhagem e “retorna a assinatura mais recente”. A opção --rotation-min-sdk-version 28 força a rotação no Android 9 e posteriores; a documentação do apksigner reserva isso para apps que já fizeram a rotação em plataformas mais antigas (apksigner).

As capacidades dizem o que a chave antiga ainda pode fazer: instalar atualizações sobre dados assinados por ela, compartilhar um UID, deter permissões de assinatura e (aqui false) permitir reverter para ela. Revogue-as com apksigner lineage se a chave antiga tiver sido comprometida.

Pós-quântico: v3.2 híbrido

O Android 17 acrescenta o v3.2, “um esquema de assinatura híbrido projetado para apoiar a transição do ecossistema Android para a criptografia pós-quântica”: um APK é assinado “com um algoritmo clássico, como RSA ou ECDSA, e um algoritmo PQC”, inicialmente ML-DSA-65 ou ML-DSA-87, em um bloco com ID 0x70e1c89f que “deve conter exatamente dois signatários” (v3.2). O Android 17 verifica os dois; versões mais antigas ignoram o bloco e usam o v3.0 ou o v3.1, que continuam obrigatórios. A plataforma trata a chave PQC como uma rotação da clássica, e os dois signatários precisam ter “históricos de assinatura anteriores idênticos”.

O keytool do Java 25 cria chaves ML-DSA (-keyalg ML-DSA-65), e o build-tools 37.0.0 assina com elas. Foram necessárias algumas tentativas: o apksigner falhava com um NullPointerException até que os dois signatários híbridos tivessem uma linhagem, e a linhagem PQC precisou ser uma rotação a partir da mesma chave anterior usada na clássica:

Assinatura híbrida: key2 (clássica) e uma chave ML-DSA-65, ambas rotacionadas a partir de key1
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
Saída (filtrada)
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

Um mesmo APK agora carrega três gerações: key1 para o Android 7 a 12, key2 para o Android 13 e posteriores, e key2 mais a chave ML-DSA-65 para a plataforma compatível com v3.2. O APK híbrido ficou 20.480 bytes maior que o assinado por um único signatário. Mesmo com --hybrid-min-sdk-version 37, este apksigner imprimiu a faixa do v3.2 como “36 (dev release=true)”, a codificação que ele usa para uma plataforma ainda em desenvolvimento; confira a saída da versão do build-tools com que você publica. A maioria dos desenvolvedores nunca vai rodar esses comandos: o Play gera e gerencia as chaves híbridas (próxima seção).

v4 e instalações incrementais

A opção --v4-signing-enabled true grava um arquivo .idsig ao lado do APK. Ele contém uma árvore de Merkle do APK no formato fs-verity e uma assinatura sobre a raiz dessa árvore, para que o adb install --incremental possa iniciar o app antes de todos os bytes chegarem. O AOSP é explícito: “Uma assinatura v4 exige uma assinatura v2 ou v3 complementar”. No laboratório, o arquivo de assinatura tinha 103.041 bytes para o APK de 12,6 MB, e apksigner verify --v4-signature-file app-v4.apk.idsig informou Verified using v4 scheme (APK Signature Scheme v4): true. O v4 é um recurso de desenvolvimento e de streaming; ele não é enviado às lojas.

Play App Signing e verificação de desenvolvedores

No Google Play, a chave que assina o que os usuários instalam normalmente não é a sua. Com o Play App Signing (Assinatura de apps do Google Play), você assina o app bundle com uma chave de upload (“uma chave RSA de 2048 bits ou mais”), e o Google assina novamente os APKs que distribui com a chave de assinatura do app, que fica com ele. Duas consequências:

  • Uma chave de upload perdida ou vazada pode ser recuperada: “você não perde o acesso ao seu app”; basta solicitar no Play Console a redefinição da chave de upload, com um novo certificado.
  • A rotação da chave de assinatura do app (o “upgrade de chave”) é uma operação do Play Console. Agora o Play inscreve os apps novos “na assinatura híbrida, preparada para a era quântica, com chaves geradas pelo Google”: “uma chave clássica RSA de 4096 bits e uma chave pós-quântica ML-DSA-65”, mais uma chave clássica para dispositivos mais antigos, “fazendo com que seu app use três chaves distintas”. O Play avisa que, por enquanto, a assinatura híbrida é incompatível com o v4. Ele também oferece um upgrade anual de chave para instalações no Android 17 e posteriores.

Fora do Play, a chave de assinatura importa ainda mais. A verificação de desenvolvedores do Android registra os desenvolvedores e os nomes de pacote deles junto com as chaves de assinatura, para apps instalados em dispositivos certificados: as proteções começaram em 30 de setembro de 2026 no Brasil, na Indonésia, em Singapura e na Tailândia, e o Google planeja expandir a verificação para o mundo todo em 2027. As perguntas frequentes do programa são diretas: “Se você perder sua chave de assinatura, não vai conseguir registrar seus pacotes”.

Erros comuns

  • Alterar o APK depois de assiná-lo. Com o apksigner, “o zipalign precisa ser usado antes de o arquivo APK ser assinado” (zipalign); o mesmo vale para qualquer ferramenta que reescreva o ZIP. Qualquer alteração feita depois da assinatura produz a divergência de digest mostrada acima.
  • Confiar no “Verifies”. Qualquer chave produz uma assinatura válida. Compare o digest SHA-256 do certificado do signatário com o que você espera (o Play Console mostra o seu; o F-Droid e muitos projetos de código aberto publicam os deles).
  • Fazer a rotação com v3 no Android 12 e anteriores sem um motivo. Deixe o apksigner usar o v3.1 como alvo.
  • Uma única cópia da chave de assinatura. Para apps fora do Play, não há redefinição: a chave é o app.
  • Chaves no repositório. Guarde a chave de upload em um armazenamento de chaves protegido por hardware ou na nuvem e dê ao pipeline de CI uma permissão de assinatura, em vez do arquivo da chave.
  • Fixar o certificado de assinatura do app no seu backend (listas de permissões de API, registros de clientes OAuth, chaves do Firebase ou do Maps restritas por certificado) sem se planejar para um upgrade de chave: a documentação do Play pede que você registre as novas impressões digitais junto aos provedores de API — com a assinatura híbrida, são duas chaves novas.

Fontes