~learn.

Aadhaar (UIDAI), the whole design.

A national identity system that has issued 1.45B numbers and answers 90M+ authentications a day in under 200 ms, because checking one person (1:1) and proving nobody enrols twice (1:N) are built as two separate paths.

7 diagrams · 8 sections · revise in ~10 min · Download PDF prints it in the light theme, diagrams included

SQ-03 · Aadhaar (UIDAI) · design sheethimanshukukreja.in/learn/sheets/bonus-03-aadhar-identity-system

At a glance#

Number Value What it drives
Aadhaar numbers 1.45B issued (~1.35B live) the gallery every new enrolment is searched against
Authentications 90M+ / day ~1,040 / s on average
Peak ~3,000 auth / s (3×) modest volume: latency and security are the hard parts
Auth latency < 200 ms network 50 · crypto 30 · lookup 20 · match 80 · overhead 20
Dedup work 600+ trillion matches / day (2014, ~1M enrolments/day) async, 3 ABIS vendors, minutes to days
Brute force ~3,170 years per day of enrolment at 100K matches/s biometric indexing + massively parallel matching
Storage ~257 KB templates / person ≈ 360 TB 7 PB raw images, ~20 PB with 3× replication
Accuracy FNIR 0.035 % · FPIR 0.057 % (2012) fingerprint + iris fusion

Architecture#

DiagramAadhaar on one page: enrolment goes async to dedup, authentication goes sync to a 1:1 match
%% title: Aadhaar on one page: enrolment goes async to dedup, authentication goes sync to a 1:1 match
flowchart TD
  RES@{ icon: "sd:user", label: "Resident<br/>village camp or service point" } --> ES@{ icon: "sd:laptop", label: "Enrolment station · offline-first<br/>10 fingers · 2 iris · photo" }
  RES --> SPD@{ icon: "sd:fingerprint", label: "Certified capture device<br/>encrypts PID with UIDAI public key · signs" }
  ES ==>|"encrypted 3–5 MB packet · HMAC<br/>SFTP or encrypted USB"| RG@{ icon: "sd:building", label: "Registrar<br/>checks structure + signatures" }
  RG ==> DD["CIDR dedup orchestrator<br/>decrypt · extract templates · async"]:::core
  DD ==> AB["3 ABIS vendors<br/>1:N on the full gallery · 2-of-3 vote"]:::service
  SPD --> AUA@{ icon: "sd:building", label: "AUA · bank, telecom, scheme<br/>600+ entities · rate-limited" }
  AUA ==>|"encrypted PID · txn ID · consent"| ASA@{ icon: "sd:network", label: "ASA · ~35 licensed<br/>dedicated leased lines" }
  ASA ==> AU["CIDR auth service<br/>1:1 match · < 200 ms · signed yes/no"]:::hot
  AU --> HSM@{ icon: "sd:key", label: "HSM<br/>decrypt PID · sign response" }
  AU --> DB@{ icon: "sd:database", label: "Resident + template store<br/>2 data centres · encrypted at rest" }
  AU -.-> LOG@{ icon: "sd:log", label: "Auth log · written async<br/>history the resident can see" }
  AB -->|"unique → new 12-digit number"| DB
  class RES client
  class ES,SPD edge
  class RG,AUA external
  class ASA edge
  class HSM,DB,LOG store
DiagramDeduplication: three independent matchers and a 2-of-3 vote (this design)
%% title: Deduplication: three independent matchers and a 2-of-3 vote (this design)
flowchart TD
  CP["CIDR processing<br/>decrypt · templates · cheap demographic check"]:::core --> A1["ABIS 1 · vendor A"]:::service
  CP --> A2["ABIS 2 · vendor B"]:::service
  CP --> A3["ABIS 3 · vendor C"]:::service
  A1 --> CN{"Consensus<br/>fingerprint + iris fusion scores"}
  A2 --> CN
  A3 --> CN
  CN -->|"2 of 3 UNIQUE"| GEN["Generate Aadhaar number<br/>store · print and mail letter"]:::ok
  CN -->|"2 of 3 DUPLICATE"| REJ["Reject<br/>points to the existing Aadhaar"]:::bad
  CN -->|"no two agree"| MAN@{ icon: "sd:users", label: "Manual adjudication" }
  class MAN queue

Key flows#

DiagramFingerprint authentication at a bank, under 200 ms
%% title: Fingerprint authentication at a bank, under 200 ms
sequenceDiagram
  autonumber
  participant D as Capture device
  participant A as AUA (bank)
  participant S as ASA
  participant C as CIDR auth
  participant H as HSM
  D->>D: capture · encrypt PID with UIDAI public key · sign
  D->>A: encrypted PID + Aadhaar or VID
  A->>S: auth request · txn ID · consent
  S->>C: forward over leased line
  C->>H: decrypt PID block
  H-->>C: captured fingerprint
  C->>C: resolve VID · load template · 1-to-1 match
  C->>H: sign response
  C-->>S: y or n · txn ID · timestamp
  S-->>A: signed yes/no
  Note over A,C: no PII, no score · audit row written async, off the 200 ms path
DiagramEnrolment: capture offline, deduplicate at CIDR
%% title: Enrolment: capture offline, deduplicate at CIDR
sequenceDiagram
  autonumber
  participant E as Enrolment station
  participant R as Registrar
  participant C as CIDR
  participant V as ABIS 1–3
  E->>E: capture offline · encrypt · HMAC · operator signs
  E->>R: packet by SFTP or USB, within 30 days
  R->>R: check structure + signatures
  R->>C: upload over secure channel
  C->>C: decrypt · extract templates
  C->>V: search the full gallery, in parallel
  V-->>C: UNIQUE / DUPLICATE / REVIEW from each
  alt 2 of 3 say UNIQUE
    C->>C: generate 12-digit number · store
    Note over C: letter with number + QR printed and mailed
  else 2 of 3 say DUPLICATE
    C-->>R: reject · points to existing Aadhaar
  else no two agree
    C->>C: manual adjudication
  end
  Note over E,V: 3–90 days end to end
DiagramEdge paths: worn fingerprints, and a replayed capture
%% title: Edge paths: worn fingerprints, and a replayed capture
sequenceDiagram
  autonumber
  participant D as Device + AUA
  participant C as CIDR
  D->>C: fingerprint auth, worn prints
  C-->>D: n
  D->>C: another finger, then iris
  alt still no match
    D->>C: OTP to registered mobile, or face with liveness
    C-->>D: y
  end
  Note over D,C: genuine residents get a fallback, never a lower global threshold
  D->>C: yesterday's signed capture, resent
  C-->>D: reject · stale timestamp, txn ID already seen
  Note over C: alert: one device, hundreds of residents, far from home

Data model#

DiagramCore entities
%% title: Core entities
classDiagram
  class Resident {
    +aadhaar : 12 digits
    +demographics : ~2 KB
    +mobile : for OTP
    +vids : 16 digits · revocable
    +biometricLock : bool
  }
  class BiometricRecord {
    +fingerprints : 10 templates · ~100 KB
    +iris : 2 templates · ~100 KB
    +photo : ~50 KB
    +rawImages : ~5 MB archived
  }
  class EnrolmentPacket {
    +packetId : string
    +payload : encrypted · 3–5 MB
    +hmac : bytes
    +operatorSig : biometric
    +gps : coordinates
    +deviceId : string
  }
  class AuaToken {
    +auaCode : string
    +token : unique per AUA
  }
  class AuthTransaction {
    +txnId : string · replay check
    +auaCode : string
    +mode : finger / iris / face / OTP / demo
    +result : y / n
    +at : timestamp
  }
  EnrolmentPacket "1" --> "0..1" Resident : creates
  Resident "1" --> "1" BiometricRecord : has
  Resident "1" --> "many" AuaToken : one per AUA
  AuaToken "1" --> "many" AuthTransaction : history

Lifecycle#

DiagramEnrolment packet state machine
%% title: Enrolment packet state machine
stateDiagram-v2
  [*] --> CAPTURED: encrypted on the laptop
  CAPTURED --> UPLOADED: sync or USB, within 30 days
  UPLOADED --> TAMPERED: bad signature or HMAC
  UPLOADED --> DEDUP_PENDING: queued for ABIS
  DEDUP_PENDING --> ISSUED: 2 of 3 unique
  DEDUP_PENDING --> REJECTED: 2 of 3 duplicate
  DEDUP_PENDING --> MANUAL_REVIEW: no consensus
  MANUAL_REVIEW --> ISSUED: cleared
  MANUAL_REVIEW --> REJECTED: duplicate confirmed
  ISSUED --> [*]: letter mailed
  REJECTED --> [*]
  TAMPERED --> [*]
  class ISSUED ok
  class REJECTED,TAMPERED bad
  class MANUAL_REVIEW queue

Decisions#

Decision Chose Over Because
Two workloads 1:1 sync auth, 1:N async dedup One matching path 200 ms vs minutes to days
Dedup search space Full biometric gallery Demographic blocking alone a fraudster picks a new name, DOB, state
Matchers 3 independent ABIS, 2-of-3 vote One vendor different algorithms catch different cases; no lock-in
What auth returns Signed yes/no + txn ID Score or data nothing to leak, no hill-climbing
Where biometrics decrypt Only in CIDR's HSM At the AUA or ASA no hop before CIDR can read or alter it
Enrolment connectivity Offline-first, encrypted packets Online capture 640,000+ villages, many without internet
Exposing the number VID + per-AUA tokens Raw Aadhaar everywhere services can't correlate a person

When it breaks#

Failure What users see Contain it with
Worn or faded fingerprints first attempt fails other finger → iris → OTP → face
Camp offline for weeks number comes later, nothing lost packets stay encrypted, sync or USB within 30 days
Re-enrolment under a new name rejected, linked to first Aadhaar full-gallery search, 3 ABIS, adjudication
Replayed capture or silicone finger fraud blocked device key + timestamp + txn ID, liveness, OTP, per-device anomaly alerts
DDoS or brute force that AUA throttled rate limit per AUA, ASA IP allow-list, no internet exposure
ABIS backlog grows new numbers take longer async queue, monitored; auth path untouched

Say it in the interview#