wandres.dev
GOOGLE PLAY · publicar y actualizar

Firma de apps: tu clave, la de Play y el fin del desastre irreversible

La firma como identidad permanente de una app: qué garantiza realmente y qué no, los esquemas v1 a v4 y el ataque que cierra cada uno, el modelo de dos claves de la firma gestionada por Play con clave de subida y clave de la app, el linaje de rotación introducido en v3, y el protocolo real de recuperación cuando pierdes el keystore.

⏱ 18 min

La firma de una app Android no certifica quién eres: certifica que la versión de hoy viene del mismo sitio que la de ayer. El sistema no consulta autoridades de certificación, no valida cadenas de confianza y no le molesta que tu certificado sea autofirmado y caduque en el año 2074. Solo compara: si el certificado que firma la actualización no coincide con el que firmó la instalación, la actualización se rechaza y no hay apelación. Esa igualdad exacta convirtió durante una década al keystore en el activo más frágil de cualquier equipo Android, un archivo cuya pérdida obligaba a publicar una app nueva, con otro identificador, y abandonar a todos los usuarios instalados. La firma gestionada por la tienda y la rotación de claves desmontaron esa fragilidad. Entender cómo lo hicieron es entender qué firma exactamente cada una de las dos claves que ahora tienes.

🎯 Al terminar esta lección sabrás
  • Distinguir integridad, identidad y continuidad en lo que aporta una firma.
  • Comprender los esquemas v1 a v4 y qué ataque concreto cierra cada uno.
  • Separar la clave de subida de la clave de la app en la firma gestionada por Play.
  • Conocer el linaje de rotación y el protocolo real de recuperación ante una pérdida.

Qué garantiza una firma y qué no

Una firma aporta tres cosas distintas que conviene no mezclar. Aporta integridad: si alguien altera un byte del paquete después de firmarlo, la verificación falla y el instalador lo rechaza. Aporta identidad relativa: dos paquetes firmados con la misma clave provienen del mismo poseedor de esa clave. Y de la combinación de ambas nace la propiedad que de verdad gobierna el ecosistema, la continuidad: el sistema solo acepta actualizar una app si la nueva versión trae el mismo certificado que la instalada.

Lo que no aporta es identidad absoluta. Un certificado autofirmado no dice nada sobre quién lo generó, y por eso la confianza real del ecosistema no vive en la criptografía sino en la tienda, que asocia una cuenta de desarrollador verificada con un identificador de aplicación y con una clave concreta. La firma es el mecanismo; la atribución es un servicio.

De la continuidad se derivan consecuencias que sorprenden. El certificado puede estar caducado y la app sigue actualizándose, porque el sistema compara certificados sin comprobar su validez temporal en las actualizaciones. Dos apps firmadas con la misma clave pueden declarar un identificador de usuario compartido en configuraciones antiguas, hoy desaconsejado. Y ciertas comprobaciones de permisos a nivel de firma solo se conceden entre apps firmadas por la misma clave, que es el mecanismo con el que los fabricantes reservan capacidades a su propio conjunto de aplicaciones.

# Generar un keystore propio, con validez larga y clave fuerte
keytool -genkeypair -v \
  -keystore subida.jks -alias subida \
  -keyalg RSA -keysize 4096 -validity 10000

# Ver la huella del certificado que hay dentro
keytool -list -v -keystore subida.jks -alias subida

# Comprobar con qué certificado esta firmado un paquete ya construido
apksigner verify --print-certs --verbose app-release.apk

Las huellas que devuelven esos comandos son el objeto que de verdad se compara. La huella SHA-256 del certificado es lo que registran los servicios externos que dependen de tu identidad: los enlaces de aplicación verificados, la autenticación de terceros, las claves de API restringidas por aplicación. Si esa huella cambia sin que actualices esos registros, la app sigue instalándose y todo lo que dependía de la firma deja de funcionar en silencio, que es la clase de fallo más difícil de diagnosticar.

Los cuatro esquemas y el ataque que cierra cada uno

La firma de Android no es un mecanismo, son cuatro superpuestos, y cada uno nació de un fallo del anterior. Conviene conocerlos porque explican por qué un paquete puede llevar varias firmas a la vez y por qué ciertas herramientas de posprocesado rompen la verificación.

v1   Firma estilo JAR, entrada a entrada dentro de META-INF
     No cubre los metadatos del ZIP ni el orden de las entradas
     Vulnerable al ataque que inserta un DEX en la cabecera del archivo

v2   Firma del archivo completo en un bloque propio antes del directorio central
     Cubre todos los bytes, no solo las entradas comprimidas
     Rompe si cualquier herramienta reescribe el ZIP tras firmar

v3   Anade el linaje de rotacion: la clave nueva demuestra que la antigua la autorizo
     Permite cambiar de clave sin perder continuidad de actualizacion

v4   Firma incremental con arbol de Merkle en un fichero lateral idsig
     Habilita instalar y empezar a ejecutar antes de terminar la descarga

El salto interesante es el de v2. Al firmar el archivo completo y no entrada a entrada, cerró de golpe una familia de ataques que aprovechaban que los metadatos del contenedor quedaban fuera de la firma. El coste es que cualquier manipulación posterior del paquete invalida la firma, y por eso el orden correcto es siempre alinear y comprimir primero, firmar después: la herramienta de firma moderna hace ambas cosas en el orden adecuado, mientras que las cadenas artesanales que firman y luego alinean producen paquetes que el instalador rechaza sin explicar bien por qué.

El v4 pertenece a otra categoría. No añade seguridad frente a un atacante nuevo, añade una propiedad operativa: al construir un árbol de resúmenes sobre bloques del archivo, permite verificar trozos sueltos sin haber recibido el archivo entero. Eso habilita la instalación incremental, en la que el sistema empieza a ejecutar la app mientras sigue descargando lo que falta, y es lo que hace posible probar un juego de varios gigabytes en segundos durante el desarrollo. Su firma vive en un fichero lateral, no dentro del paquete, y por eso conviven sin estorbarse.

Existe también una vía de escape que conviene conocer aunque casi nunca convenga usarla: la firma de depuración. Es un certificado generado automáticamente, compartido por todos los proyectos del mismo equipo de desarrollo, con contraseña conocida y publicada. Sirve para instalar en tu propio dispositivo y nada más. Su peligro real es que muchos servicios externos aceptan registrar esa huella para facilitar las pruebas, y un registro de depuración olvidado en producción es una puerta abierta a cualquiera que sepa que esa clave es pública.

💡
Las firmas conviven, y esa convivencia es intencional

Un paquete moderno suele llevar v2 y v3 a la vez, y a menudo también v1 si aún declara compatibilidad con versiones muy antiguas del sistema. Cada versión de Android verifica el esquema más alto que entiende e ignora el resto. Eso significa que un paquete puede estar perfectamente firmado para dispositivos recientes y ser rechazado por uno antiguo si te olvidaste de habilitar el esquema que ese dispositivo necesita. La configuración de firma de Gradle expone interruptores independientes por esquema justamente por eso, y el valor por defecto depende del minSdk que declares.

Dos claves: la de subida y la de la app

Cuando el formato de publicación pasó a ser un artefacto no instalable, la tienda tuvo que empezar a fabricar y firmar paquetes en el servidor. Eso obligó a que la clave real de la app viviera allí, y de ahí nace el modelo de dos claves que hoy es obligatorio para toda app nueva.

flowchart TD
DEV[Firmas el artefacto con tu clave de subida] --> VER[Play verifica la clave de subida]
VER --> STRIP[Play descarta esa firma de subida]
STRIP --> GEN[Genera los paquetes divididos por dispositivo]
GEN --> SIGN[Firma cada paquete con la clave de la app]
SIGN --> DEV2[El dispositivo compara con el certificado instalado]
VER --> REJ[Si la clave de subida no coincide rechaza la subida]

La clave de subida solo sirve para demostrarle a la tienda que quien sube eres tú. Nunca llega a ningún dispositivo. La clave de la app es la que produce la firma que verán los usuarios, la que determina la continuidad de las actualizaciones y la que aparece en las huellas que registras en servicios externos. La custodia Google en su infraestructura de claves, y tú puedes consultar sus huellas pero no extraerla.

// build.gradle.kts del modulo de aplicacion
android {
    signingConfigs {
        create("subida") {
            storeFile = file(providers.gradleProperty("RUTA_KEYSTORE").get())
            storePassword = providers.gradleProperty("PASS_KEYSTORE").get()
            keyAlias = providers.gradleProperty("ALIAS_CLAVE").get()
            keyPassword = providers.gradleProperty("PASS_CLAVE").get()

            enableV1Signing = false
            enableV2Signing = true
            enableV3Signing = true
        }
    }
    buildTypes {
        release {
            signingConfig = signingConfigs.getByName("subida")
            isMinifyEnabled = true
        }
    }
}

Fíjate en que las credenciales no están escritas en el fichero de compilación sino leídas de propiedades externas. Ese detalle no es cosmético: un keystore versionado junto a su contraseña en el repositorio es equivalente a no tener firma, porque cualquiera con acceso al código puede producir artefactos que la tienda aceptará como tuyos.

Rotación, pérdida y el protocolo de recuperación

El esquema v3 introdujo el linaje: una cadena en la que cada clave nueva viene acompañada de una prueba, firmada por la clave anterior, de que esta autorizó el relevo. El dispositivo, que ya conoce el certificado antiguo, puede verificar el eslabón y aceptar la actualización aunque la firma final sea de otra clave. Rotar deja de ser imposible y pasa a ser una operación planificada.

La rotación sirve para dos escenarios: sospechas de que la clave se ha comprometido, o quieres migrar a un algoritmo más fuerte. No sirve para el caso que asusta de verdad, que es haber perdido la clave, porque sin ella no puedes firmar la prueba de relevo. Ahí es donde el modelo de dos claves cambia la naturaleza del desastre.

🔐

Pierdes la clave de subida

Recuperable. Generas una nueva, registras su huella con la tienda y esta la asocia a tu cuenta. La clave real de la app no se toca y los usuarios no notan nada.

🏛️

Pierdes acceso a la cuenta

Es el riesgo que queda. La clave está a salvo pero tú no puedes usarla. Cuentas de organización, varios administradores y verificación en dos pasos son la mitigación real.

💀

Sin firma gestionada

Perder la clave significa perder la app. Nuevo identificador, ficha desde cero, cero valoraciones heredadas y ninguna ruta de actualización para los instalados.

🔁

Clave comprometida

Rotas con el linaje de v3 si aún tienes la clave, o solicitas el cambio a través de la tienda si la firma es gestionada. La continuidad se preserva en ambos casos.

Conviene además desmontar una creencia extendida: activar la firma gestionada no significa que Google haya visto nunca tu clave original. Al migrar una app antigua, la clave existente se exporta cifrada con una herramienta que la envuelve contra un certificado público de la infraestructura de claves, de modo que solo esa infraestructura puede abrirla, y ni siquiera transita en claro. Para apps nuevas ni siquiera existe esa migración, porque la clave de la app se genera directamente en el servidor y nunca ha estado en ningún portátil.

Queda un matiz importante que mucha gente ignora hasta que le muerde: la rotación no es retroactiva para todo. Los permisos concedidos a nivel de firma, las relaciones de identidad con servicios externos y los enlaces de aplicación verificados siguen anclados a huellas concretas, así que al rotar hay que registrar la huella nueva además de la antigua en cada uno de esos sitios y dejar ambas activas mientras convivan versiones instaladas.

La firma dejó de ser un secreto que custodias y pasó a ser una identidad que administras

Durante quince años, la clave de firma fue el punto único de fallo más absurdo de la plataforma: un archivo en el portátil de alguien, cuya pérdida borraba años de descargas, valoraciones y posicionamiento sin ninguna posibilidad de reparación. Todo el sistema descansaba sobre una comparación byte a byte que era simultáneamente su mayor virtud —imposible de suplantar sin la clave— y su mayor fragilidad —imposible de recuperar sin la clave—. Lo que hicieron la firma gestionada y el linaje de v3 no fue debilitar esa comparación, sino añadir un nivel de indirección entre quien publica y quien firma. Ese es exactamente el mismo movimiento que resuelve casi todos los problemas duros de sistemas: cuando un identificador es a la vez el mecanismo de autenticación y la garantía de continuidad, cualquier necesidad de cambiarlo se vuelve catastrófica; separas ambos papeles y lo catastrófico se convierte en un trámite. La clave de subida autentica, la clave de la app garantiza continuidad, y como ya no son la misma cosa puedes reemplazar una sin tocar la otra. El linaje añade la segunda mitad de la solución: hace que la propia continuidad sea transferible, convirtiendo una igualdad estática en una cadena verificable. La consecuencia práctica es que el riesgo se ha desplazado, no eliminado: ya no pierdes una app por perder un fichero, la pierdes por perder el control de una cuenta. Y eso significa que la disciplina de seguridad que hace diez años consistía en copiar un keystore a tres sitios hoy consiste en gobernar quién tiene acceso administrativo, con qué segundo factor y bajo qué proceso de baja. Quien sigue pensando que su activo crítico es un archivo está protegiendo la puerta equivocada.

⚔️ Audita tu propia cadena de firma
  1. Extrae la huella SHA-256 del certificado de tu app instalada y compárala con la que muestra la consola de la tienda para la clave de la app.
  2. Comprueba con apksigner qué esquemas de firma lleva un paquete tuyo de producción y justifica por qué están activos esos y no otros.
  3. Localiza cada servicio externo en el que hayas registrado una huella y anota cuál usaste: la de subida, la de la app o la de depuración.
  4. Escribe el procedimiento exacto que ejecutarías mañana si perdieras el keystore de subida, y estima cuánto tardarías en volver a publicar.
  5. Enumera quién tiene hoy acceso administrativo a la cuenta de la tienda y qué pasaría si esa persona desapareciera.