rn-cr-cedula-reader
v1.0.0
Published
React Native module for scanning Costa Rica Cédulas de Identidad. Detects PDF417 and QR codes from the device camera, decrypts PDF417 binary payloads with the official 17-byte XOR key, and returns structured identity fields.
Maintainers
Readme
rn-cr-cedula-reader
React Native module for scanning Costa Rica Cédulas de Identidad.
Detects whether the barcode is a PDF417 (back of cédula) or a QR code (front), then applies the correct resolution strategy:
| Side | Format | Strategy |
|------|--------|----------|
| Back | PDF417 | XOR-decrypt binary payload locally with 17-byte rotating key → structured fields |
| Front | QR Code | Validated TSE URL → fetch() to consulta.tse.go.cr → parse response → structured fields |
Both paths return the same CedulaData type.
PDF417 decryption algorithm ported from:
Installation
npm install rn-cr-cedula-readerPeer dependency
npm install react-native-vision-camera@^4Only needed if you use
CedulaCameraoruseCedulaScannerfrom the/camerasubpath. TheresolveIdresolver in the main entry has no native dependencies.
iOS
cd ios && pod installAdd to Info.plist:
<key>NSCameraUsageDescription</key>
<string>Camera access is required to scan your Cédula de Identidad.</string>Android
Add to AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />Package structure
This package ships two entry points to keep the resolver bundle-size friendly:
| Entry | Import | Contents |
|-------|--------|----------|
| rn-cr-cedula-reader | main | resolveId, CedulaData, low-level decoder utilities — no native deps |
| rn-cr-cedula-reader/camera | subpath | CedulaCamera, useCedulaScanner — requires react-native-vision-camera |
Quick Start
Option A — Async resolver (BYO camera)
Use your existing barcode scanner to get the raw value, then call resolveId:
import { resolveId } from 'rn-cr-cedula-reader';
import type { CedulaData } from 'rn-cr-cedula-reader';
// code.value + code.type come from react-native-vision-camera's useCodeScanner,
// or any other barcode SDK
const barcodeType = code.type === 'pdf-417' ? 'PDF417' : 'QR_CODE';
try {
const data: CedulaData = await resolveId(code.value, barcodeType);
console.log(data.cedula, data.nombres, data.primerApellido);
} catch (err) {
console.error('Scan failed', err);
}Option B — Drop-in camera component
import { CedulaCamera } from 'rn-cr-cedula-reader/camera';
import type { CedulaData } from 'rn-cr-cedula-reader';
export function ScanScreen({ navigation }) {
function handleResult(data: CedulaData) {
console.log(data);
navigation.goBack();
}
return (
<CedulaCamera
onResult={handleResult}
onCancel={() => navigation.goBack()}
/>
);
}Option C — Hook (custom camera UI)
import { useCedulaScanner } from 'rn-cr-cedula-reader/camera';
import { Camera } from 'react-native-vision-camera';
export function CustomScanScreen() {
const { status, data, device, codeScanner, reset } = useCedulaScanner({
onResult: (data) => console.log(data),
rescanDelay: 2000,
});
if (!device) return null;
return (
<Camera
style={{ flex: 1 }}
device={device}
isActive={status === 'scanning' || status === 'resolving'}
codeScanner={codeScanner}
/>
);
}API Reference
resolveId(raw, type) — main entry
import { resolveId } from 'rn-cr-cedula-reader';
async function resolveId(raw: string, type: BarcodeType): Promise<CedulaData>| Param | Type | Description |
|-------|------|-------------|
| raw | string | Raw barcode value from the scanner |
| type | 'PDF417' \| 'QR_CODE' | Barcode format |
PDF417→ XOR-decrypts locally, no network. Returns immediately.QR_CODE→ Validates the TSE signed URL, fetchesconsulta.tse.go.cr, parses the HTML table.
Throws on network errors, invalid URLs, or unsupported barcode types.
CedulaData
interface CedulaData {
cedula: string; // 9-digit national ID number
nombres: string; // Given name(s)
primerApellido: string; // First surname
segundoApellido?: string; // Second surname
fechaNacimiento?: string; // DD/MM/YYYY (QR) or YYYY-MM-DD (PDF417)
sexo?: string; // 'M' | 'F' — PDF417 only
conocidoComo?: string; // Nickname / known-as — QR/TSE only
fechaExpiracion?: string; // DD/MM/YYYY (QR) or YYYY-MM-DD (PDF417)
source: 'QR_TSE' | 'PDF417';
}<CedulaCamera /> — /camera subpath
Drop-in full-screen camera component with permission handling.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| onResult | (data: CedulaData) => void | required | Called on successful scan + resolve |
| onCancel | () => void | — | Called when user taps Cancel |
| style | ViewStyle | — | Container style override |
useCedulaScanner(options?) — /camera subpath
import { useCedulaScanner } from 'rn-cr-cedula-reader/camera';
const { status, data, error, device, codeScanner, reset } = useCedulaScanner({
rescanDelay: 2000, // ms cooldown between scans (default: 2000)
onResult: (data) => {}, // called on each successful resolve
});| Return field | Type | Description |
|-------------|------|-------------|
| status | ScannerStatus | 'idle' \| 'requesting_permission' \| 'permission_denied' \| 'scanning' \| 'resolving' \| 'success' \| 'error' |
| data | CedulaData \| null | Resolved identity data when status === 'success' |
| error | string \| null | Error message when status === 'error' |
| device | CameraDevice | Pass to <Camera device={device} /> |
| codeScanner | CodeScanner | Pass to <Camera codeScanner={codeScanner} /> |
| reset | () => void | Returns to 'scanning' state |
Low-level utilities — main entry
import { decodeScan, decryptPDF417Payload, parseCedulaFields, hexStringToBytes } from 'rn-cr-cedula-reader';
// XOR-decrypt a byte array with the 17-byte rotating CR cédula key
decryptPDF417Payload(dataBytes: number[]): string
// Parse the decrypted string into structured fields
parseCedulaFields(decrypted: string): CedulaFields
// Convert "27 30 04 A0" or "273004A0" → [0x27, 0x30, 0x04, 0xA0]
hexStringToBytes(hex: string): number[]
// Route by type: PDF417 → XOR decrypt, QR_CODE → raw passthrough
decodeScan(rawValue: string, type: BarcodeType): ScanResultHow PDF417 decryption works
The PDF417 on the back of the CR cédula is XOR-encrypted with a 17-byte rotating key:
KEY = [0x27, 0x30, 0x04, 0xA0, 0x00, 0x0F, 0x93, 0x12,
0xA0, 0xD1, 0x22, 0xE0, 0x03, 0xD0, 0x00, 0xDF, 0x00]For each byte b at position i:
decrypted[i] = b XOR KEY[i % 17]Printable ASCII (0x20–0x7E) and Latin-1 extended characters (0xA0–0xFF, covering accented letters like Á É Í Ó Ú Ñ) are preserved. Control characters become spaces. The decrypted string is then tokenised to extract identity fields.
How QR resolution works
The QR code on the front of the CR cédula encodes a signed TSE URL:
https://www.consulta.tse.go.cr/consultacedula/Cedula?cedula=XXXXXXXXX&sol=...&sign=...resolveId validates the hostname, extracts the cedula parameter, fetches the page, and parses the Bootstrap <th>/<td> table for: Nombre, Primer Apellido, Segundo Apellido, Fecha de Nacimiento, Conocido Como, Fecha de Vencimiento.
Known Limitations
PDF417 field parsing is heuristic. Token layout can vary between cédula print generations. Validate against real samples in your QA environment. Call parseCedulaFields directly if you need custom extraction logic.
QR resolution requires network. The TSE endpoint must be reachable. Offline or slow connections will produce a rejected promise.
Contributing
Issues and pull requests are welcome at github.com/Quality-XP-Development-SESSA/rn-cr-cedula-reader.
License
MIT — Copyright (c) 2026 QXD — Quality XP Development
Decryption algorithm derived from lateraluz/Python-Datos-Cedula-Costa-Rica (GPL-3.0).
