Saltar al contenido
ecdsa.com
← Todos los artículos

Esquemas de firma APK v1 a v4, rotación de claves y Play

Verificamos un APK real con apksigner de build-tools 37.0.0, volvimos a firmar copias con nuestras propias claves, rotamos la clave, añadimos un firmante poscuántico ML-DSA-65 y rompimos una firma a propósito. Todas las salidas que siguen son reales.

Respuesta breve. Según el dispositivo, Android comprueba uno de varios formatos de firma: v1 (firma JAR, archivo por archivo, para Android 6 y anteriores), v2 (firma del archivo completo en el bloque de firma de APK, Android 7.0), v3 (v2 más un linaje de prueba de rotación, Android 9), v3.1 (rotación dirigida solo a Android 13 y posteriores), v3.2 (una firma híbrida, clásica más ML-DSA, Android 17) y v4 (un archivo .idsig aparte para las instalaciones incrementales, Android 11). Las aplicaciones orientadas al nivel de API 30 o superior deben llevar v2 o un esquema más reciente. Las actualizaciones deben firmarse con la misma clave o con una sucesora en el linaje de rotación de la aplicación, así que la clave de firma es la identidad de la aplicación durante toda su vida: deja que Google Play la guarde (Play App Signing), quédate solo con la clave de subida y comprueba cada versión con apksigner verify -v --print-certs.

En las pruebas usamos apksigner de las build-tools 37.0.0 del Android SDK (se identifica como versión 0.9) con Java 25.0.4.1 en eclipse-temurin:25.0.4.1_1-jdk-noble. La muestra es el APK del cliente de F-Droid tal como se descargó de f-droid.org el 22 de octubre de 2026; todas las demás claves se generaron para este artículo.

Los esquemas

EsquemaDesdeDóndeQué añade
v1Android 1.0META-INF/*.SF, *.RSA/*.ECFirma JAR: un hash por cada entrada del ZIP; los metadatos del ZIP no quedan cubiertos
v2Android 7.0 (API 24)Bloque de firma de APK, ID 0x7109871aFirma sobre todo el archivo, salvo el propio bloque
v3Android 9 (API 28)ID 0xf05368c0v2 más el rango de SDK admitido y un linaje de prueba de rotación
v3.1Android 13 (API 33)un ID de bloque nuevoRotación aplicada solo en Android 13 y posteriores
v3.2Android 17 (API 37)ID 0x70e1c89fExactamente dos firmantes, uno clásico y otro poscuántico, ambos verificados
v4Android 11 (API 30)app.apk.idsig junto al APKÁrbol de Merkle sobre el APK para adb install --incremental; requiere v2 o v3

El esquema de firma de APK v2 «trata el archivo APK como un blob y comprueba la firma en todo el archivo», de modo que «cualquier modificación del APK, incluidas las de los metadatos del ZIP, invalida la firma del APK» (AOSP). También es más rápido: el dispositivo ya no descomprime cada entrada para comprobarla. v1 se mantiene solo para los dispositivos anteriores a Android 7.0; un APK cuyo minSdkVersion es 24 o superior no lo necesita, y apksigner lo omite. Conservar v1 no permite ocultar que se eliminó la firma v2: los APK firmados con v2 que también llevan firma v1 incluyen en sus archivos .SF un atributo X-Android-APK-Signed, que la firma v1 cubre, y los verificadores deben rechazar un APK al que le falte la firma del esquema que anuncia.

Desde Android 11, «las aplicaciones orientadas a Android 11 (nivel de API 30) que actualmente solo están firmadas con el esquema de firma de APK v1 ahora también deben firmarse con el esquema de firma de APK v2 o superior» (cambios de comportamiento).

Cómo leer la salida de apksigner verify

apksigner verify -v --print-certs comprueba cada esquema que lleva el APK para el rango de SDK que este admite y muestra los firmantes. Para el cliente de F-Droid publicado (salida filtrada a las líneas de esquemas y firmantes):

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

El false de v1 no es un error: el SDK mínimo de la aplicación es 24, así que v1 no hace falta. El resumen del certificado es el valor que identifica al firmante de la aplicación: F-Droid publica exactamente 43238d51…a9c9ccab para su cliente en su página de claves de firma, y Android compara el firmante en cada actualización. No hay cadena de confianza: los certificados de APK suelen ser autofirmados, y las reglas de verificación de v2 comprueban que el SubjectPublicKeyInfo del primer certificado coincide con la clave de firma, sin ningún paso para emisores ni fechas de validez (v2). Este certificado se emitió en 2010.

Volver a firmar una copia con nuestra propia clave P-256 y después cambiar un solo byte del archivo muestra las dos mitades de esa comprobación:

lab.sh (extracto)
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
Salida (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>

La copia firmada de nuevo se verifica sin ningún problema, con un certificado distinto. Por eso es el resumen del firmante, y no «Verifies», lo que te indica que un APK procede del editor correcto. Si se instalara sobre el F-Droid original, Android la rechazaría por ser una actualización firmada con otra clave. El resumen CHUNKED_SHA256 se calcula sobre fragmentos consecutivos de 1 MB de las secciones del archivo, y eso es lo que permite a v2 y a los esquemas posteriores verificar de forma rápida y en paralelo.

Rotación de la clave: v3 y v3.1

No se puede cambiar la clave de firma de una aplicación con solo volver a firmarla: la actualización se rechazaría. v3 añade una prueba de rotación, «una lista enlazada simple en la que cada nodo contiene un certificado de firma usado para firmar versiones anteriores de la aplicación», donde cada certificado firma el siguiente (v3). Un dispositivo que conoce la clave antigua acepta un APK firmado con la nueva si el linaje las conecta.

Rotar key1 -> key2 y firmar con el linaje
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
Salida (filtrada; se omiten los resúmenes SHA-1 y MD5)
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

De forma predeterminada, apksigner coloca la clave nueva en un bloque v3.1 para Android 13 y posteriores, y mantiene la clave antigua en el bloque v3.0 para Android 7 a 12. Es intencionado: AOSP dice que «la rotación de claves de APK no se recomienda para Android 12 (nivel de API 31) y anteriores», porque solo a partir de Android 13 checkSignatures reconoce el linaje y «devuelve la firma más reciente». --rotation-min-sdk-version 28 fuerza la rotación en Android 9 y posteriores; la documentación de apksigner lo reserva para las aplicaciones que ya rotaron la clave en plataformas anteriores (apksigner).

Las capacidades indican lo que la clave antigua todavía puede hacer: instalar actualizaciones sobre datos firmados con ella, compartir un UID, tener permisos de firma y (aquí false) permitir volver a ella con una reversión. Revócalas con apksigner lineage si la clave antigua se vio comprometida.

Firma poscuántica: v3.2 híbrido

Android 17 añade v3.2, «un esquema de firma híbrido diseñado para facilitar la transición del ecosistema Android a la criptografía poscuántica»: un APK se firma «tanto con un algoritmo clásico, como RSA o ECDSA, como con un algoritmo PQC», inicialmente ML-DSA-65 o ML-DSA-87, en un bloque con ID 0x70e1c89f que «debe contener exactamente dos firmantes» (v3.2). Android 17 verifica ambos; las versiones anteriores omiten el bloque y usan v3.0 o v3.1, que deben seguir presentes. La plataforma trata la clave PQC como una rotación de la clásica, y los dos firmantes deben tener «historiales de firma previos idénticos».

El keytool de Java 25 crea claves ML-DSA (-keyalg ML-DSA-65), y build-tools 37.0.0 firma con ellas. Costó varios intentos: apksigner fallaba con una NullPointerException hasta que los dos firmantes híbridos tuvieron un linaje, y el linaje PQC tenía que ser una rotación desde la misma clave anterior que el del firmante clásico:

Firma híbrida: key2 (clásica) y una clave ML-DSA-65, ambas rotadas desde 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
Salida (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

Un mismo APK lleva ahora tres generaciones: key1 para Android 7 a 12, key2 para Android 13 y posteriores, y key2 más la clave ML-DSA-65 para la plataforma compatible con v3.2. El APK híbrido ocupa 20.480 bytes más que el de firma simple. Incluso con --hybrid-min-sdk-version 37, este apksigner mostró el rango de v3.2 como «36 (dev release=true)», la codificación que usa para una plataforma aún en desarrollo; comprueba la salida de la versión de build-tools con la que publicas. La mayoría de los desarrolladores nunca ejecutará estos comandos: Play genera y gestiona las claves híbridas (siguiente sección).

v4 e instalaciones incrementales

--v4-signing-enabled true escribe un archivo .idsig junto al APK. Contiene un árbol de Merkle sobre el APK en formato fs-verity y una firma sobre su raíz, de modo que adb install --incremental puede iniciar la aplicación antes de que hayan llegado todos los bytes. AOSP lo deja claro: «Una firma v4 requiere una firma v2 o v3 complementaria». En las pruebas, el archivo de firma ocupó 103.041 bytes para el APK de 12,6 MB, y apksigner verify --v4-signature-file app-v4.apk.idsig informó Verified using v4 scheme (APK Signature Scheme v4): true. v4 es una función para desarrollo y streaming; no se sube a las tiendas.

Play App Signing y verificación de desarrolladores

En Google Play, la clave que firma lo que instalan los usuarios no suele ser la tuya. Con Play App Signing, tú firmas el app bundle con una clave de subida («una clave RSA de 2048 bits o más») y Google vuelve a firmar los APK que entrega con la clave de firma de apps que guarda. Esto tiene dos consecuencias:

  • Una clave de subida perdida o filtrada tiene solución: «no pierdes el acceso a tu aplicación»; solicitas en Play Console el restablecimiento de la clave de subida con un certificado nuevo.
  • La rotación de la clave de firma de apps («actualización de clave», key upgrade) es una operación de Play Console. Play inscribe ahora las aplicaciones nuevas «en la firma híbrida preparada para la computación cuántica, con claves generadas por Google»: «una clave clásica RSA de 4096 bits y una clave poscuántica ML-DSA-65», más una clave clásica para los dispositivos antiguos, «con lo que tu aplicación usa tres claves distintas». Play advierte de que la firma híbrida es, por ahora, incompatible con v4, y ofrece una actualización anual de la clave para las instalaciones en Android 17 y posteriores.

Fuera de Play, la clave de firma importa todavía más. La verificación de desarrolladores de Android registra a los desarrolladores y sus nombres de paquete, junto con sus claves de firma, para las aplicaciones instaladas en dispositivos certificados: las protecciones empezaron el 30 de septiembre de 2026 en Brasil, Indonesia, Singapur y Tailandia, y Google prevé extender la verificación a todo el mundo en 2027. Sus preguntas frecuentes son tajantes: «Si pierdes tu clave de firma, no podrás registrar tus paquetes».

Errores frecuentes

  • Modificar el APK después de firmarlo. Con apksigner, «zipalign debe usarse antes de firmar el archivo APK» (zipalign); lo mismo vale para cualquier herramienta que reescriba el ZIP. Cualquier cambio posterior a la firma produce la discrepancia de resúmenes que vimos arriba.
  • Confiar en «Verifies». Cualquier clave produce una firma válida. Compara el resumen SHA-256 del certificado del firmante con el que esperas (Play Console muestra el tuyo; F-Droid y muchos proyectos de código abierto publican el suyo).
  • Rotar con v3 en Android 12 y anteriores sin motivo. Deja que apksigner use v3.1.
  • Una sola copia de la clave de firma. Para las aplicaciones fuera de Play no hay restablecimiento posible: la clave es la aplicación.
  • Claves en el repositorio. Guarda la clave de subida en un almacén de claves respaldado por hardware o en la nube, y da a la CI un permiso de firma en lugar del archivo de la clave.
  • Fijación del certificado de firma de apps en tu backend (listas de permitidos de la API, registros de clientes OAuth, claves de Firebase o de Maps restringidas por certificado) sin prever una actualización de clave: la documentación de Play te pide registrar las nuevas huellas digitales con tus proveedores de API; con la firma híbrida, son dos claves nuevas.

Fuentes