The Android jobs failed with "Get Key failed: Given final block not properly
padded" once the store password was corrected - the keystore opened, but the
KEY could not be decrypted.
Cause: GitHub Actions substitutes an empty string, not an unset variable, for
a secret that does not exist. ANDROID_KEY_PASSWORD is deliberately not set (our
keystore is PKCS12, where the key password cannot differ from the store
password), so OSTP_KEY_PASSWORD arrived as "". Kotlin's elvis operator only
falls back on null, so `getenv(...) ?: storePassword` kept the empty string and
used it as the literal key password.
signingSetting() now maps blank to null, so the documented fallback actually
happens. Applies to every signing field, not just the key password - the same
trap would have hit any of them.
Our upload keystore is PKCS12 (verified from its DER header, 0x30 0x82 —
JKS would start 0xFEEDFEED). That format has nowhere to store a key password
distinct from the store password, and keytool enforces the two being equal,
so requiring a separate OSTP_KEY_PASSWORD meant configuring a secret whose
only possible correct value was a copy of another one.
Falls back to the store password when unset; an explicit value still takes
precedence for the legacy JKS format, where the two can genuinely differ.
Published APKs could never be updated over - users hit "App not installed" or
"unable to parse the package" and had to uninstall first. The cause was not the
version code (verified: local.properties carries flutter.versionCode=23 and
gha.ps1 bumps pubspec's build number every release, so it increments correctly).
It was the signing key: app/build.gradle.kts still had the stock Flutter
template TODO and pointed the release build type at signingConfigs["debug"].
Android identifies an app by applicationId + signing key and refuses to update
across a key change, and the debug keystore is generated per machine - on
ephemeral CI runners that means every single published build was signed with a
different random key.
Release builds now take their key from android/key.properties or the
OSTP_KEYSTORE_* environment variables, falling back to debug (with a loud
warning) only so local `flutter build apk --release` keeps working. CI
materialises the keystore from repository secrets, refuses to build at all if
the secret is absent, and re-verifies the finished APK is not debug-signed
rather than ever shipping an un-updatable build again.
NOTE: existing installs are signed with a now-unreproducible random key, so
users must uninstall once more for THIS release. Every update after it works.