Skip to content

Android Upload Key — Rotation & Recovery

App: eSHARS Mobile (Android) Related ticket: HM-8427

Overview

The eSHARS mobile app uses Google Play App Signing. Google holds the app signing key (used to re-sign the APK delivered to users) and we hold the upload key (used to sign AABs before uploading to Google Play). This split means that even if the upload keystore is compromised, only the upload key needs to be rotated — the app itself is never at risk.

GitHub Actions
└─ upload-keystore.jks ─(signs AAB)─► Google Play ─(re-signs APK)─► User device

Where Artifacts Live

ArtifactLocationSecret / File Name
Upload keystoreGitHub repo secret (base64)ANDROID_KEYSTORE_BASE64
key.propertiesGitHub repo secret (base64)ANDROID_KEY_PROPERTIES_BASE64
Google Play service account JSONGitHub repo secretGOOGLE_PLAY_JSON_KEY
App signing keyGoogle Play Console (managed by Google)Never leaves Google

Secrets are stored in the eshars-mobile-ui GitHub repository: Settings → Secrets and variables → Actions.

key.properties format

storePassword=<store-password>
keyPassword=<key-password>
keyAlias=upload
storeFile=upload-keystore.jks

Passwords are also stored in Key Vault (kv-eshars-mgmt), secrets:

  • mobile-android-store-password
  • mobile-android-key-password

How the CI/CD Pipeline Uses These

The build.yml GitHub Actions workflow decodes both secrets and writes them to disk before the Flutter build:

- name: Setup Android signing
env:
KEY_PROPERTIES_BASE64: ${{ secrets.ANDROID_KEY_PROPERTIES_BASE64 }}
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
run: |
echo "$KEY_PROPERTIES_BASE64" | base64 --decode > android/key.properties
echo "$KEYSTORE_BASE64" | base64 --decode > android/app/upload-keystore.jks

Neither file is committed to the repo — they exist only for the duration of the runner job.


Local Development Setup

To sign a release build locally you need both artifacts from Key Vault or a team lead who can export them from GitHub secrets.

  1. Get the base64 values (from GitHub secrets or Key Vault) and decode them:

    Terminal window
    echo "<ANDROID_KEYSTORE_BASE64 value>" | base64 --decode > android/app/upload-keystore.jks
    echo "<ANDROID_KEY_PROPERTIES_BASE64 value>" | base64 --decode > android/key.properties
  2. Confirm both files are excluded by .gitignore before proceeding.

  3. Build a signed AAB:

    Terminal window
    flutter build appbundle --release

Upload Key Rotation

Rotate when the upload key is compromised, expired, or on a scheduled security policy basis.

1 — Generate a new upload keystore

Terminal window
keytool -genkey -v \
-keystore upload-keystore-new.jks \
-alias upload \
-keyalg RSA \
-keysize 2048 \
-validity 9125

Fill in the Distinguished Name fields when prompted. Store the new passwords in Key Vault (kv-eshars-mgmt) immediately.

2 — Export the public key for Google Play

Terminal window
keytool -export -rfc \
-keystore upload-keystore-new.jks \
-alias upload \
-file upload-key-new.pem

3 — Request key rotation via Google Play Console

  1. Go to Google Play Console → [eSHARS app] → Setup → App signing.
  2. Click Request upload key reset, upload upload-key-new.pem.
  3. Submit to Google support — typical turnaround is 1–3 business days.
  4. Google will confirm when the new key is active. The old keystore is invalid for uploads from that point on.

4 — Update GitHub secrets

  1. Base64-encode the new files:
    Terminal window
    base64 -w 0 upload-keystore-new.jks # → new value for ANDROID_KEYSTORE_BASE64
    base64 -w 0 key.properties # → new value for ANDROID_KEY_PROPERTIES_BASE64
  2. In the eshars-mobile-ui GitHub repo go to Settings → Secrets and variables → Actions and update both secrets.

5 — Update Key Vault

Update mobile-android-store-password and mobile-android-key-password in kv-eshars-mgmt with the new values (add a new version; don’t delete the old one until the rotation is confirmed working).

6 — Verify

Trigger the Build iOS and Android workflow with Deploy Android = true and confirm the AAB uploads successfully to Google Play.


Recovery — Lost or Corrupted Upload Keystore

Because Google holds the app signing key, losing the upload keystore does not mean losing the app. You can request a new upload key from Google.

Lost the JKS file (secret deleted or corrupted)

  1. Generate a new keystore (Step 1 above).
  2. Follow the Google Play key rotation steps (Steps 2–6 above).

Lost the passwords but file is intact

Passwords cannot be recovered from a JKS file. Treat this the same as a lost keystore — generate a new one and request rotation via Google Play.

Emergency contacts & access

ResourceLocation
Google Play ConsoleCheck android-release@authenticasolutions.com for account owner
GitHub secretseshars-mobile-ui repo → Settings → Secrets — any repo admin
Key Vaultkv-eshars-mgmt in rg-eshars-mgmt — Entra ID role or PIM

  • HM-8427 — Android: Configure app signing and upload key
  • Google Play App Signing docs
  • eshars-mobile-ui/android/app/build.gradle.kts — signing config wired to key.properties
  • eshars-mobile-ui/.github/workflows/build.yml — GitHub Actions build and deploy workflow