Firma y empaquetado
Antes conviene leer «Esquemas de firma v1, v2 y v3»
1. Qué es y por qué existe
Android no instala un APK sin firma. No es una recomendación: es el mecanismo por el que el
sistema decide si una actualización viene de quien publicó la versión anterior, y por el que
dos aplicaciones pueden compartir permisos o identidad de usuario. Un APK sin firmar, o con
la firma rota, se rechaza en el instalador con un error genérico que no dice cuál de las dos
cosas ha pasado.
Cualquier herramienta de los documentos anteriores produce APK sin firma. apktool no firma;
APKEditor no firma, por diseño; zipalign destruye la firma que hubiera. El ciclo no se
cierra hasta este documento.
Aquí está solo el uso: qué herramienta, con qué opciones y en qué orden. El porqué estructural
—qué cubre cada esquema, cómo está construido el APK Signing Block, por qué la alineación
existe— está en el bloque «20-firma», y en particular en
«Alineación y zipalign».
2. El orden del pipeline, que no es negociable
┌─────────────────┐
│ 1. construir │ apktool b · APKEditor build · APKEditor merge
└────────┬────────┘
▼
┌─────────────────┐
│ 2. alinear │ zipalign -P 16 -f 4
└────────┬────────┘
▼
┌─────────────────┐
│ 3. FIRMAR │ apksigner sign
└────────┬────────┘
▼
┌─────────────────┐
│ 4. verificar │ apksigner verify
└────────┬────────┘
▼
┌─────────────────┐
│ 5. instalar │ adb install · adb install-multiple
└─────────────────┘
zipalign va antes de apksigner. Siempre. La documentación oficial de zipalign lo dice
con todas las letras: con apksigner, alinear antes de firmar; cualquier cambio posterior a
la firma la invalida. Solo con el jarsigner antiguo —que no se recomienda— el orden es el
contrario.
Esto no es teoría. Se puede comprobar, y la «sección 8 · Qué se rompe si se altera el orden» lo comprueba con salida real.
3. apksigner
La herramienta oficial de firma y verificación de las build-tools. Está instalada, así que
todo lo de esta sección está ejecutado de verdad.
BT=~/Library/Android/sdk/build-tools/37.0.0
$BT/apksigner version
0.9
Cuatro comandos: sign, verify, rotate y lineage.
3.1 verify: leer una firma ajena
Es la orden más útil del documento, porque no requiere claves. Sobre el base.apk de Google
Keep del «muestrario», extraído de su .xapk:
BT=~/Library/Android/sdk/build-tools/37.0.0
$BT/apksigner verify --verbose --print-certs keep/com.google.android.keep.apk
Verifies
Verified using v1 scheme (JAR signing): false
Verified using v2 scheme (APK Signature Scheme v2): false
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): false
Verified using v4 scheme (APK Signature Scheme v4): false
Verified for SourceStamp: true
Number of signers: 1
V3.1 Signer: (minSdkVersion=33, maxSdkVersion=2147483647) certificate DN: CN=Android, OU=Android, O=Google Inc., L=Mountain View, ST=California, C=US
V3.1 Signer: (minSdkVersion=33, maxSdkVersion=2147483647) certificate SHA-256 digest: 7ce83c1b71f3d572fed04c8d40c5cb10ff75e6d87d9df6fbd53f0468c2905053
V3.1 Signer: (minSdkVersion=33, maxSdkVersion=2147483647) key algorithm: RSA
V3.1 Signer: (minSdkVersion=33, maxSdkVersion=2147483647) key size (bits): 4096
V3.0 Signer: (minSdkVersion=24, maxSdkVersion=32) certificate DN: CN=Android, OU=Android, O=Google Inc., L=Mountain View, ST=California, C=US
V3.0 Signer: (minSdkVersion=24, maxSdkVersion=32) certificate SHA-256 digest: f0fd6c5b410f25cb25c3b53346c8972fae30f8ee7411df910480ad6b2d60db83
V3.0 Signer: (minSdkVersion=24, maxSdkVersion=32) key algorithm: RSA
V3.0 Signer: (minSdkVersion=24, maxSdkVersion=32) key size (bits): 2048
Source Stamp Signer: certificate DN: CN=Android, OU=Android, O=Google Inc., L=Mountain View, ST=California, C=US
Source Stamp Signer: certificate SHA-256 digest: 3257d599a49d2c961a471ca9843f59d341a405884583fc087df4237b733bbd6d
Salida recortada; se han quitado los digests SHA-1 y MD5 y las huellas de clave pública.
Este volcado enseña la firma moderna entera de un vistazo:
- v3.1 con segmentación por SDK. Hay dos firmantes v3 distintos con rangos disjuntos: API 24-32 con una clave RSA de 2048 bits, y API 33 en adelante con una de 4096. Es rotación de clave desplegada en producción: los dispositivos antiguos siguen validando con la clave vieja y los nuevos con la nueva.
- v1 y v2 salen
false, pero no por lo mismo. v1 no está. El bloque v2 sí está, pero con elminSdkVersion32 de Keepapksignerno lo verifica, porque desde la API 28 manda v3: que un esquema salgafalseno significa que falte («Esquemas de firma → §9 · Por qué un APK moderno lleva v1+v2+v3 (y cuándo se puede omitir v1)»). Y aunque el firmante v3.0 declareminSdkVersion=24, v3 solo se verifica desde la API 28: para instalar en 24-27 hace falta v2. - Source stamp presente, con su propio certificado: acredita el origen con independencia de la firma de distribución.
3.2 sign: firmar de verdad
Pipeline completo ejecutado sobre org.fossify.calculator_1.4.0.apk del muestrario el 25 de
septiembre de 2026, con un keystore creado con la orden de la «sección 4.1 · Crear un keystore PKCS#12»:
BT=~/Library/Android/sdk/build-tools/37.0.0
$BT/zipalign -P 16 -f 4 demo-in.apk demo-aligned.apk
$BT/apksigner sign --ks demo.p12 --ks-type PKCS12 --ks-key-alias pruebas-demo \
--ks-pass pass:demopass --key-pass pass:demopass \
--min-sdk-version 24 --max-sdk-version 35 \
--v1-signing-enabled true --v2-signing-enabled true \
--v3-signing-enabled true --v4-signing-enabled true \
--out demo-signed.apk demo-aligned.apk
Sin salida y con código 0. Produce dos ficheros:
-rw-r--r-- 1 jj wheel 7176568 demo-signed.apk
-rw-r--r-- 1 jj wheel 62961 demo-signed.apk.idsig
Con -p sale exactamente el mismo APK. Las cuatro .so de la calculadora van sin comprimir
(extractNativeLibs=false), pero el APK de partida ya las traía alineadas a 16 KiB, y
zipalign las deja donde estaban: sus offsets de datos son idénticos en la entrada y en las
salidas con -p y con -P 16. Con un APK que las trajera a 4 KiB, solo -P 16 las movería en
demo-aligned.apk, pero el apksigner sign de la orden siguiente las dejaría a 16 KiB de todos
modos, porque realinea por defecto: --lib-page-alignment 16384 y --alignment-preserved false.
El .idsig es la firma v4, y viaja al lado del APK, no dentro. Si se pierde, se pierde
la firma v4 y con ella la instalación incremental.
Las opciones que importan:
| Opción | Para qué |
|---|---|
--ks <fichero> |
El keystore |
--ks-key-alias <alias> |
Qué clave del keystore usar. Obligatorio si hay más de una |
--ks-pass <formato> |
Contraseña del keystore: pass:, env:, file:, stdin |
--key-pass <formato> |
Contraseña de la clave privada, si difiere |
--ks-type <tipo> |
PKCS12, JKS… |
--key / --cert |
Alternativa al keystore: clave PKCS#8 DER y certificado X.509 sueltos |
--min-sdk-version <n> |
Determina qué esquemas hacen falta. Ver «sección 3.3 · La sutileza de verify que engaña a todo el mundo» |
--max-sdk-version <n> |
Límite superior del rango a cubrir |
--v1-signing-enabled <bool> |
Firma JAR |
--v2-signing-enabled <bool> |
Esquema v2 |
--v3-signing-enabled <bool> |
Esquema v3 |
--v4-signing-enabled <true|false|only> |
Esquema v4; genera el .idsig. only produce solo el .idsig. Sin la opción se activa si el rango de SDK llega a la API 30, así que por defecto casi siempre sale un .idsig |
--rotation-min-sdk-version <n> |
Desde qué API se usa la clave rotada |
--next-signer |
Separa las opciones de un segundo firmante |
--alignment-preserved <bool> |
false por defecto: realinea las entradas stored, a 4 bytes y las .so a la página de la opción siguiente. true conserva la alineación de entrada |
--lib-page-alignment <bytes> |
Alineación de las .so sin comprimir. 16.384 por defecto |
--v1-signer-name <base> |
Nombre base de los ficheros de firma v1 |
--out <fichero> |
Salida. Sin él, firma en el sitio |
-v, --verbose |
Detalle |
Nunca --ks-pass pass:... fuera de una demostración. La contraseña queda en el historial
del shell y en la tabla de procesos. En cualquier uso real, env: o file:.
3.3 La sutileza de verify que engaña a todo el mundo
El APK recién firmado, verificado:
$BT/apksigner verify --verbose --min-sdk-version 24 demo-signed.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
Se pidió v1 y dice false. Se pidió v4 y dice false. ¿Se ignoraron los flags? No. Los dos
false tienen causas distintas y ninguna es un fallo.
v1: está, pero no se usa en ese rango de API. Los ficheros están físicamente dentro:
unzip -l demo-signed.apk | grep -E "META-INF/(MANIFEST\.MF|[A-Z0-9_-]+\.(SF|RSA))"
156212 01-01-1981 01:01 META-INF/PRUEBAS-DE.SF
1304 01-01-1981 01:01 META-INF/PRUEBAS-DE.RSA
156085 01-01-1981 01:01 META-INF/MANIFEST.MF
Y verificando en un rango de API donde v1 sí es necesaria:
$BT/apksigner verify --verbose --min-sdk-version 21 --max-sdk-version 23 demo-signed.apk
Verifies
Verified using v1 scheme (JAR signing): true
Verified using v2 scheme (APK Signature Scheme v2): false
Verified using v3 scheme (APK Signature Scheme v3): false
...
La regla:
apksigner verifyno responde «¿está presente este esquema?» sino «¿se usó este esquema para verificar en el rango de API que me has dado?». Con--min-sdk-version 24, Android prefiere v2/v3 y v1 ni se mira. Con 21-23, solo existe v1 y es la que se usa.
Quien lea esa salida como un inventario de esquemas presentes va a informar mal. Para saber qué
hay dentro, se mira el ZIP y el APK Signing Block.
v4: hay que pasarle el .idsig explícitamente.
$BT/apksigner verify --verbose --min-sdk-version 24 \
--v4-signature-file demo-signed.apk.idsig demo-signed.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): true
Verified for SourceStamp: false
Number of signers: 1
Ahora sí. La firma v4 estaba correcta desde el principio; el verificador no la buscaba.
Opciones de verify:
| Opción | Efecto |
|---|---|
--print-certs |
Certificados: DN, digests SHA-256/SHA-1/MD5, algoritmo y tamaño de clave |
--print-certs-pem |
Además, el PEM por stdout |
--min-sdk-version / --max-sdk-version |
El rango a comprobar. Cambia el veredicto |
--v4-signature-file <fichero> |
El .idsig |
-v, --verbose |
Desglose por esquema |
-Werr |
Los avisos cuentan como error |
3.4 Por qué firmar solo en v1 hoy es no firmar
Firmando el mismo APK únicamente con v1:
$BT/apksigner sign --ks demo.p12 --ks-key-alias pruebas-demo \
--ks-pass pass:demopass --key-pass pass:demopass --min-sdk-version 21 \
--v1-signing-enabled true --v2-signing-enabled false \
--v3-signing-enabled false --v4-signing-enabled false \
--out demo-v1.apk demo-aligned.apk
$BT/apksigner verify --verbose demo-v1.apk
DOES NOT VERIFY
ERROR: Target SDK version 36 requires a minimum of signature scheme v2; the APK is not signed with this or a later signature scheme
WARNING: META-INF/com/android/build/gradle/app-metadata.properties not protected by signature. Unauthorized modifications to this JAR entry will not be detected. Delete or move the entry outside of META-INF/.
WARNING: META-INF/version-control-info.textproto not protected by signature. Unauthorized modifications to this JAR entry will not be detected. Delete or move the entry outside of META-INF/.
La aplicación declara targetSdkVersion 36, y con ese target Android exige v2 como mínimo.
La firma v1 es válida criptográficamente y aun así el APK no se instala.
Esta salida es la evidencia directa de un hallazgo de la evaluación de las trece herramientas:
cuatro de las trece firman solo en v1, y sus APK no se instalan en Android 11 o superior
con targetSdk moderno. La herramienta parece funcionar hasta el último paso.
Los WARNING son la otra lección de v1: la firma JAR no cubre las entradas de META-INF/, así
que cualquiera puede modificarlas sin invalidarla. Es la debilidad estructural que motivó v2.
3.5 rotate y lineage
Rotar la clave de firma sin perder la identidad de la aplicación. rotate construye el fichero
de lineage que acredita la sucesión; sin --in empieza un linaje nuevo, y con
--in lineage-anterior añade un eslabón a uno existente. Ejecutado el 25 de septiembre de 2026
con dos keystores creados con la orden de la «sección 4.1 · Crear un keystore PKCS#12»:
apksigner rotate --out lineage \
--old-signer --ks clave-vieja.p12 \
--new-signer --ks clave-nueva.p12
apksigner lineage --in lineage --print-certs
Signer #1 in lineage certificate DN: CN=Pruebas vieja, O=Ejemplo, C=ES
…
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=Pruebas nueva, O=Ejemplo, C=ES
…
La clave vieja conserva todas sus capacidades menos una, rollback, que viene desactivada.
Se activa con --set-rollback true detrás de --old-signer. Al firmar se aporta el linaje:
apksigner sign --ks clave-vieja.p12 \
--next-signer --ks clave-nueva.p12 \
--lineage lineage --rotation-min-sdk-version 28 --out rotada.apk app.apk
apksigner verify --print-certs rotada.apk solo enseña el firmante que vale desde la API 28, el
nuevo (V3.0 Signer: certificate DN: CN=Pruebas nueva…); el linaje que va dentro se lee con
apksigner lineage --in rotada.apk --print-certs, que devuelve los dos eslabones de arriba.
Lo que hace el instalador con eso, en un emulador Android 14 arm64-v8a, actualizando siempre el
mismo paquete:
| Instalado | Se instala encima | Resultado |
|---|---|---|
| Firmado con la vieja | Firmado solo con la nueva | INSTALL_FAILED_UPDATE_INCOMPATIBLE: Existing package … signatures do not match newer version; ignoring! |
| Firmado con la vieja | rotada.apk |
Success |
rotada.apk |
Firmado solo con la nueva | Success |
| El anterior | Firmado solo con la vieja | INSTALL_FAILED_UPDATE_INCOMPATIBLE, el mismo mensaje |
rotada.apk con --set-rollback true |
Firmado solo con la vieja | Success, y el paquete vuelve a la clave vieja |
Tras instalar rotada.apk, adb shell dumpsys package lo resume en una línea:
signatures=PackageSignatures{… signatures:[28c95b25], past signatures:[a66ae172 …, 28c95b25 …]}:
la clave actual y el linaje. Después del rollback, signatures:[a66ae172], past signatures:[]:
la clave vieja, y el linaje olvidado. Una vez instalada la rotación, volver atrás solo es
posible si se previó al rotar. Un linaje desplegado en producción, el de Google Keep, está en
la salida de la «sección 3.1 · verify: leer una firma ajena».
El mecanismo y el formato del linaje están en «Firma v4 y verificación».
4. keytool
Viene con el JDK. Está disponible (OpenJDK 21.0.10), así que esta sección también está ejecutada.
4.1 Crear un keystore PKCS#12
PKCS#12 es el formato actual; JKS es el heredado y Java avisa al usarlo.
keytool -genkeypair -v -keystore demo.p12 -storetype PKCS12 -alias pruebas-demo \
-keyalg RSA -keysize 2048 -validity 10000 \
-dname "CN=Ejemplo Demo, OU=Docs, O=Ejemplo, L=Madrid, C=ES" \
-storepass demopass -keypass demopass
Generando par de claves RSA de 2.048 bits para certificado autofirmado (SHA384withRSA) con una validez de 10.000 días
para: CN=Ejemplo Demo, OU=Docs, O=Ejemplo, L=Madrid, C=ES
[Almacenando demo.p12]
La salida sale en español porque el JDK respeta el locale del sistema. Conviene saberlo antes
de escribir un script que analice esta salida: no lo hagas.
-validity 10000 son unos 27 años. Google Play exige que el certificado siga siendo válido
bastante más allá del final de vida previsto de la aplicación; el valor de 10.000 días es la
convención heredada de la documentación de Android.
4.2 Listar el keystore
keytool -list -v -keystore demo.p12 -storepass demopass
Tipo de Almacén de Claves: PKCS12
Proveedor de Almacén de Claves: SUN
Su almacén de claves contiene 1 entrada
Nombre de Alias: pruebas-demo
Fecha de Creación: 12 ago 2026
Tipo de Entrada: PrivateKeyEntry
Longitud de la Cadena de Certificado: 1
Certificado[1]:
Propietario: CN=Ejemplo Demo, OU=Docs, O=Ejemplo, L=Madrid, C=ES
Emisor: CN=Ejemplo Demo, OU=Docs, O=Ejemplo, L=Madrid, C=ES
Número de serie: e8251338a7c3fb45
Válido desde: Wed Aug 12 12:31:43 CEST 2026 hasta: Sun Dec 28 11:31:43 CET 2053
Huellas digitales del certificado:
SHA1: 8F:B6:F0:35:AA:FE:12:EE:E3:32:49:EC:6C:B2:BA:A4:2F:71:E1:5C
SHA256: FE:9D:A8:50:02:E2:8D:F0:C5:40:16:4B:22:64:88:70:E0:92:99:58:63:F3:1C:50:BB:FE:4C:8B:AA:D0:EF:CC
Nombre del algoritmo de firma: SHA384withRSA
Algoritmo de clave pública de asunto: Clave RSA de 2048 bits
Versión: 3
Propietario y Emisor coinciden: es autofirmado, que es lo normal para firmar aplicaciones
Android. El SHA-256 del certificado es la identidad de la aplicación a efectos de
actualizaciones, y es el valor que hay que registrar en las consolas de servicios de terceros.
4.3 Exportar el certificado
keytool -exportcert -keystore demo.p12 -alias pruebas-demo \
-storepass demopass -rfc -file pruebas-demo.pem
Sin -rfc sale en DER binario. El PEM es lo que piden las consolas de Firebase, Google Maps y
similares.
4.4 El debug keystore
Android Studio crea un keystore de depuración con valores conocidos y públicos:
| Elemento | Valor |
|---|---|
| Ruta (macOS y Linux) | ~/.android/debug.keystore |
| Ruta (Windows) | %USERPROFILE%\.android\debug.keystore |
| Contraseña del keystore | android |
| Alias | androiddebugkey |
| Contraseña de la clave | android |
keytool -list -v -alias androiddebugkey -keystore ~/.android/debug.keystore
La documentación de Google Play services confirma la ruta, la contraseña android y el alias
androiddebugkey; la de firma de aplicaciones solo dice que el IDE «sets the keystore and key
passwords», sin dar los valores. Los fija el código de AGP: DefaultSigningConfig.kt
declara DEFAULT_PASSWORD = "android" y DEFAULT_ALIAS = "AndroidDebugKey", su
DebugSigningConfig devuelve esa misma contraseña para el almacén y para la clave, y
createDefaultDebugStore() crea el keystore con esos valores (platform/tools/base,
etiqueta studio-2026.1.2). keytool lista el alias en minúsculas, androiddebugkey.
Por qué no vale para distribuir. La documentación oficial de firma de aplicaciones lo dice
directamente: el certificado de depuración lo generan las herramientas de compilación y es
inseguro por diseño, y por eso la mayoría de las tiendas, incluida Google Play, no aceptan
aplicaciones firmadas con él. La razón de fondo es que la contraseña es pública: la clave
se genera en cada máquina la primera vez que se compila, así que no es la misma en dos
ordenadores, pero cualquiera que consiga ese debug.keystore —de un repositorio, de una imagen
de CI— puede firmar una actualización de la aplicación, que es exactamente lo que la firma
debía impedir.
Sirve para desarrollo, para pruebas locales y para reinstalar sobre uno mismo. Nada más.
5. zipalign
Instalado, así que ejecutado.
Alinear al escribir, con las .so a 16 KiB, que es lo que exige Play (tabla de abajo):
BT=~/Library/Android/sdk/build-tools/37.0.0
$BT/zipalign -P 16 -f 4 entrada.apk salida.apk
Comprobar sin modificar:
MUESTRAS=~/muestras-apk
$BT/zipalign -c -v 4 $MUESTRAS/com.termux_1002.apk | head -8
Verifying alignment of ~/muestras-apk/com.termux_1002.apk (4)...
87 META-INF/com/android/build/gradle/app-metadata.properties (OK - compressed)
179 classes.dex (OK - compressed)
1378689 lib/arm64-v8a/libtermux-bootstrap.so (OK - compressed)
30080690 lib/arm64-v8a/libtermux.so (OK - compressed)
30084061 lib/armeabi-v7a/libtermux-bootstrap.so (OK - compressed)
55930307 lib/armeabi-v7a/libtermux.so (OK - compressed)
55933390 lib/x86/libtermux-bootstrap.so (OK - compressed)
(OK - compressed) significa que la entrada va comprimida y por tanto la alineación no le
aplica: solo importa para las entradas stored, que son las que el sistema mapea en memoria.
Este APK comprime sus .so, lo que implica que declara extractNativeLibs="true".
Comprobación de páginas de 16 KB, que es el requisito nuevo:
$BT/zipalign -c -P 16 4 $MUESTRAS/org.videolan.vlc_13070108.apk; echo "exit=$?"
exit=0
Ese exit=0 solo dice que las .so sin comprimir empiezan en un múltiplo de 16 KiB dentro del
ZIP. zipalign no lee el ELF: que los segmentos PT_LOAD estén alineados a 16 KiB, la
otra mitad del requisito, se comprueba aparte («sección 3.1 de
Alineación y zipalign · Qué es»).
Opciones:
| Opción | Efecto |
|---|---|
-c |
Solo comprueba, no modifica |
-f |
Sobrescribe la salida si existe |
-p |
Alinea los .so sin comprimir a páginas de 4 KiB. Obsoleto: usar -P 16 |
-P <kb> |
Alinea los .so a páginas de ese tamaño. Valores válidos: 4, 16, 64 |
-v |
Detalle entrada a entrada |
-z |
Recomprime con Zopfli |
| (alineación) | Argumento obligatorio. Siempre 4 |
Google Play exige páginas de 16 KB a las aplicaciones con targetSdk 35 o superior —a las nuevas
y a las actualizaciones desde el 1 de noviembre de 2025, según el blog de Android Developers; la
guía da ahora el 1 de febrero de 2027 para las actualizaciones—, así que -P 16 deja de ser
opcional. Aunque se olvide, apksigner sign realinea las .so a 16 KiB por defecto. El porqué
está en
«Alineación y zipalign» y en
«Código nativo y ELF».
6. uber-apk-signer
| Dato | Valor |
|---|---|
| Repositorio | https://github.com/patrickfav/uber-apk-signer |
| Licencia | Apache-2.0 |
| Última versión | v1.3.0, publicada el 16 de febrero de 2023 |
| Último push | 30 de octubre de 2023 |
| Tracción | 2.703 ★ · 264 forks · 11 issues abiertas |
| Archivado | No |
Consultado el 12 de agosto de 2026 vía la API de GitHub. Estado real: sin mantenimiento activo. Tres años y medio sin release, casi tres sin commits. No está archivado, pero no se mueve.
Qué automatiza: encadena zipalign + apksigner + verificación en una sola invocación, sobre
uno o muchos APK, con el debug keystore por defecto si no se le da otro.
java -jar uber-apk-signer.jar --apks /ruta/con/apks/
java -jar uber-apk-signer.jar -a app.apk --ks release.jks \
--ksAlias mialias --ksPass ... --ksKeyPass ...
⚠️ No reproducido. La sintaxis procede de la descripción oficial del proyecto, consultada el 12 de agosto de 2026; no se ha pegado aquí ninguna salida real.
Cuándo compensa: cuando hay que firmar muchos APK de golpe, o cuando se quiere una sola
orden en un script en lugar de tres. Es lo que usan APKLab y Apk Studio internamente, y por eso
la salida de APKLab reporta «sign success / zipalign verified / signature verified [v1, v2, v3]»
en un solo paso.
Cuándo no: en cualquier flujo nuevo. Es una capa de conveniencia sin mantenimiento sobre dos herramientas oficiales que sí lo tienen y que resuelven el problema en dos líneas. Y como congela su propia noción de qué esquemas hay que aplicar, envejece igual que envejecieron las interfaces gráficas del apartado anterior.
7. Instalar
ADB=~/Library/Android/sdk/platform-tools/adb
# Un APK único
$ADB install -r demo-signed.apk
# Un conjunto de splits, sin fusionarlos
$ADB install-multiple base.apk config.arm64_v8a.apk config.es.apk config.xxhdpi.apk
# Desde un APK Set, dejando que bundletool elija
java -jar bundletool.jar install-apks --apks=fixture.apks
Las tres se ejecutaron el 25 de septiembre de 2026 contra un emulador Android 14 arm64-v8a:
install -r demo-signed.apkrespondeSuccess. Comoapksignerdeja el.idsigjunto alAPK(«sección 3.2 · sign: firmar de verdad»),adblo encuentra y hace una instalación incremental: la salida empieza porServing...yPerforming Incremental Install. Con--no-incrementalse vePerforming Streamed Install. Instalar encima la calculadora original, firmada por Fossify, daINSTALL_FAILED_UPDATE_INCOMPATIBLE: Existing package org.fossify.math signatures do not match newer version; ignoring!.install-multiplerespondeSuccesscon un base y suconfig.esgenerados conaapt2 link --split. Los fallos de ese camino, con sus mensajes reales, están en la «sección 2.3 de Diagnóstico de fallos · Los cinco que hay que conocer al detalle».install-apks, sinadben elPATH, falla igual queget-device-spec:
[BT:1.18.3] Error: Unable to determine the location of ADB. Please set the --adb flag or define ANDROID_HOME or PATH environment variable.
Con --adb ~/Library/Android/sdk/platform-tools/adb responde
The APKs have been extracted in the directory: … y la aplicación queda instalada. En un
emulador con el idioma en inglés solo instala base: el split es del APK Set no se entrega.
Tres notas que ahorran tiempo:
install-multiplees la vía para splits sin fusionarlos. Todos losAPKdel conjunto deben compartirpackageNameyversionCodey estar firmados con la misma clave; si no, la instalación falla entera.install -rreinstala conservando los datos, y solo funciona si la firma coincide con la de la versión instalada. UnAPKrefirmado con otra clave exige desinstalar primero.bundletool install-apkshaceget-device-spec,extract-apkseinstall-multipleen un paso.
El catálogo de errores de instalación —INSTALL_PARSE_FAILED_NO_CERTIFICATES,
INSTALL_FAILED_UPDATE_INCOMPATIBLE, INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES— está en
«Diagnóstico de fallos».
8. Qué se rompe si se altera el orden
La comprobación, ejecutada. El APK de partida es
org.fossify.calculator_1.4.0.apk del muestrario, que viene firmado en v2:
$BT/apksigner verify --verbose demo-in.apk | head -4
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): false
Se le pasa zipalign después de estar firmado, que es el error:
$BT/zipalign -P 16 -f 4 demo-in.apk demo-aligned.apk
$BT/apksigner verify --verbose demo-aligned.apk
DOES NOT VERIFY
ERROR: Missing META-INF/MANIFEST.MF
La firma ha desaparecido. zipalign reescribe el ZIP entrada por entrada para colocarlas en
sus offsets, y en ese proceso no conserva el APK Signing Block, que es donde vivía la
firma v2. Como este APK tampoco tenía v1, no queda nada — y de ahí que el error mencione
META-INF/MANIFEST.MF, que es lo primero que el verificador busca cuando no encuentra bloque
de firma.
| Orden | Resultado |
|---|---|
| construir → alinear → firmar | Correcto |
| construir → firmar → alinear | Firma destruida. Hay que volver a firmar |
| construir → firmar → editar un fichero | Firma inválida |
alinear → firmar → zipalign -c |
Correcto: -c solo lee |
El corolario práctico: zipalign -c es seguro siempre; zipalign sin -c sobre un APK
firmado obliga a refirmar.
Y el corolario de diseño, que es más profundo: cualquier herramienta que reescriba el ZIP
—apktool, APKEditor, cualquier otra— invalida la firma por construcción. No es un fallo que corregir,
es la propiedad que hace útil a la firma.
Fuentes
- apksigner — documentación oficial de Android — https://developer.android.com/tools/apksigner
Consultado el 12 de agosto de 2026. De aquí salen las tablas de opciones de
signyverifyde las secciones «3.2» y «3.3», y la sintaxis derotateylineagede la «sección 3.5 · rotate y lineage». - zipalign — documentación oficial de Android — https://developer.android.com/tools/zipalign
Consultado el 12 de agosto de 2026. De aquí salen la tabla de opciones de la «sección 5 · zipalign» y la
regla de orden de la «sección 2 · El orden del pipeline, que no es negociable»: con
apksigner, alinear antes de firmar; conjarsigner, después. - bundletool — documentación oficial de Android — https://developer.android.com/tools/bundletool
Consultado el 12 de agosto de 2026. De aquí sale la sintaxis de
install-apksde la «sección 7 · Instalar». - Sign your app — documentación oficial de Android Studio —
https://developer.android.com/studio/publish/app-signing
Consultado el 12 de agosto de 2026. De aquí salen la ruta
$HOME/.android/debug.keystorey la afirmación literal de que el certificado de depuración es inseguro por diseño y que las tiendas, incluida Google Play, no aceptan aplicaciones firmadas con él. - Client authentication — Google Play services —
https://developers.google.com/android/guides/client-auth
Consultado el 12 de agosto de 2026. De aquí salen la contraseña
androiddel debug keystore, el aliasandroiddebugkey, las rutas en macOS/Linux y Windows, y la orden dekeytoolpara listarlo. - uber-apk-signer — repositorio oficial —
https://github.com/patrickfav/uber-apk-signer
Consultado el 12 de agosto de 2026 vía la API de GitHub y
/releases/latest. De aquí salen la licencia Apache-2.0, la versión v1.3.0 del 16 de febrero de 2023, el último push del 30 de octubre de 2023 y los recuentos de la «sección 6 · uber-apk-signer». - Ejecuciones locales sobre el muestrario — realizadas el 12 de agosto de 2026 con
build-tools37.0.0 (apksigner0.9,zipalign,aapt2), OpenJDK 21.0.10 (keytool) ybundletool1.18.3, sobre macOS Darwin 25.5.0. De aquí salen todas las salidas pegadas de las «secciones 3.1 · verify: leer una firma ajena» a 3.4, 4.1, 4.2, 5, 7 y 8, ejecutadas contraGoogleKeep_com.google.android.keep.xapk,org.fossify.calculator_1.4.0.apk,com.termux_1002.apkyorg.videolan.vlc_13070108.apkdel muestrario. - AOSP —
build/tools/zipalign/ZipAlign.cppen la etiquetaandroid-16.0.0_r1— https://android.googlesource.com/platform/build/+/refs/tags/android-16.0.0_r1/tools/zipalign/ZipAlign.cpp Consultado el 25 de septiembre de 2026. De aquí sale quezipalignsolo mira el offset de cada entrada delZIP(getAlignmentdevuelve el tamaño de página para las.so) y no lee su contenido ELF, de la «sección 5 · zipalign». - AOSP —
tools/base,DefaultSigningConfig.ktyGradleKeystoreHelper.kt, etiquetastudio-2026.1.2— https://android.googlesource.com/platform/tools/base/+/refs/tags/studio-2026.1.2/build-system/builder/src/main/java/com/android/builder/signing/DefaultSigningConfig.kt Consultado el 25 de septiembre de 2026. De aquí salen la contraseña y el alias de la clave de depuración de la «sección 4.4 · El debug keystore». - Emulador Android 14
arm64-v8a— ejecutado el 25 de septiembre de 2026. De aquí salen la rotación conrotateylineagede la «sección 3.5 · rotate y lineage» y las tres órdenes de la «sección 7 · Instalar».