Construir aplicaciones es una pasión que comparten los desarrolladores de software de todo el mundo. Pero la sobrecarga administrativa de la gestión de la firma de código es lúgubre. Cómo podemos hacerlo bien a la primera? Lewis Cianci lo analiza.
Déjame decir esto por adelantado.
La firma de código es tan aburrida que me hace doler los dientes. Es un concepto que existe con una buena razón. Es decir, quieres que la gente esté segura de que tu paquete de software es realmente tuyo, ¿verdad? Y, sin embargo, es algo que muchos desarrolladores se esfuerzan por hacer bien a diario. Es como hacer tus impuestos después de todo un año de trabajo y tener tantos formularios que rellenar. Yippee.
Desplázate hacia abajo si sólo quieres ver la guía paso a paso sobre la firma de código en Android y no te interesa saber por qué lo hacemos😉
Por qué firmamos el código
Firmamos nuestros paquetes para que la gente que se descargue nuestro paquete desde la Play Store sepa realmente que somos nosotros. Lo hacemos firmando nuestro paquete con una clave que generamos. Cuando subimos nuestro paquete firmado a Google Play, éste recuerda la clave que se utilizó para subir el paquete inicial y se asegura de que los paquetes posteriores se firmen con la misma clave.
Para lograr este objetivo, la firma de paquetes de Android en realidad se aprovecha de una herramienta que viene del Marco de Desarrollo de Java llamada keytool. Keytool ha existido probablemente tanto tiempo como el propio JDK, así que es bastante antigua. Esto se presta a probablemente algunas de las razones por las que la firma de un APK o AAB (paquete de aplicaciones de Android) es tan confuso como lo es.
¿Por qué no puede el Play Store simplemente manejar la firma de código para nosotros?
Estaríamos tentados a pedir un nirvana en el que podríamos dar todos nuestros paquetes de aplicaciones sin firmar a la Play Store y sólo tienen que trabajar y sólo firmar por nosotros. Sin embargo, la lógica de esto se rompe rápidamente. Si escribieras un libro, ¿harías que te lo firmara otra persona? No. Lo firmarías tú porque eres el autor.
Hoy en día firmar el código es mucho más fácil de lo que solía ser. Siempre que firmemos nuestros paquetes con la misma clave (la «clave de subida»), Google Play generará y gestionará de hecho nuestras claves de firma de código por nosotros.
Si eres especialmente emprendedor, puedes intentar leer y entender todo lo que hay aquí, pero yo llevo desarrollando para Android la mayor parte de tres años y me apena decir que ni siquiera yo lo entiendo del todo. Todo lo que sé es que cuando se rompe, es un gran dolor para arreglar.
Tomemos el tiempo para entender no sólo cómo codificar signo sino también por qué codificamos signo. Cuando entendamos la necesidad de este proceso, será más fácil completarlo.
¿Qué necesitamos para firmar código?
La versión corta está aquí. Para la firma de código necesitamos:
- crear el archivo del kit de desarrollo de Java (JDK);
- firmar el paquete de nuestra app o APK con nuestra clave privada;
- modificar el build.gradle;
- enviar el paquete al distribuidor (Google Play).
Al final de este artículo también encontrarás cómo hacer funcionar la firma de código con Codemagic.
Ahora una versión un poco más larga con una guía paso a paso sobre lo que necesitamos para la firma de código en Android y cómo hacerlo.
Guía paso a paso para la firma de código en Android
PASO 1: El kit de desarrollo de Java (JDK)
Si estás desarrollando para Android, probablemente ya los tengas instalados.
Necesitamos crear un archivo Java Key Store (JKS) que contenga nuestra información de firma. Al generar un JKS para nuestra aplicación, en realidad estamos creando una clave privada en nuestro ordenador. Esta clave privada está protegida por una contraseña que establecemos.
Desde un símbolo del sistema, podemos escribir lo siguiente para obtener un JKS.
keytool -genkey -v -keystore %DESKTOP%/key.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias DEVELOPERNAME
Le estamos diciendo a keytool
que genere un Java Key Store y lo ponga en nuestro escritorio. Esta clave será válida durante 10.000 días o aproximadamente 27 años, lo que nos permitirá impulsar las actualizaciones durante toda la vida de nuestra aplicación. También se nos pide que establezcamos un alias. Yo simplemente lo hago con mi nombre de desarrollador o algo que pueda recordar.
keytool
nos pedirá varios datos a lo largo del proceso. Es importante especificar esto correctamente, ya que estamos definiendo los detalles de nuestra clave privada.
Se le pedirá:
- Contraseña del almacén de claves – lo necesitará para desbloquear este almacén de claves de nuevo en el futuro. Si se pierde esta contraseña, es prácticamente imposible recuperarla.
- Reintroducir la contraseña del almacén de claves
- Detalles personales sobre qué poner en el certificado personal
Se nos pedirá que rellenemos algunos detalles sobre nosotros. Son los datos que se asocian a nuestra clave privada, por lo que deberían ser algo relevantes. Depende de ti lo que pongas en estos campos, pero como regla general, yo no lo haría demasiado loco.
Esta es la salida de keytool
.
C:\code\signingtest\android\app>keytool -genkey -v -keystore key.jks -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 -alias androidappsEnter keystore password:Re-enter new password:What is your first and last name?: Codemagic Article DudeWhat is the name of your organizational unit?: Fantastic Apps And Where To Find ThemWhat is the name of your organization?: GreatappsWhat is the name of your City or Locality?: EstoniaWhat is the name of your State or Province?: TartuWhat is the two-letter country code for this unit?: EEIs CN=Codemagic Article Dude, OU=Fantastic Apps And Where To Find Them, O=Greatapps, L=Estonia, ST=Tartu, C=ES correct?:
¡Pon atención! Si sólo spam Enter a través de este proceso, la creación sólo bucle una y otra vez como usted está respondiendo ‘no’ a la última pregunta.
Al hacer esto, hemos creado un JKS y hemos puesto nuestra propia clave privada generada en él. Como la hemos generado y hemos puesto la contraseña, podemos estar seguros de que cualquiera que tenga este archivo JKS somos nosotros o está específicamente autorizado a usarlo.
Si alguien tiene tu JKS y las credenciales correctas, puede firmar paquetes como tú o tu empresa. Manténgalo a salvo, no lo ponga en el control de fuentes.
Ahora tenemos nuestro Java Key Store, ¡así que estamos a mitad de camino! Alégrate en consecuencia.
Paso 2: Firmar nuestro paquete de aplicaciones o APK con nuestra clave privada
Ahora, queremos firmar nuestro paquete de aplicaciones con ese JKS que acabamos de hacer. Es posible firmar manualmente nuestro APK o release build cada vez, pero en realidad, sería mejor configurarlo para que cuando ejecutemos flutter build apk --release
simplemente firme automáticamente nuestro paquete con la clave de subida correcta. La documentación de Flutter habla de cómo actualizar los archivos de Gradle aquí, pero lo haremos poco a poco y lo explicaremos por el camino.
Para empezar, vamos a abrir nuestro archivo flutter_app/android/app/build.gradle
. Aproximadamente en la línea 49 podemos ver esto:
buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so flutter run --release works. signingConfig signingConfigs.debug }}
Lo principal que está sucediendo aquí es que nuestras construcciones están siendo firmadas con el almacén de claves debug
, por lo que nuestra construcción de lanzamiento todavía funciona. Queremos cambiar esto para que nuestros lanzamientos sean firmados con nuestro propio keystore. De esta manera se pueden subir a la tienda de Google Play.
Lo primero que hacemos es crear un key.properties
en nuestro directorio de aplicaciones. Creamos esto en flutter_app/android/key.properties
.
key.properties
Incluirá todos los detalles que necesitamos para firmar con éxito nuestro paquete.
storePassword=The JKS store passwordkeyPassword=The key passwordkeyAlias=The alias for your keystoreFile=Where to look for your keystore file
Una nota rápida sobre el control de código fuente
Deberías pensar antes de comprobar este código en el control de código fuente. Si los malos actores obtuvieran acceso al almacén de claves y a estas credenciales, y tuvieran control sobre tus cuentas, podrían potencialmente enviar una nueva actualización a tu aplicación con malware u otras cosas malas. La mayoría de las soluciones CI/CD te permiten proporcionar estos detalles como «secretos», pero la implementación difiere según la plataforma.
PASO 3: Recapitulación &Modificar el build.gradle
Hemos creado un archivo keystore, y especificado un alias, así como una contraseña para protegerlo. Si estamos usando la firma de aplicaciones de Google Play (que se usa por defecto), entonces la clave que hemos generado actúa como nuestra clave de subida. El primer paquete que subamos a través de la consola de Google Play estará firmado con esta clave. Esto demuestra a Google que somos quienes decimos ser.
¿Tiene sentido? Genial, hagamos que se firme como parte de nuestro proceso de construcción de Flutter.
Modifica el build.gradle
Abre flutter_app/android/app/build.gradle
. Más o menos en la línea 31 deberías ver un texto como este:
android { compileSdkVersion 29` lintOptions { disable 'InvalidPackage' }...
Queremos decirle a Gradle dónde encontrar el almacén de claves. Lo hacemos poniendo estos datos más o menos en la línea 28, encima de la sentencia android {
.
def keystoreProperties = new Properties()def keystorePropertiesFile = rootProject.file('key.properties')if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile))}
Desglosemos lo anterior…
Definimos una variable keystoreProperties
. A continuación, comprobamos si key.properties
existe en relación con la raíz de nuestro proyecto androide (no el proyecto Flutter).
Cuando nuestra construcción se ejecuta, se carga key.properties
. key.properties
identifica la ubicación del keystore, además de las credenciales necesarias para desbloquear el Java Key Store para firmar el paquete. Con todos los detalles necesarios en la mano, Gradle ahora firma el paquete de aplicaciones o APK como parte de nuestra construcción de lanzamiento.
Volvamos a comprobar que todos nuestros archivos están en el lugar correcto.
Nuestro build.gradle
modificado está en flutter_app/android/app/build.gradle
.
Nuestro archivo key.jks
está en flutter_app/android/app/key.jks
.
Nuestro archivokey.properties
está enflutter_app/android/key.properties
.
Una vez que estemos seguros de lo anterior, deberíamos poder ejecutar flutter build apk --release
ahora y la firma debería funcionar bien.
PASO 4: Enviarlo a Google Play Store
Ahora podemos subir nuestro APK o paquete de aplicaciones a la Play Store. Cuando lo hagamos con nuestro paquete firmado, y con Google Play Signing activado (que lo está por defecto), Google reconocerá la clave que hemos utilizado para firmar el paquete y la recordará como nuestra clave de subida. A continuación, Google firmará nuestro APK o paquete de aplicaciones con su propia clave. Es importante que cualquier actualización posterior que proporcionemos para esta app, la firmemos con esta misma clave. Google Play reconoce esta clave como nuestra clave de subida y no podemos lanzar actualizaciones sin ella.
No entiendo nada de lo anterior y agradecería una ilustración increíblemente visual de lo que está ocurriendo exactamente.
¡Puedo hacerlo! Esto es lo que está pasando.
- Generamos una forma súper secreta de identificarnos, casi como si nos hiciéramos un pasaporte.
- Debido a que cualquier persona con este «pasaporte» podrá identificarse positivamente como nosotros (es decir: hacerse pasar por nosotros sin mucha resistencia), lo encerramos tras una contraseña en nuestra caja fuerte (el JKS, o Java Key Store).
- Creamos el paquete de aplicaciones o APK, y luego firmamos el paquete con la misma firma que usamos en el pasaporte. Para acceder a este pasaporte, tenemos que desbloquear la caja fuerte en la que está el pasaporte (proporcionando la contraseña y el alias al proceso de construcción de Gradle).
- Enviamos el paquete al distribuidor (Google Play). El distribuidor, al ver el paquete por primera vez, toma nota de nuestra firma que usamos en este paquete y se lleva una copia de la misma.
- Cuando enviamos paquetes a nuestro distribuidor (Google Play) en el futuro, firmamos estos paquetes con los mismos datos que usamos inicialmente. Nuestro distribuidor, recordando los datos que usamos inicialmente para subir el paquete, acepta o rechaza el paquete. Si coincide (si la clave de subida es la misma que usamos inicialmente), entonces el paquete es aceptado y distribuido. De lo contrario, no es aceptado.
- Nuestro distribuidor, sabiendo que el paquete inicial y los posibles paquetes futuros son definitivamente de nosotros, distribuye el paquete.
Hacer que la firma de código funcione con Codemagic
En última instancia, queremos firmar esto como parte de nuestro flujo de trabajo CI/CD, pero al mismo tiempo, no queremos comprobar en nuestro almacén de claves y archivo de propiedades al control de origen. En su lugar, queremos que nuestro proveedor de CI/CD construya el paquete y luego lo firme más tarde en el proceso de construcción con un almacén de claves que proporcionamos.
Configurándolo con Git
Si tenemos una aplicación Flutter totalmente nueva, entonces podemos cambiar a la carpeta y escribir git init
para comenzar a usar el control de origen con la aplicación.
Por defecto, vamos a comprobar felizmente en nuestro almacén de claves y archivo de propiedades de almacén de claves que es una mala idea desde una perspectiva de seguridad.
Deberías conseguir esto desde el principio
Si accidentalmente compruebas en tus propiedades de almacén de claves y archivo de almacén de claves y empujar esos cambios, la gente será capaz de arrancar esos archivos en cualquier momento en el futuro mirando a través de su historial de Git. Puedes eliminar manualmente los archivos de Git en el futuro, o puedes reiniciar tu repositorio sin esos archivos, pero es mejor no comprobarlos en primer lugar.
Queremos añadir estas líneas al final de nuestro archivo .gitignore
:
# Don't check in the keystore files or equivalent*.jkskey*.properties
No se comprobará en el control de código ningún KeyStore de Java (JKS) ni propiedades para la firma de código. Qué bonito.
Hacer que build.gradle no firme cuando se ejecuta en CI/CD
Mientras se construye el proyecto, el keystore y la configuración no están disponibles. Queremos que la compilación siga produciendo una compilación de lanzamiento aunque no esté firmada.
Esta es la parte de mi build.gradle
que permite esto:
signingConfigs { file(rootProject.file('key.properties')).with { propFile -> if (propFile.canRead()) { release { keyAlias keystoreProperties keyPassword keystoreProperties storeFile file(keystoreProperties) storePassword keystoreProperties } } else { print('not signed') } }}buildTypes { release { file(rootProject.file('key.properties')).with { propFile -> if (propFile.canRead()) { // because we can read the keystore // we are building locally // so sign locally // otherwise build an unsigned apk for later signing by the CI/CD provider signingConfig signingConfigs.release } } applicationVariants.all { variant -> variant.outputs.all { output -> output.outputFileName = "app-release.apk" } } // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. // signingConfig signingConfigs.release }}
Configuración de Codemagic para firmar nuestras compilaciones
En tu proceso de compilación, encuentra la sección de firma de código de Android (está en la sección Publish). Se ve así:
Ahora, subimos nuestro keystore y establecemos nuestra contraseña, alias de la clave y contraseña de la clave (que son los mismos que establecimos inicialmente en nuestro archivo keystore.properties).
Luego le damos a «Guardar». Cuando Codmagic ejecute nuestro proceso de compilación, producirá automáticamente un APK o App Bundle firmado para nosotros.
¡Y eso es todo! Con este APK o App Bundle firmado, puedes desplegar tu aplicación en la Play Store.
Puedes echar un vistazo a mi repo de Git para ver un ejemplo aquí (obviamente, sin el keystore o las propiedades).
Eso es todo.
Si todavía estás perdido, no dudes en hacérmelo saber en @azimuthapps e intentaré ayudarte. Puede ser frustrante hacerlo bien, pero una vez que lo hagas, debería funcionar en el futuro previsible.
Lewis Cianci es un desarrollador de software en Brisbane, Australia. Su primer ordenador tenía una unidad de cinta. Ha estado desarrollando software durante al menos diez años, y ha utilizado bastantes marcos de desarrollo móvil (como Ionic y Xamarin Forms) en su tiempo. Sin embargo, después de convertirse a Flutter, nunca volverá atrás. Puedes contactar con él en su blog, leer sobre otras cosas no relacionadas con Flutter en Medium, o tal vez verle en la cafetería más cercana y más elegante con él y su querida esposa.
Más artículos de Lewis: