Banking apps on stock LineageOS + microG in 2026
Table of contents
I run stock LineageOS without GApps. microG handles the Play Services replacement. Most apps work fine on this setup. Banking apps and anything that calls Google's Play Integrity API do not, at least not without a stack of community modules and one specific microG toggle that defaults to "block".
Can a Google-free Android still pass Play Integrity well enough for banking apps?
Short answer: yes, DEVICE-grade, on a custom ROM with an unlocked bootloader. It takes a leaked keybox, the right modules, and one toggle off. I spent a long evening getting Revolut, Erste George, McDonald's, and the rest of the Aurora-installed proprietary apps to work on this device. Here is what mattered and what was a dead end.
The premise#
Phone: Samsung Galaxy S10+, unlocked bootloader (Knox tripped, Samsung's permanent warranty bit), stock LineageOS 23.2 (Android 16), rooted with APatch. No Google Play Services - microG only, installed as a system priv-app. F-Droid + Aurora Store for app installs. The phone exists because I want a usable Android without a Google account, but I also want my bank and my food orders to work.
Default state, before any integrity work: Revolut would not get past the login screen. George (Austrian Erste Bank app) crashed on launch in user 0 and showed an integrity error in the work profile. McDonald's refused to install because Aurora Store sets installerPackageName=com.aurora.store and the app checks for com.android.vending. The Play Integrity API Checker returned a generic -100 error which is microG-specific noise, not a useful diagnostic.
End state, a few hours later: all of them work. Reliably enough that I have not had to retouch the setup in a week. Here is what actually mattered.
The one toggle#
microG 0.3.9 added a setting called Block hardware attestation under Device Attestation -> Advanced. It defaults to on. Most "I tried microG, banking apps don't work" guides stop right there.
When this is on, microG's DroidGuard re-implementation refuses to forward hardware-attestation requests to Google's Play Integrity API. The verdict path returns MEETS_BASIC_INTEGRITY at best. That is why every guide on the internet says microG can't pass DEVICE integrity. Banking apps that require DEVICE, which is most of them now, show their flavour of "this device cannot be trusted".
Turn it off, reboot, and microG produces DEVICE-grade integrity tokens when the rest of your stack is set up right. I found it nowhere in the first dozen pages of search results. It is buried in a microg/GmsCore GitHub issue with 72 comments going back to mid-2025, in a reply from the maintainer.
The "rest of your stack" is the keybox story.
The keybox economy#
Hardware-backed Play Integrity wants the device to sign an attestation challenge with a key chained to Google's Hardware Attestation Root CA. A real phone with a working TEE produces this naturally. A custom ROM with an unlocked bootloader cannot, the bootloader unlock broke the chain.
The workaround is a leaked OEM keybox. Someone extracted a real per-device attestation key from a real phone (Samsung Knox, Pixel TEE, MediaTek, the OEM doesn't matter much). The private key plus its full certificate chain ends up in an XML file called keybox.xml. You drop that file in /data/adb/tricky_store/keybox.xml. A module called TEESimulator (the successor to the dormant Tricky Store) intercepts the Android Keystore API calls, signs the attestation challenge with the leaked key, and returns the leaked chain.
App asks for integrity verdict
|
v
Android Keystore attestation --> TEESimulator intercepts
| |
| signs with leaked key,
| returns leaked chain
v v
Google verifier checks chain against Hardware Attestation Root CA
|
+-- leaf serial NOT on CRL --> DEVICE
+-- leaf serial on CRL --> BASIC, back to weekly rotationGoogle checks the chain, doesn't notice (or doesn't care) that the same per-device key is in use across thousands of devices, and returns DEVICE. It does eventually notice. Every leaked keybox lands on Google's certificate revocation list at https://android.googleapis.com/attestation/status, and verifiers check that list. The window between "leaked keybox shows up in a community feed" and "Google revokes the leaf serial" runs 6 to 31 days based on the historical data I looked at.
So the keybox is a subscription you renew. Two public GitHub feeds publish fresh leaked keyboxes:
KOWX712/Tricky-Addon-Update-Target-Listkeyboxbranch (was primary in 2025, has slowed)Yurii0307/yurikey(more frequently updated in 2026, has been my primary)
A module called Tricky Addon provides a WebUI to manage the keybox file, with a Fetch button meant to pull from these feeds with CRL validation. On my setup the WebUI's network access was broken in the WebView and the button returned "Please check your internet connection" with the device online, so at first I rotated by hand: fetch and verify on a PC, then adb push to the phone.
# Fetch + decode
curl -sL https://raw.githubusercontent.com/KOWX712/Tricky-Addon-Update-Target-List/keybox/.extra \
| xxd -r -p | base64 -d > keybox.xml
# Sanity check - expect 6 BEGIN CERTIFICATE blocks (3 per chain, ECDSA + RSA)
grep -c BEGIN.CERTIFICATE keybox.xml
# Verify the leaf is not on Google's CRL before relying on it
SERIAL=$(awk '/BEGIN CERT/,/END CERT/' keybox.xml | sed -n '1,/END CERT/p' \
| openssl x509 -noout -serial 2>/dev/null | cut -d= -f2 | tr A-Z a-z)
curl -s https://android.googleapis.com/attestation/status | grep -ic "$SERIAL"
# 0 = live, install. 1+ = revoked, do not bother.
# Install + reboot
adb push keybox.xml /sdcard/Download/keybox.xml
adb shell 'su -c "cp /sdcard/Download/keybox.xml /data/adb/tricky_store/keybox.xml"'
adb rebootThat manual loop got old, so I moved to Enginex0/tricky-addon-enhanced, a Rust daemon that does the fetch, CRL check, and rotation on its own. It needs a newer APatch, which I have since moved to. The curl one-liner above is now just the fallback for pinning a specific keybox by hand.
The security patch date matters and trips people up. The patch date claimed by your spoofed device fingerprint (PIF auto-rotates a Pixel Canary build) must match the patch date in your Tricky Store config exactly. If PIF rotates to a fingerprint with patch 2026-04-05 and your security_patch.txt still says 2026-03-05, Google's verifier sees an inconsistent chain and rejects it. The keybox passes CRL but the verdict comes back BASIC. "I had DEVICE last week and now I don't" is almost always this.
Per-app behaviour#
The integrity layer is one problem. Each banking-style app then layers its own checks on top, and they don't agree with each other.
| App | Extra checks | Where it has to live |
|---|---|---|
| Revolut | integrity verdict only | anywhere |
| McDonald's | installer == com.android.vending, refuses work profile | user 0 |
| Erste George | commercial RASP, SIGABRT in user 0 | user 10 (work profile) |
Revolut is the easiest. Once the integrity stack works, Revolut accepts the verdict and logs in. Done.
McDonald's does two checks Revolut does not. First, getInstallerPackageName() must return com.android.vending. Aurora Store records itself as the installer (com.aurora.store) by default. There is a "Root installer" mode in Aurora Store -> Settings -> Installer that, once you grant Aurora root permission, installs apps with the Play Store as the recorded installer. Set that and McDonald's stops complaining about installation source. Second, McDonald's checks UserManager.isProfile() and refuses to run in a work profile. So McDonald's lives in user 0.
Erste George (at.erstebank.george, the Austrian Erste Bank app, uses commercial RASP, likely Promon Shield) is the opposite. It crashes on launch in user 0 with a SIGABRT from inside its native library. The crash comes from Promon's TEETESTSUPPORT keystore probe behaving differently in user 0 versus a work profile, since the SELinux context for untrusted_app differs across user contexts and leaks signal to Promon. The same install in user 10 (a Shelter-managed work profile) does not crash and reaches the integrity check normally. Pass the integrity check and you log in.
So I split apps by where they tolerate running. One user 0 profile holds the apps that refuse work-profile context (McDonald's and friends). A Shelter-managed work profile holds George. Aurora Store lives in user 0 because its Root installer hardcodes --user 0 in source, so it always installs to the primary user regardless of which profile you launch it from. For George I install once via adb shell pm install --user 10 -i com.android.vending, then clone if needed.
If George ever breaks again, the s Identity companion app from Erste runs the 2FA signing flow with much weaker integrity checks. Combined with web George at george.at, that is a reliable fallback that does not depend on the keybox at all.
What is NOT load-bearing#
A lot of advice on the internet repeats steps that do not move the verdict. I tested each by toggling it:
- Random
supath in APatch settings. Default is/system/bin/su. Banking apps in my set do not stat this. Nice defense-in-depth, not load-bearing. Zygisk Assistantmodule. Some guides treat it as required. With NeoZygisk +denylist_enforce=1already active, ZA does nothing additional I could measure. Promon Shield in George also detects ZA's companion socket and crashes, so for some apps it is actively harmful.Hide My Applistscope on banking apps. HMA hides packages fromgetInstalledPackages()queries. With APatch'sUmount modulestoggle hiding the mount artifacts at zygote spawn, HMA's package-list hiding is overkill. The apps I tested don't probe for APatch's manager package directly.Play Integrity API Checker(gr.nikolasspyr.integritycheck). microG returns-100to its calls regardless of actual integrity state, because the checker queries with a hardcodedcloudProjectNumber. Useless on microG. Test with the actual app instead.- microG's SafetyNet self-check. Always fails with a 404, Google killed the legacy
androidcheck/v1/attestations/attestendpoint in mid-2025. Apps that matter use the newer Play Integrity API, which works.
The integrity-spoofing community publishes a lot of "everything you must do" lists, and most of them compose into a worse setup than the minimum that works.
The minimum: NeoZygisk + Vector + TEESimulator + a live community keybox + PIF with all flags on + microG with the hardware-attestation block disabled + per-app umount in APatch. The rest is optional and some of it hurts.
What this costs to keep working#
| Cadence | Task |
|---|---|
| automated | Enginex0/tricky-addon-enhanced fetches, CRL-checks, and rotates the keybox on its own; I only step in when something breaks |
| ~once/month | PIF rotates the Pixel fingerprint to a fresh Canary build and the patch date shifts; update security_patch.txt to match |
| ~twice/year | a microG update lands in their F-Droid repo and Droidify auto-updates it |
The microG update has not broken anything for me in the year I have been running this. The whole thing only stops working if Google revokes the entire leaked-OEM-keybox root certificate family (f92009e853b6b045) wholesale rather than per-leaf serial. That has not happened in four years. If it does, software-only DEVICE-grade integrity on a custom ROM is dead for everyone outside Pixel hardware, and I switch to web George + s Identity full-time.
The full step-by-step procedure (modules, URLs, configuration files, per-app fixes) is the LineageOS + microG docs page. This post is the narrative. If anyone wants the recipe and not the story, the modules are easy to find: NeoZygisk, Vector, TEESimulator, and Tricky Addon or Enginex0/tricky-addon-enhanced for the keybox/integrity stack; microG Installer Reborn for the microG-as-system-priv-app dance on stock LineageOS; APatch for root; Aurora Store for installs; Shelter for the work profile. The microG toggle to flip is the one mentioned above. The rest is reading.