DevOps
Google Sign-In Works in Debug but Fails in Production on Android? Check This Hidden SHA-1
Sanjay Kumar Sah Dev.to (EN Zone)
3 views
Google Sign-In worked on my development build. It worked on my preview build. On the version Google Play delivered to real testers, tapping "Sign in with Google" did absolutely nothing — no account picker, no error message, no crash. Just a button that did nothing.
It took me several days to find. The answer turned out to be a fingerprint that Play Console doesn't show you anymore.
If you're here from a search, the short version is below.
TL;DR — the fix
If your app uses Play App Signing and you've opted into Quantum-ready app signing (beta), the SHA-1 fingerprint buttons on Play Console's App signing page are not the certificate that signs your app.
Go to Play Console → Protect with Play → Play Store protection → Manage Play app signing
Click Download certificates (not the SHA-1 fingerprint buttons)
You'll get three files. The one you need is deployment_cert.der
Get its SHA-1 fingerprint (commands below)
Put that fingerprint in your Android OAuth client in Google Cloud Console
No rebuild needed. Mine started working within minutes.
What "Google Sign-In is broken" actually looked like
Nothing. That's the hard part.
On modern Android, Sign in with Google runs through a system component called Credential Manager. When it can't complete a sign-in, it hands your app back a result that means "cancelled." Here's the code in the library I use:
} catch (e: GetCredentialCancellationException) {
OneTapResponse.cancelled()
} catch (e: NoCredentialException) {
OneTapResponse.noSavedCredential()
}
The problem: "cancelled" is also what you get when a user swipes the dialog away. Android uses one bucket for all of these:
the user genuinely dismissed the dialog
your app isn't registered properly with Google
your app's security certificate doesn't match what Google expects
your OAuth consent screen is still in "Testing" mode and this person isn't on the tester list
No error code. No message. Nothing to look up. And it only happens in the version you can't easily debug — the one Google Play installed on someone else's phone.
Plain English: what a "signing certificate" is and why it matters
Skip this if you already know. It's the core of the bug, so it's worth being clear.
Every Android app is digitally signed — think of it as a tamper-proof wax seal stamped onto the app file. The seal proves the app came from you and hasn't been modified.
A SHA-1 fingerprint is just a short, unique ID for that seal. It looks like this:
88:93:67:BB:FA:76:AC:40:3E:1D:AC:E2:9B:7D:2A:C0:45:7E:D1:BF
When your app asks Google "please sign this person in," Google checks two things:
Is this app's package name registered with me? (e.g. com.example.myapp)
Does the app's seal match the fingerprint I have on file?
If either answer is no, Google refuses — and on Android, that refusal comes back as "cancelled." So a wrong fingerprint looks exactly like a user changing their mind.
The twist: Google Play re-seals your app
Here's what trips up most people, and it's worth understanding even if your bug turns out to be something else.
When you upload your app to Google Play, you sign it with your upload key. Play then removes your seal and applies its own before sending the app to users. This is a feature called Play App Signing, and it exists so that losing your own key doesn't lock you out of updating your app forever.
The consequence: the app on a user's phone has a different fingerprint than the file you uploaded.
So you need to register both fingerprints with Google:
your upload key fingerprint (for builds you install directly, e.g. from a CI service)
Play's app signing key fingerprint (for everything from the Play Store)
Register only the first, and every Play Store install fails while every local install works perfectly. That is the classic version of this bug.
I checked the obvious thing. It looked correct.
So naturally, that's where I looked first.
I opened Google Cloud Console. The Android OAuth client had the right package name. It had a SHA-1 fingerprint copied straight from Play Console's app signing page. The consent screen was published to production. No unusual permissions requested. Everything matched.
I concluded the fingerprint theory was dead and started investigating the user's Google account on the device instead.
That was wrong, and it cost me another day. My mistake was simple: I was comparing a field in one console against a field in another console and trusting both. Neither of them described the app sitting on the phone.
The breakthrough: stop reading the console, read the phone
The console tells you what you configured. The phone tells you what's true. Google checks the certificate of the app that's actually installed, so that's the thing to measure.
So I plugged in the test phone, pulled the installed app off it, and read its fingerprint directly. (Full step-by-step is further down — it's optional, and you probably won't need it.)
Out came:
88:93:67:BB:FA:76:AC:40:3E:1D:AC:E2:9B:7D:2A:C0:45:7E:D1:BF
That matched neither the fingerprint registered in Google Cloud nor my upload key. A third fingerprint I had never seen before.
The real cause: Quantum-ready app signing (beta)
Back in Play Console, there was a small badge on the app signing page I'd never paid attention to: Quantum-ready (beta).
Quantum-ready app signing is Google preparing for a future where today's encryption can be broken by quantum computers. If you opt in, your app gets a hybrid key — a traditional ("classical") half and a new post-quantum half.
It also quietly changes the page. Where Play Console used to show plain text rows — MD5, SHA-1, SHA-256 of your app signing certificate — it now shows two buttons instead: Classical key and Post-quantum cryptography key.
Neither button gives you the certificate that signs your app.
Click Download certificates and you get three files:
File
What it actually is
deployment_cert.der
The certificate Play uses to seal your app. This is the one to register.
hybrid_classical_cert.der
Classical half of the quantum-ready pair — what the SHA-1 button copies
hybrid_pqc_cert.der
Post-quantum half — also not it
The deployment certificate — the ordinary Play App Signing key that has been there all along — has no fingerprint button on that page at all.
I had dutifully copied the only SHA-1 the page offered me. It was never going to work.
Fixing it, step by step
Play Console → Protect with Play → Play Store protection → Manage Play app signing
Click Download certificates and unzip. Find deployment_cert.der
Read its SHA-1 fingerprint (see the next section)
Google Cloud Console → APIs & Services → Credentials
Open your Android OAuth client, or create one (type Android, package name = your app's package)
Paste the fingerprint into SHA-1 certificate fingerprint. Save
Wait a few minutes, then test on a Play Store build
While you're there, create a second Android OAuth client with your upload key fingerprint, so builds you install directly also work. Each client holds exactly one fingerprint, so you need one per certificate.
Three ways to read a .der fingerprint
A certificate's SHA-1 fingerprint is simply a SHA-1 hash of the certificate file itself. So you don't need any Android tooling at all.
Windows PowerShell — nothing to install, and it formats the result with colons exactly the way Google Cloud wants:
$c = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("deployment_cert.der")
$c.Thumbprint -replace '(..)(?!$)','$1:'
Windows, even shorter — certutil ships with Windows (output has no colons; Google Cloud accepts it either way):
certutil -hashfile deployment_cert.der SHA1
macOS / Linux / anywhere with a JDK or OpenSSL:
keytool -printcert -file deployment_cert.der
# or
openssl x509 -inform DER -in deployment_cert.der -noout -fingerprint -sha1
A genuine Play App Signing certificate shows CN=Android, O=Google Inc. as its owner. That's a good sanity check that you grabbed the right file.
Optional: prove it by reading the app on the phone
You probably don't need this section. If registering deployment_cert.der's fingerprint fixed your sign-in, you're done — close the tab.
Do this when the fix didn't work, or when you want hard proof of which certificate is on a device before you change anything. It's the step that ended a week of guessing for me, and it works on any Android phone with any app.
Step 1 — Get adb
adb (Android Debug Bridge) is the tool that talks to a connected phone. You do not need Android Studio.
Download SDK Platform-Tools for your OS: developer.android.com/tools/releases/platform-tools
Unzip it anywhere — a platform-tools folder with adb inside
Either add that folder to your PATH, or just run adb with its full path
Quick check that it runs:
adb version
# Android Debug Bridge version 1.0.41
macOS users can alternatively brew install --cask android-platform-tools.
Step 2 — Turn on USB debugging on the phone
Settings → About phone → tap Build number seven times. You'll see "You are now a developer"
Settings → System → Developer options → turn on USB debugging
Plug the phone into your computer with a USB cable
Change the USB mode to File transfer / MTP (not "Charging only") — on some phones the debugging prompt won't appear otherwise
A dialog appears on the phone: "Allow USB debugging?" → tick Always allow from this computer → Allow
Confirm the computer can see it:
adb devices -l
# List of devices attached
# 95QW4TIFNRIBCAR8 device product:... model:...
If it says unauthorized, you missed the prompt on the phone. If the list is empty, try a different cable — plenty of USB cables are charge-only.
Step 3 — Confirm you're testing the Play Store build
This matters. A sideloaded build carries a different certificate, and testing the wrong one sends you down the wrong path.
adb shell pm list packages -i | grep com.example.myapp
Look at the installer= value:
installer=com.android.vending → installed by the Play Store ✅ this is what you want
installer=com.google.android.packageinstaller → sideloaded APK
installer=null → installed by a developer tool
Step 4 — Pull the app off the phone
adb shell pm path com.example.myapp
# package:/data/app/~~aBcD.../com.example.myapp-XyZ.../base.apk
adb pull /data/app/~~aBcD.../com.example.myapp-XyZ.../base.apk ondevice.apk
Paste the exact path the first command printed. If it prints several lines, you want the one ending in base.apk.
Step 5 — Read its real fingerprint
If you have Android SDK build-tools installed, this is a one-liner:
apksigner verify --print-certs ondevice.apk
Watch out for this trap. The obvious command does not work:
keytool -printcert -jarfile ondevice.apk
# (prints absolutely nothing)
Apps delivered by Play use a newer signature format that -jarfile can't read. It doesn't fail with an error — it succeeds and prints nothing at all. Easy to misread as "no info available." I did exactly that.
No build-tools? Save this as apk-cert.js and run node apk-cert.js ondevice.apk. No dependencies:
// apk-cert.js — print the signing certificate fingerprint of an APK.
// Works on modern (v2/v3-signed) APKs, including ones delivered by Google Play.
const fs = require("fs");
const crypto = require("crypto");
const buf = fs.readFileSync(process.argv[2]);
// 1. Find the ZIP "end of central directory" record. It points at the central
// directory, and the APK Signing Block sits immediately before that.
let eocd = -1;
for (let i = buf.length - 22; i >= Math.max(0, buf.length - 66000); i--) {
if (buf.readUInt32LE(i) === 0x06054b50) { eocd = i; break; }
}
if (eocd < 0) throw new Error("Not a ZIP/APK file");
const cdOffset = buf.readUInt32LE(eocd + 16);
if (buf.slice(cdOffset - 16, cdOffset).toString("latin1") !== "APK Sig Block 42") {
throw new Error("No APK Signing Block — try: keytool -printcert -jarfile <apk>");
}
// 2. Walk the block's id/value pairs. 0xf05368c0 = scheme v3, 0x7109871a = v2.
const blockSize = Number(buf.readBigUInt64LE(cdOffset - 24));
let off = cdOffset - blockSize - 8 + 8;
const blockEnd = cdOffset - 24;
const blocks = {};
while (off < blockEnd) {
const len = Number(buf.readBigUInt64LE(off));
blocks[buf.readUInt32LE(off + 8)] = buf.slice(off + 12, off + 8 + len);
off += 8 + len;
}
const schemes = [[0xf05368c0, "v3"], [0x7109871a, "v2"]].filter(([id]) => blocks[id]);
if (schemes.length === 0) throw new Error("No v2/v3 signature found");
// 3. signers -> first signer -> signed data -> certificates -> first certificate (DER).
// Every field is a 4-byte little-endian length followed by that many bytes.
for (const [id, name] of schemes) {
const v = blocks[id];
let p = 0;
const u32 = () => { const x = v.readUInt32LE(p); p += 4; return x; };
u32(); // length of the signers sequence
u32(); // length of the first signer
const signedDataLen = u32();
const sd = v.slice(p, p + signedDataLen);
let q = 0;
const s32 = () => { const x = sd.readUInt32LE(q); q += 4; return x; };
const digestsLen = s32();
q += digestsLen; // skip the digests section
const certsLen = s32();
const certsEnd = q + certsLen;
console.log(`--- ${name} signature ---`);
let n = 0;
while (q < certsEnd) {
const certLen = s32();
const der = sd.slice(q, q + certLen);
q += certLen;
const fp = (alg) =>
crypto.createHash(alg).update(der).digest("hex").toUpperCase().match(/../g).join(":");
console.log(` certificate #${++n}`);
console.log(` SHA-1 : ${fp("sha1")}`);
console.log(` SHA-256: ${fp("sha256")}`);
}
}
Output looks like this — and both signature versions should agree:
--- v3 signature ---
SHA-1 : 88:93:67:BB:FA:76:AC:40:3E:1D:AC:E2:9B:7D:2A:C0:45:7E:D1:BF
SHA-256: 27:9F:A2:D6:99:52:E5:03:6F:94:26:86:00:B7:FB:81:A4:D9:4A:F8:...
--- v2 signature ---
SHA-1 : 88:93:67:BB:FA:76:AC:40:3E:1D:AC:E2:9B:7D:2A:C0:45:7E:D1:BF
Step 6 — Compare
Whatever comes out of Step 5 is what Google checks. If it doesn't match the SHA-1 on your Android OAuth client, that is your bug, no matter how correct the consoles look.
One more sanity check, in case your app's signing key was ever rotated:
adb shell dumpsys package com.example.myapp | grep signatures
# signatures:[...], past signatures:[]
An empty past signatures:[] means no key rotation is muddying the comparison.
Quick troubleshooting checklist
If Google Sign-In fails only on your Play Store build, work down this list:
[ ] Is the package name on your Android OAuth client exactly right? Many setups add suffixes like .dev or .staging per build type — each needs its own client
[ ] Did you register Play's app signing fingerprint, not just your upload key?
[ ] If you use Quantum-ready app signing, did you use deployment_cert.der rather than the SHA-1 buttons?
[ ] Is your OAuth consent screen published ("In production"), not left in Testing? In Testing mode, anyone not on the tester list is blocked — and that also shows up as "cancelled"
[ ] Are you passing your Web client ID to the sign-in library, not an Android one? Most libraries want the Web client ID even on Android — it's confusing, and it's correct
[ ] Still stuck? Pull the APK off the phone and read its real fingerprint (section above)
Five things I'd tell myself a week ago
When two dashboards agree and reality disagrees, go measure reality. I spent days comparing a Google Cloud field to a Play Console field. Both were internally consistent. Neither described the app on the phone. The first adb pull ended the investigation in minutes.
Beta features move where the truth lives. Quantum-ready app signing didn't break anything. It relocated a value and left a similar-looking, differently-named one in its place. Every blog post and checklist that says "copy the SHA-1 from the app signing page" is now quietly wrong for these apps — including my own team's internal docs, which I've since fixed.
A tool that prints nothing is worse than one that errors. keytool -printcert -jarfile on a modern APK succeeds silently and outputs nothing. Empty output is not the same as "no data exists." Always question it.
Add your diagnostics before you need them. I'd already wasted a build cycle adding a console.log, so I made the app report on itself: every stage of sign-in sends an analytics event carrying the app version, build number, device model and configuration fingerprints. That didn't find the bug — but it eliminated my entire app as a suspect in one afternoon, and told me exactly which build and which phone each failure came from.
When an error collapses several causes into one label, add something that separates them. "Cancelled" means four different things here. I now time how long the sign-in call takes: under about 300 milliseconds means no dialog ever appeared, which points at configuration; several seconds means the user really did see the dialog, which points at their device or account. That one number would have halved my search space on day one.
If this saved you a day, the specific thing to remember is short: deployment_cert.der, not the SHA-1 button.
Read original: https://dev.to/sanjaysah/google-sign-in-works-in-debug-but-fails-in-production-on-android-check-this-hidden-sha-1-18nn
← Previous
Got tired of writing READMEs, so I built a tool that does it for me
Next →
I Built a Financial Dashboard for Indie Devs and Digital Creators and It is Free
Related
The 2-Hour Bash Bug That Taught Me How Quoting Actually Works
DevOps
0
Dev.to (EN Zone)
Choosing free on-prem git server - Gitea is the winner!
DevOps
1
DEV Community
Daily Dose of DevOps — Terraform remote state explained
DevOps
1
DEV Community
[Showoff Saturday] Mac MCP: background browser automation and live agent sessions on macOS
DevOps
1
Reddit r/webdev
Comments0
No comments yet — be the first