What I Learned Building Digital Identity Verification on Android
June 30, 2026 · 7 min read
Identity verification in emerging markets usually comes down to one tired pattern: ask the user to photograph their ID card and pray an OCR engine can read it. On a $100 Android with a scratched lens in bad lighting, optical OCR fails constantly — high error rates, manual review queues, and a wide-open door for anyone holding a printed photo of someone else's ID up to the camera.
There's a better path, and almost nobody outside the identity space talks about how it actually works under the hood. Most modern passports and national eIDs follow the ICAO 9303 standard, which means they carry an NFC chip with cryptographically signed data on it — including a high-resolution face photo. Read that chip, verify the signature chain, and you eliminate the entire class of OCR-and-printed-photo fraud in one move.
It sounds like a tap-and-done feature. It is not. I've been building an ePassport reader on Android as a personal project, and getting it working across cheap, fragmented hardware turned into one of the more stubborn cryptography-meets-hardware problems I've taken on. Here's what I learned, and where the real walls are.
1. The trust architecture, and why the network is your enemy
To prove a document is authentic you perform Passive Authentication: you hash the data groups on the chip and check them against the signatures in the Document Security Object (SOD). That signature has to chain back to a valid government Country Signing Certificate Authority (CSCA).
The problem in emerging markets is that mobile data is slow and flaky, and the moment a user is holding their ID against the back of the phone is the worst possible time to be blocking on a network call. If you try to pull a master list or a revocation list mid-read, the UX times out and the whole thing feels broken.
The fix is to keep the verification path local. The ICAO PKI distributes trust anchors through the ICAO Public Key Directory (PKD) master list, and revocation through CRLs — there is no meaningful real-time OCSP layer for CSCAs, so don't design around one. I cache the CSCA trust anchors on-device and refresh CRLs in the background, well outside the read flow. By the time a chip is being read, all the heavy cryptographic checking happens locally and doesn't depend on a clean 4G signal.
2. Waking the chip: BAC, PACE, JMRTD, and BouncyCastle
The chip is locked until you authenticate to it. The access key is derived from fields in the document's Machine Readable Zone (MRZ) — the document number, date of birth, and expiry date, each including its check digit (a detail that will silently break your key derivation if you skip it).
Older documents use Basic Access Control (BAC). Modern eIDs use PACE (Password Authenticated Connection Establishment), which is now the ICAO-preferred mechanism and what you'll hit on most recently issued documents — BAC is effectively legacy. A real reader needs to handle both and negotiate the right one.
I used JMRTD to drive the low-level APDU (Application Protocol Data Unit) command/response exchange with the chip. The transport here is half-duplex — one command, one response, over a single channel — so there's no actually reading two data groups "in parallel." What matters is reading the minimum set of data groups and ordering them well: DG1 (the MRZ data) is tiny and comes back instantly, so I read it first and start UI feedback immediately, then stream DG2 (the high-resolution face image, typically JPEG2000) in chunks while the user keeps the card steady. The perceived speed comes from prioritization, not parallelism.
Once the bytes are in hand, the data is wrapped in legacy ASN.1 / DER structures. BouncyCastle does the parsing — pulling apart the SOD, extracting the Document Signer Certificate, and running the actual signature verification.
One honest caveat worth stating plainly: Passive Authentication proves the data is genuinely signed by a legitimate authority and hasn't been tampered with. It does not, on its own, prove the chip isn't a clone — that's what Active Authentication / Chip Authentication address — and it says nothing about whether a live human is present, which is the job of face liveness on top. "Cryptographically signed" is not the same as "unspoofable." Anyone who tells you NFC reading alone gives you zero fraud is selling something.
3. The software trap: Fragments vs. Intents
Implement NFC on a modern Android app and you'll hit a structural problem almost immediately: the Android lifecycle fights you.
When a user taps an NFC tag, the Android Tag Dispatch System fires an Intent, and that Intent is caught at the Activity level. If you're on a modern single-Activity architecture with Fragments handling your scanning UI, this is where things break. With enableForegroundDispatch, navigating between fragments means the dispatch either fails to route the tag data or multiple fragments race to intercept the same tag — and you crash.
What worked for me:
- Centralize the intent. All NFC-catching logic lives strictly in the main Activity. Nothing downstream touches the raw hardware event.
- Use a shared ViewModel. The Activity catches the raw NFC intent and hands it to a shared ViewModel; the UI fragments just
observe()that ViewModel for state and animation. This fully decouples the hardware event from the fragment lifecycle. - Drop foreground dispatch for
enableReaderMode. It gives much tighter control over the NFC polling loop, and critically it lets you passFLAG_READER_NO_PLATFORM_SOUNDSto mute the OS-level scan chirps so the system doesn't stomp on your own in-app UX.
4. The hardware lottery
Testing on a Pixel at a desk does not prepare you for real devices. Android hardware and AOSP builds vary enormously, and that variance is where the pain lives:
- Antenna placement is a UX problem. NFC antenna location and sensitivity differ wildly by brand — top corner on one device, dead center on another. Telling a user to find an invisible square somewhere on the back of their phone is genuinely hard to do well.
- Cases are a documented killer. Thick, metallic, or magnet-laden cases attenuate and detune the RF coupling enough to block APDU traffic to the chip. (People often call this a "Faraday cage" effect — it's really RF attenuation and antenna detuning, but the practical result is the same: no read.) This is a well-known hazard in the NFC space, and any production flow needs to detect it and prompt the user to remove the case.
- NFC stack hangs were the worst surprise. During my own device testing I hit cases where a poor or interrupted transmission didn't just fail the read — it wedged the entire system-level NFC service, and the only recovery was rebooting the phone. This tracks with vendor-specific firmware quirks across AOSP forks, and it means defensive timeouts and clear recovery guidance aren't optional.
5. The edge case nobody warns you about: government supply chains
The strangest issue I ran into: valid eIDs from the same country failing data extraction at random. After a lot of debugging, the cause wasn't in my code — it was upstream, in how the documents are manufactured.
Governments buy blank eIDs in batches, from different international vendors (think Gemalto, Idemia, and others), across different years. A document issued in one year can carry a physically different chip and a slightly different file structure than one issued two years later — same country, same document type. Robust extraction has to tolerate inconsistency not just country-to-country, but batch-to-batch within a single country. That's the kind of thing you only discover by feeding a reader a messy pile of real documents, and it's rarely written down anywhere.
Conclusion
Building a cryptographic identity pipeline on Android drops you straight into the messy intersection of abstract math, a fragmented OS lifecycle, and cheap, inconsistent hardware. It's far more work than dropping in a third-party API.
But the payoff is real: you take automated OCR-and-printed-photo fraud off the table entirely, and you can verify a genuine document in seconds on a phone that costs less than the document it's reading. Pair the chip read with chip authentication and face liveness and you've got something a forged photo simply can't beat — built on standards that have been public the whole time, if you're willing to fight the hardware for them.