You cannot ship an unsigned app to users. On both iOS and Android, code signing is a prerequisite for distribution — it proves that "this package really comes from the claimed developer and has not been tampered with." But the mechanics differ a lot between the two platforms, and concepts like certificates, profiles, and keystores often confuse newcomers. This article explains both in plain language.

Code signing solves three things

Whichever the platform, signing provides three guarantees:

  • Identity: the package comes from an identifiable developer or organization.
  • Integrity: the package has not been altered since it was signed.
  • Trust: the operating system (and stores) use this to decide whether to allow install or execution.

Without a valid signature, iOS refuses to run the app, and Android on modern versions strongly warns or blocks it.

iOS: certificates, App IDs, and provisioning profiles

iOS signing combines several artifacts, and their relationship is the biggest source of confusion:

  • Certificate: issued by Apple and tied to your developer account. There are development, distribution, and enterprise certificates. The private key lives in your keychain.
  • App ID: the app's unique identifier (usually a reverse-domain bundle id), which can be linked to capabilities (push, payments, etc.).
  • Provisioning profile: bundles "certificate + App ID + allowed devices/capabilities" into one file. The app must be signed with the certificate that matches the profile to install.

An ad-hoc profile lists the device UDIDs allowed to install; an enterprise profile targets any device in the organization; an App Store profile targets all users (via the store). Changing distribution method often just means changing the profile.

Android: the signing keystore

Android is more direct: you sign .apk or .aab files with a keystore (containing one or more entries with private keys). Debug builds use an auto-generated debug keystore; release builds must use your own release keystore. Every version of the same app should use the same key, so the system recognizes them as the same app and allows updates.

Losing your release keystore means losing the app's "identity" — you can no longer publish updates (unless Play App Signing is enabled).

Google Play App Signing

Google offers optional Play App Signing: you sign uploads with an upload key, Google uses it to generate the final signature delivered to users, and Google safeguards the app signing key for you. The benefit: if the upload key is lost or leaked, you can request a reset instead of losing publishing ability forever. For most new apps, it is recommended.

Common pitfalls

  • Expired certificates: iOS certificates are valid for one year. Renew before expiry or you cannot ship new builds.
  • Profile mismatch: the signing certificate differs from the one in the profile, or a device UDID is not in the ad-hoc profile — installation fails outright.
  • Lost or leaked keys: an Android release keystore with no backup, or a private key committed to source control, are serious incidents.
  • Inconsistent signing across channels: direct APK and store builds using different keys mean users cannot update across channels.

Best practices

  • Back up your keystore securely: multiple offline copies, password stored separately, never in source control.
  • Enable Play App Signing as a safety net for Android releases.
  • Make signing part of your pipeline: when CI signs builds, inject keys via secure variables rather than importing them by hand.
  • Track expiry dates: set reminders for iOS certificate renewal to avoid last-minute scrambles.

Conclusion

Code signing is the gatekeeper of distribution. Understanding its mechanics eliminates a huge amount of "won't install" and "can't update" debugging. iOS's certificate-and-profile system is more complex but rule-based; Android's keystore is simpler but the cost of losing a key is extreme. Treat signing assets as core infrastructure, and the rest of distribution becomes far smoother.