Android App Signing: Keystore, Gradle, CI/CD, and Play App Signing
Losing the keystore file is irreversible. If the private key is lost, updating your existing app on Google Play becomes impossible. You'll have to publish a new app with a new package name, losing all reviews, download history, and search rankings. In our experience, such incidents cost developers months of downtime and lost revenue. To prevent this, we configure Android app signing end-to-end: from keystore generation to CI/CD and Play App Signing. Our engineers are Google-certified with years of Android development experience. Contact us for a free project assessment and optimal solution.
In this guide, we will cover keystore creation, configuring Signing Config in Gradle without storing passwords in code, enabling Play App Signing, and automating signing on CI. Following these steps will protect your keys and prevent losing access to your app.
Creating and Storing the Keystore
Generate via keytool:
keytool -genkeypair -v \ -keystore release.keystore \ -alias myapp \ -keyalg RSA \ -keysize 2048 \ -validity 10000 \ -storetype JKS -validity 10000 is about 27 years. Google recommends at least 25 years for apps on Play Store. A shorter period may cause Google Play to reject future updates. We recommend using PKCS12 format (-storetype PKCS12) — more secure and compatible.
The keystore must not be stored in a Git repository, not even a private one. Storage rules: encrypted backup in at least two cloud storages, a physical copy off-site, and passwords separate from the file.
Keystore Parameters: Recommendations
| Parameter | Recommendation | Why |
|---|---|---|
| Algorithm | RSA 2048 bits | Balance of security and performance |
| Validity | 10,000 days (27 years) | Covers the full app lifecycle |
| Format | PKCS12 | More robust encryption than JKS |
| Alias | App name | Convenient for multiple keys |
How to Configure Signing Config in Gradle Without Passwords
Hardcoding paths and passwords in build.gradle is an antipattern:
// DO NOT DO THIS — passwords in the repository signingConfigs { release { storeFile file("../keys/release.keystore") storePassword "mysecretpassword" // will end up in git keyAlias "myapp" keyPassword "mysecretpassword" } } The correct approach is through environment variables or local.properties:
// build.gradle (app) def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('keystore.properties') if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } android { signingConfigs { release { keyAlias keystoreProperties['keyAlias'] ?: System.getenv('KEY_ALIAS') keyPassword keystoreProperties['keyPassword'] ?: System.getenv('KEY_PASSWORD') storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null storePassword keystoreProperties['storePassword'] ?: System.getenv('STORE_PASSWORD') } } buildTypes { release { signingConfig signingConfigs.release minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } keystore.properties should be in .gitignore. On CI, pass environment variables directly. For Kotlin DSL, the configuration is analogous using getProperty. Learn more about Signing Config.
Additional: Verify fingerprint
keytool -list -v -keystore release.keystore -alias myapp In Play Console: Setup → App signing → App signing key certificate — compare SHA-256. For Firebase/OAuth fingerprints, use the app signing key, not the upload key.
How to Protect Keys from Leakage?
Use environment variables or keystore.properties in .gitignore. Never store passwords in code. For CI, use base64 encoding of the keystore and repository secrets. This reduces the risk of key exposure if the repository is compromised.
Why Use Play App Signing?
Play App Signing is insurance: if the upload key is lost, Google can rotate it. You sign only the upload key, and Google repackages the app with a separate app signing key. This is 100 times more secure than storing a single key. Enable it in Play Console: Release → Setup → App signing. Once enabled, it cannot be disabled.
Comparison of Signing Methods
| Method | Security | Recovery | Complexity |
|---|---|---|---|
| Single key (upload only) | Medium | No | Low |
| Play App Signing | High | Yes (via Google) | Medium |
| Multiple keys (without Play) | High | No | High |
How to Integrate Signing into CI/CD?
On GitHub Actions:
- name: Sign APK env: KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} KEY_ALIAS: ${{ secrets.KEY_ALIAS }} KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }} run: | echo "$KEYSTORE_BASE64" | base64 --decode > release.keystore ./gradlew bundleRelease \ -Pandroid.injected.signing.store.file=$(pwd)/release.keystore \ -Pandroid.injected.signing.store.password=$STORE_PASSWORD \ -Pandroid.injected.signing.key.alias=$KEY_ALIAS \ -Pandroid.injected.signing.key.password=$KEY_PASSWORD Encode the keystore in base64 (base64 release.keystore) and save it in repository secrets. On the agent, decode, use, and delete after the job.
What's Included
- Keystore creation with parameters tailored to your project (algorithm, validity, format).
- Gradle configuration for all flavor builds.
- Integration with Play App Signing (including key migration).
- CI/CD setup (GitHub Actions, GitLab CI, or other).
- Documentation on key storage and rotation.
Typical Signing Configuration Mistakes
- Storing keystore in the repository — key can be compromised.
- Using JKS instead of PKCS12 — less secure encryption.
- Incorrect fingerprint — Firebase, OAuth services stop working.
- No keystore backup — losing access to the app.
Timeline Estimates
Setting up signing for one flavor takes 2 to 4 hours. For multiple flavor configurations and Play App Signing integration, about one working day. Schedule a consultation for your project today.
According to Android Developers recommendations, Play App Signing reduces the risk of losing app access by an order of magnitude.







