DEV Community

Cover image for Biometrics + PIN Fallback: How FinLedger Secures Keys Without a Backend Server
DEVANSHU PATIL
DEVANSHU PATIL

Posted on

Biometrics + PIN Fallback: How FinLedger Secures Keys Without a Backend Server

Biometrics + PIN Fallback: How FinLedger Secures Keys Without a Backend Server

When someone hands their phone to a friend to show a photo, the last thing they want is that friend casually opening their personal finance app and browsing their account balances or net worth.

Mobile authentication is the front door of personal privacy.

Yet many Android apps implement authentication with alarming vulnerabilities:

  • Storing a plaintext 4-digit PIN in unencrypted SharedPreferences.
  • Using weak, unsalted MD5 or SHA-256 hashes vulnerable to instant precomputed rainbow table attacks (a 4-digit numeric PIN has only 10,000 possible combinations, which a modern smartphone CPU can crack in less than 2 milliseconds).
  • Using vulnerable string comparison methods (pin == inputPin) susceptible to CPU execution side-channel timing attacks.

Here is how FinLedger architected its authentication subsystem during Hardening Sprint H8, combining hardware-backed Android BiometricPrompt with 50,000-iteration salted PBKDF2 PIN hashing and constant-time verification.

The Problem With 4-Digit PINs

A 4-digit numeric PIN has a fundamental mathematical limitation: the keyspace is tiny.

From 0000 to 9999, there are exactly 10,000 possible passwords. If an attacker extracts a standard SHA-256 hash of a 4-digit PIN:
$$\text{SHA-256}("1234") = \text{03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4}$$

A single CPU core can compute all 10,000 SHA-256 hashes in approximately 1.8 milliseconds. Without proper cryptographic key stretching and salting, a 4-digit hash provides virtually zero protection against an extracted database.

flowchart TD
    subgraph WeakHashing["The Fast Hash Vulnerability"]
        direction TB
        WeakPIN["4-Digit PIN (Only 10,000 Possible Combinations)"] --> FastAlgo["Standard SHA-256 / MD5 Hash"]
        FastAlgo --> FastCrack["Cracked in under 2 milliseconds via Brute-Force!"]
    end
    subgraph HardenedPBKDF2["FinLedger PBKDF2 Key Stretching"]
        direction TB
        PINInput["User 4-Digit PIN"] --> Salt["16-Byte Cryptographically Secure Salt (SecureRandom)"]
        Salt --> PBKDF2["PBKDF2 with HMAC-SHA256:
50,000 Sequential Iterations"]
        PBKDF2 --> HardenedHash["Computationally Expensive Derived Hash:
Defeats GPU / ASIC Brute-Force Exhaustion"]
    end

The Solution: Salted PBKDF2 with 50,000 Iterations

To secure numeric PINs, FinLedger implements Password-Based Key Derivation Function 2 (PBKDF2) combined with HMAC-SHA256:

  1. Unique 16-Byte Random Salt: When the user configures their PIN, the SecurityManager generates a cryptographically random 16-byte salt using java.security.SecureRandom. This ensures that even if two users choose the identical PIN (1234), their stored hashes are completely different, defeating precomputed rainbow tables.
  2. 50,000 Iteration Stretching: PBKDF2 applies a pseudo-random function sequentially 50,000 times. This intentionally introduces a 30-millisecond computational delay for a single verification. For a legitimate user entering their PIN once, 30ms is completely imperceptible. But for an attacker attempting to brute-force all 10,000 combinations, the total attack time increases from 1.8 milliseconds to over 5 minutes of intensive computation.

Eliminating Timing Attacks with Constant-Time Comparison

In standard Java/Kotlin, comparing two strings using stringA == stringB or stringA.equals(stringB) uses an early-exit comparison: it checks characters one by one and returns false the microsecond it finds a mismatch.

In high-security environments, an attacker measuring CPU instruction cycles can deduce which character was incorrect based on how many nanoseconds the comparison took (a timing side-channel attack).

FinLedger verifies PIN hashes using constant-time verification:

MessageDigest.isEqual(storedHashBytes, computedHashBytes)
Enter fullscreen mode Exit fullscreen mode

MessageDigest.isEqual always compares every single byte in the array regardless of where a mismatch occurs, ensuring that the execution time is strictly identical whether the first byte or the last byte differed.

Progressive Rate Limiting and Lockouts

To stop automated brute-force attacks on the physical device itself, FinLedger enforces a progressive lockout state machine:

stateDiagram-v2
    [*] --> Unlocked
    Unlocked --> Locked : App Backgrounded / Timeout
    Locked --> Unlocked : Valid Biometric or PIN
    Locked --> AttemptFailed : Incorrect PIN
    AttemptFailed --> Locked : Failed Attempts < 5
    AttemptFailed --> SoftLockout : 5 Failed Attempts (30s Lockout)
    SoftLockout --> HardLockout : 10 Failed Attempts (60s Lockout)
    HardLockout --> MaximumLockout : 15 Failed Attempts (300s Lockout)
  • 5 Failed Attempts: Temporary 30-second lockout.
  • 10 Failed Attempts: Temporary 60-second lockout.
  • 15 Failed Attempts: Full 5-minute (300-second) lockout.
  • Lockout counters and expiration timestamps are stored inside Android Keystore-backed EncryptedSharedPreferences, ensuring that restarting the application cannot bypass active lockout countdowns.

The Zero-Lockout Migration Strategy

In early development builds, FinLedger utilized a simpler single-round SHA-256 hash. When we upgraded to 50,000-iteration PBKDF2 in Sprint H8, we faced a critical deployment challenge: how do you upgrade existing user PINs without forcing everyone to reset their accounts?

We engineered a Zero-Lockout Auto-Upgrade Migration:

  1. When a user with an older hash logs in, the SecurityManager verifies the entered PIN against the legacy SHA-256 algorithm.
  2. The instant authentication succeeds, the engine takes the verified plaintext PIN in memory, generates a fresh 16-byte random salt, stretches it through 50,000 iterations of PBKDF2, saves the new hardened hash, and permanently purges the legacy hash.
  3. The user experiences zero friction, and their security is transparently upgraded to modern standards on their very next login.

Android BiometricPrompt Integration

Alongside the hardened PIN, FinLedger integrates directly with the AndroidX BiometricPrompt API:

  • Allows instant authentication using fingerprint sensors or secure 3D face recognition.
  • Uses BIOMETRIC_STRONG crypto-based authentication backed by the hardware TEE (Trusted Execution Environment).
  • If biometric authentication fails or the sensor is dirty, the user seamlessly falls back to the PBKDF2 PIN keypad.

Key Takeaway

A 4-digit PIN can be simple for the user without being easy for an attacker.

By combining hardware-backed biometrics with 50,000 iterations of salted PBKDF2, constant-time verification, and progressive lockout state machines, FinLedger delivers consumer convenience backed by defense-grade security.


FinLedger is a privacy-first, offline-first personal finance application for Android that models bank accounts, credit cards, and cash without double-counting.

Explore FinLedger on Google Play: https://play.google.com/store/apps/details?id=com.devanshufinance.app

Top comments (0)