Skip to content
SV Dev
← Back to the blog

Security · Android · Fintech

Play Integrity Is Locking Out Your Paying Customers

Google's Play Integrity API does not answer whether a device is compromised. It answers a narrower question, and the gap is costing you customers you cannot see.

Jeferson Guardezi
Google's Play Integrity documentation showing the default device verdict with only two outcomes: a certified manufacturer image, or a blank value labelled as signs of attack or system compromise.

A customer writes to support because your app will not open on their phone. That phone has a locked bootloader and an operating system whose every boot is cryptographically verified — stricter, by most measures, than half the handsets your app admits without a second thought. It is refused anyway, because your app asked Google's Play Integrity API whether the device could be trusted. Play Integrity does not answer that question. Its default verdict answers a narrower one: is this a Google-certified manufacturer image. Those are different questions, and the gap between them is turning away people who want to pay you.

The story reached the news in the usual shape: banking apps block hardened Android builds, GrapheneOS is annoyed, Revolut is named. That framing makes it a story about a niche operating system. It is not. Every consumer fintech, marketplace and wallet app on Android ships an integrity gate, almost all of them ship the same one, and it was chosen once by whoever wired up the SDK. That gate decides who is allowed to be your customer.

What is checked here, and what is not

Two kinds of claim follow, and they are not equally solid.

Verified against primary documents on 24 October 2026: every quote from Google's Play Integrity reference and from the Android Open Source Project attestation schema, and the signed allowlist file — 1,452 bytes, 21 key fingerprints, Ed25519 signature checked locally with ssh-keygen -Y verify.

Reported by an interested party and not independently measured: the headline figures. GrapheneOS says roughly 10% of banking apps are unavailable to its users, and that around a dozen banks have begun permitting it. Both numbers are the project's own, published with no methodology or sample, and the crowd-sourced list it links in the same post names four institutions, not a dozen. What Revolut specifically checks is likewise GrapheneOS reading a closed-source app — the outlet that covered it does not say it verified the claims, and Revolut has not commented. None of that touches the mechanism, which is in Google's own documentation. But a number an interested party reports about itself should be read as one.

Post by GrapheneOS dated 23 October 2026 stating that around 10% of banking apps are unavailable and around a dozen have begun permitting it.

GrapheneOS, 23 October 2026. Both figures are the project's own; neither is independently measured. Source: grapheneos.social.

The default verdict has two outcomes, and neither means "compromised"

Google documents the default deviceRecognitionVerdict with exactly two possible values. MEETS_DEVICE_INTEGRITY means "the app is running on a genuine and certified Android device", with hardware-backed proof on Android 13 and higher "that the device bootloader is locked and the loaded Android OS is a certified device manufacturer image". The other value is blank — an empty verdict — which Google documents as "the app is running on a device that has signs of attack (such as API hooking) or system compromise (such as being rooted)".

Read those together. A locked bootloader running a verified non-Google OS satisfies the security property in the first sentence and fails the certification property in the same sentence, so it lands in the second bucket — alongside a rooted phone with a hooking framework attached. Your fraud rule cannot tell them apart, because the API never told it.

No lower rung rescues this. MEETS_BASIC_INTEGRITY sounds like the graceful-degradation tier, but Google defines it as a state where "the device bootloader can be locked or unlocked, and the boot state can be verified or unverified". It is weaker, not narrower: accepting it to let hardened devices in also admits everything the gate exists to exclude.

Google ships a second attestation API that answers the real question

Play Integrity is built on Android's hardware key attestation, and the primitive underneath turns out to be more informative than the product sitting on it. The AOSP schema returns a RootOfTrust structure with four separate fields:

RootOfTrust ::= SEQUENCE {
    verifiedBootKey    OCTET_STRING,
    deviceLocked       BOOLEAN,
    verifiedBootState  VerifiedBootState,
    verifiedBootHash   OCTET_STRING,
}

VerifiedBootState ::= ENUMERATED {
    Verified    (0),
    SelfSigned  (1),
    Unverified  (2),
    Failed      (3),
}

The AOSP key attestation schema showing RootOfTrust with four fields and VerifiedBootState as an enum in which SelfSigned is value 1.

The same company, a different API: RootOfTrust hands an app the boot key, the lock state and a SelfSigned verdict as separate facts. Source: Android Open Source Project attestation schema.

SelfSigned is the rung Play Integrity cannot express: the boot chain verified, under a signing key that is not the manufacturer's. It is distinct from Unverified and from Failed, and it arrives next to the key that did the signing — so the app decides which non-stock keys it trusts instead of inheriting a vendor's list. This API has shipped since Android 8.

Play Integrity (default verdict)Hardware key attestation
Question it answersIs this a Google-certified manufacturer image?Is the boot chain verified, and under whose key?
What comes backMEETS_DEVICE_INTEGRITY, or a blank valueverifiedBootKey, deviceLocked, verifiedBootState, verifiedBootHash
Locked, verified, non-stock keyBlank — identical to rooted and under attackSelfSigned, plus the key, as separate facts
Available sinceAndroid 8, via Google Play servicesAndroid 8, hardware-backed, no Play dependency
Who sets the policyGoogleYou

Nor is this only the argument of the people being blocked. An academic comparison from Johannes Kepler University Linz concludes that the hardware attestation API "provides a more flexible alternative, enabling support for third-party operating systems while maintaining strong security guarantees through hardware-backed key storage", and that Play Integrity's proprietary nature and reliance on Google Play Services "raise anti-competitive concerns".

The rejected customer appears in none of your numbers

This is what makes it a management problem rather than an engineering one. A customer your gate turns away produces no failed transaction: no declined payment, no support ticket in a category anyone reviews, no drop-off on a funnel step you instrumented. The app closed before any of that could exist.

So the cost is real and unmeasured, which is exactly why the gate is never revisited. Every other control in your product has a false-positive rate somebody argues about in a meeting. This one's is unknown, and the people inside it are the only cohort your analytics cannot see.

Which means nobody in your company has asked whether you should admit these users. A dependency answered it.

What the fix costs

The developer guide for the most common hardened Android build states the check in two sentences: enforce that verifiedBootState is either Verified or SelfSigned, and in the SelfSigned case check that verifiedBootKey matches one of the published verified boot keys.

I downloaded that list. It is a 1,452-byte JSON file with 21 key fingerprints, each a 64-character hex string, stamped 20 April 2026, shipped with an OpenSSH signature and a published signer key. Verifying it is one command:

ssh-keygen -Y verify -f allowed_signers -I contact@grapheneos.org \
  -n 'attestation keys' -s attestation.json.sig < attestation.json

It exits 0 with a good Ed25519 signature. I ran it on 24 October 2026.

The attestation compatibility guide instructing apps to accept verifiedBootState Verified or SelfSigned and to match verifiedBootKey against published keys.

The integration, in two sentences, from the developer guide: check one enum, match one hash against a signed 1.4 KB file. Source: grapheneos.org.

Be honest about the size of the work. If your app already does hardware key attestation, admitting these devices is two fields you receive today. If it only calls Play Integrity, the work is adopting the API underneath the one you already call — a sprint with a known shape, against documentation eight years old. It is neither a research project nor an impossibility, which are the two things it is usually called.

Some banks have already shipped it

The crowd-sourced compatibility list at PrivSec.dev carries 907 unique app entries. Two are annotated as having allowlisted the hardened OS through the hardware attestation API — Swissquote and Yuh — and two as officially supporting it: UBS (with UBS key4) and AmeriCU Credit Union.

Entries from a community compatibility list showing Swissquote and Yuh allowlisted via the hardware attestation API and UBS as officially supporting the OS.

Community-maintained compatibility list, read 24 October 2026: two banks annotated as allowlisting via the hardware attestation API, one as officially supporting. Source: PrivSec.dev; GrapheneOS does not vouch for the list's accuracy.

Treat those entries as what they are: user reports. GrapheneOS, which links the list, says plainly that it "does not make any guarantees regarding the list's validity", and no bank named here has told us which attestation API it uses or why. What the list establishes is not a vendor roadmap. It establishes that the thing is shippable, because somebody shipped it.

The same list counts the friction in the other direction: 8 entries where the app cannot be installed from Google Play at all because of Play Integrity, 4 where it runs only if the API is blocked outright, and one — N26 — where Play Integrity blocks new signups while existing accounts keep working. That last one deserves a moment. It is a gate that forbids acquisition and permits retention, the opposite of how anyone would have written the policy on purpose.

The decision, stated as a decision

Three questions, and they belong to a product owner, not to whoever owns the mobile build.

  1. What do we need to know about a device? If the answer is "the boot chain is intact", you are calling the wrong API. If it is "a Google-certified image", say so out loud — that is a commercial position, not a security one.
  2. What is our false-positive policy? Pick a number. "We reject privacy-hardened devices and accept the lost signups" is defensible. "We never looked" is not.
  3. Who owns this check? Name a person. An unowned control drifts with its vendor's defaults, and you find out when a customer tells you.

We see this shape constantly in work that arrives for a production pass: a dependency added for a good reason, wired into a decision nobody wrote down, then treated as settled because it compiles. Same failure as shipping a demo as if it were a product (the Roadster problem), same failure as letting generated code define a boundary nobody reviewed (containing AI-written code). The integrity gate is an unusually expensive instance, because the error is silent and it falls on the customers who care most about securing their own device.

Key takeaways

  • Play Integrity's default device verdict has two outcomes: a certified manufacturer image, or a blank value Google documents as signs of attack or system compromise.
  • A locked bootloader running a verified non-Google OS gets the blank verdict — indistinguishable from rooted and under attack.
  • Hardware key attestation, which Play Integrity is built on, returns verifiedBootState with a distinct SelfSigned value plus the signing key, so the app sets its own policy.
  • The rejected customer generates no transaction, ticket or funnel event, so the gate's cost never reaches a reviewed number — which is why it is never revisited.

FAQ

Does Play Integrity tell my app whether a device is rooted? Not distinctly. Google groups root with other failures in a single blank verdict that also covers any OS which is not a certified manufacturer image. If your rule needs "rooted" specifically, the default verdict cannot give it to you.

Is accepting MEETS_BASIC_INTEGRITY a reasonable compromise? No. Google defines that tier as allowing a bootloader that may be locked or unlocked and a boot state that may be verified or unverified. It is strictly weaker, so it admits what you were trying to exclude.

How many customers would this actually admit? Nobody publishes that number, and that is the point of this post. You can measure it: log the attestation fields you already receive, count the devices that are locked and verified under a non-stock key, and you have the cohort size before you decide anything.

We work with founders anywhere in the world who have a working product built with AI — and the good judgment to want senior eyes on it before real users arrive.

Get your product reviewed

Ready to launch with confidence?

30 minutes with a senior engineer. We'll tell you exactly what stands between your product and production.

Get your product reviewed

30 minutes, no pitch, no pressure. If your product is ready to ship as-is, we'll tell you that too.