CI/CD & App Store Deployment

App Signing Fundamentals: Android Keystores

15 min Lesson 1 of 12

App Signing Fundamentals: Android Keystores

Before any Android application can be distributed through the Google Play Store — or installed directly on a device outside of debug mode — it must be digitally signed. Signing proves that a particular APK or AAB was produced by a trusted party (you, the developer) and has not been tampered with since it was built. This lesson covers everything you need to know: what a keystore is, how to generate one, how to configure key aliases and passwords, and how to wire Gradle's signingConfigs block so your release builds are signed automatically.

Note: A debug build is automatically signed with a shared debug key created by Android Studio. That key is never accepted by the Play Store. A release build must be signed with your own keystore before it can be uploaded or installed on end-user devices.

What Is a Keystore?

A keystore is a binary file (typically .jks or .keystore extension) that acts as a secure container. It stores one or more key entries, each consisting of a private key and its associated public-key certificate chain. Android uses the private key inside the keystore to sign your APK or AAB, and devices use the corresponding public key to verify the signature at install time.

  • Keystore password — protects the keystore file itself.
  • Key alias — a human-readable name identifying a specific key entry inside the keystore.
  • Key password — protects the individual key entry (can be the same as the keystore password, but keeping them separate is safer).
  • Validity — Google recommends a validity of at least 25 years so the same key covers the entire lifetime of the app.
Warning: If you lose your keystore file or forget its passwords, you can never produce an update that Android or the Play Store will accept for that app. Back up the keystore in at least two secure, offline locations the moment you generate it.

Generating a Keystore with keytool

The Java Development Kit (JDK) ships with a command-line utility called keytool that creates and manages keystores. Run the following command in your terminal (replace the placeholders with your own values):

Generate a new keystore

keytool -genkey -v \
  -keystore ~/keys/my-release-key.jks \
  -storetype JKS \
  -keyalg RSA \
  -keysize 2048 \
  -validity 10000 \
  -alias my_key_alias

The flags mean:

  • -keystore — path where the new keystore file will be written.
  • -storetype JKS — use the Java KeyStore format (also acceptable: PKCS12).
  • -keyalg RSA and -keysize 2048 — RSA 2048-bit is the minimum accepted by the Play Store.
  • -validity 10000 — approximately 27 years of validity.
  • -alias — the name you will reference in Gradle.

After running the command, keytool will interactively ask for the keystore password, the key password, and your Distinguished Name fields (first/last name, organisation, city, country). Fill these in accurately — the Play Store stores them for identity verification.

Storing Credentials Securely with a key.properties File

Hard-coding passwords in build.gradle is a serious security mistake — anyone who clones your repository gains access to your signing credentials. The Flutter community convention is to store credentials in a key.properties file that lives outside version control.

android/key.properties

storePassword=yourKeystorePassword
keyPassword=yourKeyPassword
keyAlias=my_key_alias
storeFile=../../keys/my-release-key.jks

Add key.properties to your .gitignore immediately:

.gitignore (android section)

# Signing credentials — never commit
key.properties
*.jks
*.keystore

Configuring signingConfigs in build.gradle

Open android/app/build.gradle. You need to (1) load the key.properties file, (2) declare a signingConfigs block, and (3) apply that config to the release build type.

android/app/build.gradle — complete signing setup

// 1. Load key.properties at the top of the file, before android {}
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

android {
    // ... existing compileSdkVersion, defaultConfig, etc.

    // 2. Declare the signing configuration
    signingConfigs {
        release {
            keyAlias     keystoreProperties['keyAlias']
            keyPassword  keystoreProperties['keyPassword']
            storeFile    keystoreProperties['storeFile'] != null
                             ? file(keystoreProperties['storeFile'])
                             : null
            storePassword keystoreProperties['storePassword']
        }
    }

    // 3. Apply it to the release build type
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
                          'proguard-rules.pro'
        }
    }
}
Tip: The null-check on storeFile prevents Gradle from crashing on CI machines where key.properties may not exist. On CI you should inject the signing credentials via environment variables or secret managers instead.

Building a Signed Release APK or AAB

Once Gradle is configured, run one of the following Flutter CLI commands from the project root:

Build signed release artifacts

# Build a signed App Bundle (preferred for Play Store)
flutter build appbundle --release

# Build a signed APK (for direct installation or Amazon Appstore)
flutter build apk --release

# Build fat APK containing all ABIs (larger, useful for sideloading)
flutter build apk --release --split-per-abi

The signed AAB will be written to build/app/outputs/bundle/release/app-release.aab, and the APK to build/app/outputs/flutter-apk/app-release.apk. You can verify the signature with:

Verify signature on the output artifact

keytool -printcert -jarfile build/app/outputs/flutter-apk/app-release.apk

Play App Signing vs. Self-Signing

Google Play offers Play App Signing, where Google manages the final signing key and you only upload an upload key. If you enroll:

  • Your upload key (what you generate above) signs the AAB you upload.
  • Google re-signs the app with the app signing key before distributing it to devices.
  • If your upload key is compromised, you can request a key reset. If you self-sign and lose the key, your app is permanently unreleasable.
Summary: App signing is mandatory for release Android builds. Use keytool to generate a .jks keystore, store credentials in a gitignored key.properties file, declare a signingConfigs block in build.gradle that reads from that file, and run flutter build appbundle --release to produce a Play-Store-ready signed AAB. Back up the keystore immediately.