The invisible library inside every UPI app
Every UPI app in India — GPay, PhonePe, BHIM, your bank's app — must embed a component it does not compile: the NPCI Common Library (CL). It draws the PIN pad, scrambles your PIN, holds your UPI Lite wallet, and attests your device. We took apart 17 app versions from 2016 to 2026 to reconstruct what it does and how it changed. Along the way we found that every CL signer certificate in the corpus was already expired when the app shipped — a finding Nemo (@captn3m0) hit independently; he found it, we followed. The full evidence kit is public so you can verify every claim yourself.
What the CL actually is
UPI rules require every app to take its PIN-entry and PIN-security component from NPCI,
not build their own. That component lives inside the APK under
org.npci.upi.security.pinactivitycomponent. It draws the keypad you type your
UPI PIN on, encrypts the PIN into a payment-network cryptogram, and — since 2022 — holds
your UPI Lite wallet balance on-device. Nothing else in the app is allowed to see your
PIN. It is, functionally, a single point of trust that a decade of UPI security sits on.
Four eras, from 17 autopsies
- 2016 — the WebView SDK. BHIM 1.0 embeds a whole NPCI app shell
(
CL_VERSION 2.0): a JavaScript bridge, an encrypted web bundle, OTP rules. The "library" was practically an app inside an app. - 2020–2022 — the Java component. Rebuilt as a real library
(
2.3), componentized, obfuscated by R8. UPI Lite moved into it in 2022: a SQLite wallet with its own key-rotation and balance rules. - 2023–2025 — the freeze. GPay and PhonePe ship the identical frozen
2.3-v1.8for roughly three years. One component, zero published changelog, riding inside millions of app updates. - 2025–26 — native + server-side. PSPs swap the Java CL for a native
binary (
libSecureComponent.so: device attestation, secure-element hooks, "native defender" service). And NPCI's Consolidated Circular (Sep 2026) adds a Server-Side Common Library hosted at NPCI — for the first time the CL's monopoly ends, and NPCI itself becomes part of your payment's critical path.
The certificate finding
The CL ships with X.509 certificates that sign its payloads and prove the component to NPCI's switch. We pulled every one from the corpus and checked validity. All three distinct signers were already expired when the app containing them shipped:
| Shipped in | Signer | Validity | State at ship |
|---|---|---|---|
| BHIM 1.0 (2016) → BHIM 2.3.1 (2020) | upinpci ← IDRBT CA 2014 | 31 Mar 2016 → 31 Mar 2018 | expired before most of its own shipping window |
| GPay 210 (Jan 2023) → PhonePe 25.09 (Sep 2025) | upinpci ← IDRBT CA 2014 | 19 Jan 2022 → 19 Jan 2024 | expired Jan 2024; shipped ~20 more months |
| GPay & PhonePe current builds (Oct 2026) | upinpci ← IDRBT Sub CA 2022 | 20 Sep 2023 → 19 Sep 2025 | expired ~13 months |
| GPay & PhonePe current builds (eRupee signer) | psosigner.npci.org.in ← DigiCert EV | 1 Sep 2023 → 1 Oct 2024 | expired ~24 months |
Does an expired cert break your payment? No — the apps treat these as signing/identity material, not TLS chain anchors, and nothing in the observed flow enforces expiry. That's also the point: there is no visible hygiene, no rotation story, and no consumer-visible attestation of the component holding your PIN. Nemo published the independent discovery and follow-ups (including the eRupee signer) — @captn3m0 on X.
Verify it yourself
Every claim above re-derives from the evidence kit: the full report, the feature matrix (17 samples × 28 markers with match evidence), the RASP survey across 5 apps, the extracted certificates, per-sample SHA-256 hashes, and the exact pipeline scripts. No APKs are redistributed (they're copyrighted); the kit carries hashes and source links so you can fetch the same files and confirm byte-for-byte:
# 1. fetch a UPI app APK (APKPure, apkeep, or any source)
apkeep -a com.phonepe.app -d .
# 2. confirm it's the same file we dissected
sha256sum com.phonepe.app*.apk # compare with data/hashes.txt
# 3. unpack and look at the CL signer certificate
unzip -o com.phonepe.app*.apk -d pp
openssl x509 -in pp/signer-upi.crt -noout -subject -dates -fingerprint -sha256
# 4. read the CL's own version constants
jadx --single-class org.npci.upi.security.pinactivitycomponent.CLConstants \
--no-res -d out pp/classes*.dex
grep -E "VERSION" out/**/CLConstants.java
Method note: 12 samples came from the archive.org apkarchive item (fetched via an
Indian vantage node), 5 current builds from APKPure. Read-only analysis; no apps were run;
nothing in the kit is user data.
Read more
- The invisible library — plain-language page (what the CL is, installed-app checks, MASF)
- GPay's expired UPI certificate, ELI5 (why Google distrusts eMudhra in hours but ships expired UPI certs for years)
- GitHub: CashlessConsumer/npci-cl-evolution (report, data, certs, scripts)