December 11th, 2025
The Day I Finally Understood Android Signing and Authentication Today was split into two major learning sessions: understanding Android’s entire signing system from scratch, and debugging BetterAuth + Convex integration.Part 1: Android Signing Deep Dive
Why Does Android Need Signing?
Android MUST ensure three things:- The APK really came from the developer
- The APK was not modified by anyone
- Updates must be signed by the same developer
- Developer keeps a private key (SECRET)
- Developer gives everyone a public key (PUBLIC)
- Everyone can verify, but only the private key can sign
The Keystore: The Heart of Everything
A keystore contains: 1. Private key- Your true identity. Secret.
- Created by Gradle for debug builds
- Created by you for release builds
- Mathematically linked to private key
- Impossible to reverse
- Public wrapper containing: public key, your metadata (subject, issuer, validity), the signature
How Certificates Are Built
The certificate includes:- Version
- Serial Number
- Signature Algorithm ID
- Issuer
- Subject
- Validity Period
- Public Key Info
- Extensions (like KeyUsage)
- Signature:
RSA(private_key, SHA256(TBS))
The Signing Algorithm (SHA256withRSA)
Step 1: Hash the certificate data (TBS) using SHA-256- You OWN the private key
- The certificate has not been altered
APK Signing is Different (And More Powerful)
This was the missing piece I discovered. Android does NOT just sign the certificate. Android signs THE ENTIRE APK CONTENT. APK Signing Scheme v1/v2/v3 do this:- Your certificate
- Your APK signature
Why You Cannot Copy Certificate + Signature
My old confusion: “If someone copies the certificate and signature from App A and pastes them to App B… Why doesn’t it work?” The truth: The signature signs the hash of the entire APK, not just the certificate.- Signature_A and Certificate_A applied to APK_B
- Android computes:
hash_B = SHA256(APK_B_Content) - Android decrypts:
hash_A = RSA_Decrypt(public_key, Signature_A) - Compare:
hash_A != hash_B→ INVALID
- Attacker copied both certificate AND signature
- Attacker did not touch the certificate fields
- Attacker had infinite money
- Attacker had supercomputers
Why Modifying the Certificate or Signature Is Impossible
APK is a ZIP with strict structure. If attacker tries to:- Modify certificate
- Modify META-INF files
- Modify signature blocks
Why Public Certificate Is SAFE to Expose
People worry: “Anyone can see my certificate. Won’t they pretend to be me?” NO. Because:- Certificate contains public key → safe
- Signature contains encrypted hash → safe
- Only the private key can generate a valid signature
RSA(private_key, hash) DOES NOT help you derive private key.
Knowing: public key, signature, hash is completely safe. Modern crypto makes reversing this mathematically impossible.
Why SHA-1 Fingerprint Exists (OAuth)
Google OAuth asks for SHA-1. Why? Because SHA-1 fingerprint uniquely identifies YOUR certificate. Google servers store:- Google Play Services sends: your package name, your certificate’s SHA-1
- Google checks if they match
The PRIVATE KEY = YOUR IDENTITY
Everything depends on ONE thing only: Your private key.- If you lose it → You can NEVER update your app again
- If someone steals it → They can impersonate your app COMPLETELY
Final Clean Summary
When building APK:- Gradle generates or uses your private key
- Creates certificate containing your public key
- Signs entire APK with private key
- Certificate (public)
- Signature (public)
- Your code
- Modify APK
- Reuse signature
- Reuse certificate
- Produce new valid signature
- Spoof SHA-1
- Bypass OAuth
Your private key signs the APK. The signature proves authenticity. The certificate exposes nothing dangerous. Copy-pasting signatures or certificates NEVER works.
Part 2: BetterAuth + Convex Debugging
Understood BetterAuth Client Setup
Went through this block:anonymousClient()→ allows functions you can call without logging in (public access)expoClient()→ injects deep linking + secure storage into auth flow for React NativeconvexClient()→ syncs BetterAuth with Convex backend (session, user, tokens)
oauth.signIn("google")auth.getSession()auth.getUser()- Secure token storage
- Deep link callback handling
Why We Need Deep Linking (expoClient)
Discussed how the OS knows which app to return to after Google login. Examples:- Your scheme = “mono”
- Redirect URI =
mono://auth/callback
Set Up Sign-In Screen Logic
Updated the Signin screen to:- Check if a previous session exists (using
authClient.auth.getSession()) - Redirect if logged in (using
router.replace("/(main)/commits")) - Added Google login handler (using
authClient.oauth.signIn("google"))
- Session object
- User object
Identified the REAL Problem
This was the big discovery today: The auth routes/auth/session, /auth/user were returning 404
This means:
- BetterAuth backend was not deployed OR
- Correct Convex deployment not linked OR
- Env variables were pointing to the wrong convex.site URL
Ran Convex Backend Locally
Executed:Compared ENV Files
Checked backend env, frontend env, native env, and saw: Mixed deployments:- Some envs pointing to
watchful-ptarmigan-492 - Some pointing to
different-dotterel-809 - Some
.siteurls not updated - Some
.cloudurls mismatched
/auth/session.
Tested the Route in Browser
Opened:- Either wrong deployment
- Or backend not deployed to cloud after adding auth.ts
Final Attempt: Corrected the .env.local
Gave a fully fixed env:Summary: What’s Left for Tomorrow
- Confirm the backend actually deployed with BetterAuth routes
- Hit this URL in browser:
- Backend deployment
- Convex status
- Convex logs
- File placement (convex/auth.ts)
- Correct URL wiring
- Google redirect URI
- Deep linking setup
What I Learned Today
- BetterAuth client architecture
- Expo deep linking
- OAuth redirection
- Convex + BetterAuth integration
- Why sessions return 404
- How environment variables wire a system
- How backend routes work in Convex
- Complete Android signing system (RSA, SHA, certificates)
- Why copy-pasting signatures never works
- Why public certificates are safe