Skip to main content

2 posts tagged with "nfc"

View All Tags

What It Actually Takes to Integrate the Romanian ID Card Over NFC

· 8 min read
Cătălin Toma
Founder, EidKit

This is the third article in our series on Romania's electronic identity card. The previous articles cover the address problem that's quietly breaking KYC flows and what your electronic ID card can and can't sign.

If you have integrated an electronic passport or another chip-based identity document before, you will enter this project with a reasonable set of assumptions. Most of them are partially wrong for the Romanian CEI.

It is not that the ICAO standards do not apply — they do, as a starting point. The problem is that the CEI is a national identity card with country-specific extensions that appear nowhere in complete public documentation. What follows is a map of the terrain based on complete implementations for both Android and iOS, tested against real CEI cards.


What You Already Know — and What Still Applies

Any ICAO-based electronic identity card uses PACE (Password Authenticated Connection Establishment) to establish a secure channel before any data can be read. The CEI does the same, using the CAN code — 6 digits printed on the front of the card — as the password.

The result of PACE is a Secure Messaging (SM) channel that wraps all subsequent APDU commands. Any command sent raw after the channel is established is rejected by the card — standard behaviour, not specific to the CEI.

Passive authentication works on familiar principles on both platforms: the chip contains a Security Object Document that chains SHA-256 hashes of the data groups up to MAI's CSCA root certificate. The verification looks different depending on the platform, but the principle is the same:

On Android, jMRTD handles SOD parsing. Chain verification is done manually against the CSCA certificate bundled with the SDK:

val sod = SODFile(sodRaw.inputStream())
val dsc = sod.docSigningCertificate
val csca = assets.open("csca_romania.der").use {
CertificateFactory.getInstance("X.509").generateCertificate(it) as X509Certificate
}
dsc.verify(csca.publicKey) // throws if invalid

Passive authentication must always run before trusting any data read from the card. An engineer with ICAO document experience will be comfortable up to this point. This is roughly where the familiar terrain ends.


Where the Assumptions Start to Break

The Chip Has Four Applets, Not One

Standard ICAO travel documents have a relatively predictable applet structure. The CEI does not follow the same pattern. The chip contains four applets with distinct roles:

AppletRole
AID1 / National AppPACE entry point, hosts security parameters
ICAO LDSPhoto, digitised handwritten signature, SOD for passive authentication
EDATAPersonal data: name, CNP, home address
GenPKIKeys and certificates for active authentication and signing

The ESIGN applet exists on the chip and appears in some reference documents. It is not used. Signing goes through GenPKI, via a command different from what you would assume from reading the standards.

Each applet follows its own selection and authentication flow. You do not select an applet and read what you need.

Two Phases, Different Requirements

Reading data from the CEI splits naturally into two phases:

Phase 1 — CAN only: accesses data available without a PIN — the holder's photo, the digitised handwritten signature, and the data needed for passive authentication. This phase uses the standard ICAO applet.

Phase 2 — CAN + 4-digit PIN: accesses the full personal data from the EDATA applet, including the home address — which is no longer printed on the physical card.

The Order of Operations Before PACE Is Not Documented — and Matters

What you must do before PACE depends on what you want to do after PACE, and the rules are asymmetric depending on the usage scenario. Phase 1 requires different preparation from Phase 2 and GenPKI. If the preparation is wrong for the given scenario, failures appear at unexpected points with error codes that do not indicate the real problem.

The situation differs between the two platforms for architectural reasons:

The Android NFC stack starts in MF context — no AID is pre-selected. This is the correct state for Phase 1: PACE works directly in MF context, after which SM SELECT ICAO gives access to DG2/DG7/SOD.

// Phase 1: no pre-selection needed — Android stack starts in MF context
isoDep.timeout = 20_000 // default timeout is insufficient for CEI

val paceResult = ps.doPACE(canKey, paceOid, paceParams, null)
val wrapper = paceResult.wrapper
// all commands from here go through the wrapper

// Phase 2: SELECT AID1 before PACE — chip requires AID1 context for EDATA/GenPKI

The asymmetric pre-PACE preparation behaviour is not documented anywhere — it was discovered by elimination on both platforms.


Data Formats: Where the Romanian Implementation Diverges

DG1 Is Not MRZ

This is where code that works perfectly for passports breaks completely, on both platforms. The identity data returned by the EDATA applet is not in the MRZ format that standard ICAO libraries parse — it is in a Romanian implementation-specific ASN.1 format, with correctly encoded diacritics and differently structured fields:

SEQUENCE (0x30)
[0] (0x80) lastName UTF-8 e.g. "TOMA"
[1] (0x81) firstName UTF-8 e.g. "CĂTĂLIN" (diacritics preserved)
[2] (0x82) sex UTF-8 "M" or "F"
[3] (0x83) dateOfBirth UTF-8 DDMMYYYY
[4] (0x84) cnp UTF-8 13 digits
[5] (0x85) nationality UTF-8 "ROU"

jMRTD's DG1File cannot parse this format. A custom parser is required:

// jMRTD won't help here — Romanian-specific ASN.1 format
val tags = parseContextTags(dg1Bytes) // custom parser
val lastName = tags[0x80]?.toString(Charsets.UTF_8)
val firstName = tags[0x81]?.toString(Charsets.UTF_8) // diacritics are correct
val cnp = tags[0x84]?.toString(Charsets.UTF_8)

The format is not publicly documented — it was determined by direct inspection of bytes returned by the card.

The Two Cryptographic Keys in GenPKI

GenPKI contains two distinct keys, on different elliptic curves, with different internal signing behaviours:

OperationPINInternal behaviour
Active authentication4 digitsKey on secp384r1, reference 0x81
Document signing6 digitsKey on brainpoolP384r1, reference 0x8E

Confusing them produces an incorrect signature with no error message indicating the cause. The behaviour is identical on Android and iOS — this is a chip constraint, not a platform one.


Things That Break Before You Reach Business Logic

The cryptographic provider must be explicitly registered in the correct order before any chip operation. Wrong order produces silent failures:

Security.removeProvider("BC")
Security.insertProviderAt(BouncyCastleProvider(), 1)

Android 13+ changed the API for NFC tag interception. If you support older Android versions, you handle two variants with slightly different behaviour.

PIN counter query does not work in SM mode, on either platform. There is no way to query remaining attempts before sending the actual PIN. You handle SW=63CX in the VERIFY response (X = attempts remaining) and SW=6983 for a blocked card — a detail that directly affects application UX.


We write about the Romanian CEI — its capabilities, its integration challenges, and the regulatory context around it. If a topic here is relevant to something you're building, feel free to reach out.

Your New Romanian ID Card Has Your Address. Your Bank Doesn't Know How to Read It.

· 7 min read
Cătălin Toma
Founder, EidKit

This is the first article in our series on Romania's electronic identity card. The second covers what the 2024 electronic signature law means for the CEI.

Something quietly changed when Romania rolled out its new electronic identity card — the Carte Electronică de Identitate, or CEI. The home address disappeared from the physical card. No more printed street, number, city, county on the back. That information is now stored exclusively on the chip inside the card, readable only via NFC or a card reader.

In theory, this is an upgrade. The address can be updated electronically when you move, without needing to reissue the card. In practice, it has created a slow-motion crisis that is now visibly breaking down.


The Problem in One Sentence

Millions of Romanians now carry an ID card that legally contains their home address — but cannot hand it to a bank teller, notary, or civil servant in a way they can read it.

As of this week, the Romanian government has logged over 300 formal complaints in the "Passport and Identity Card" category alone on its fara-hartie.gov.ro platform. The most reported issue by far: the missing printed address. Banks, notaries, schools, ANAF offices, and local authorities are all asking for a separate adeverință de domiciliu — a paper certificate proving the address that is already, technically, on the document they are holding.

One person described arriving at a notary for a property transaction and being turned away because the CEI "was not sufficient to prove domicile." Another went to open a bank account, same story. A 34-year-old recounted: "I got the electronic ID because I understood it was more modern and secure. Nobody told me I'd need a separate certificate every time I have to prove my address."

This is what happens when infrastructure moves before institutions are ready to use it.


What the Government's Fix Looks Like

To its credit, the government has moved quickly. On March 25, 2026 — two days ago — civil registry offices were instructed to look up applicants' addresses themselves in the national database, rather than conditioning service on a physical document.

Banks have received direct database access to the population registry and, according to the government announcement, no longer need to ask for the certificate.

For notaries, a similar mechanism is being tested.

And for everyone else — citizens who need to show address proof somewhere that doesn't have database access yet — the Ministry of Internal Affairs has launched a mobile app called RoCEIReader. You tap your card, enter the 6-digit CAN code and your 4-digit PIN, and the app reads the address off the chip and lets you save it as a PDF.

It is available for Android. The iOS version is "coming soon."

The shape of this solution

The government's answer to "institutions can't read the chip" is a consumer app for citizens to read the chip themselves and produce a PDF. That PDF is then presented to the institution that couldn't read the chip.

The problem has been partially converted from a technical integration challenge into paperwork — digital paperwork, but paperwork. It works, and it is better than nothing. But it illustrates the gap between what the CEI is — a cryptographically secure NFC smart card with a verified, government-signed dataset — and what most systems are currently prepared to do with it.


The Options for Reading the Address

For anyone building software that needs a verified home address in Romania, the transition has real consequences. The old workflow — ask the user for a scan of their ID card, OCR the address from the back — no longer works. The address is not on the back.

The alternatives, roughly in order of robustness:

Government database lookup Banks have been given direct access to the DGEP population registry. Clean, no NFC required, no user interaction beyond a CNP. Access requires a formal agreement with the government authority and is not available to arbitrary private companies on request.

NFC chip read The card is read directly using the CAN code printed on its front face. This gives you the address as the government has it — cryptographically signed, verifiable against the Ministry's certificate chain, no dependency on a third-party database. The address lives in the card's EDATA applet, behind a PACE secure channel and a 4-digit PIN. Reading it correctly requires handling some Romanian-specific data formats that standard ICAO libraries do not cover out of the box.

User-produced certificate The workaround the government is now facilitating via RoCEIReader. Legally valid. Introduces a manual step for the user, a 6-month validity window on the certificate, and friction at exactly the point where onboarding flows tend to lose people.


The Larger Pattern

The address issue is the most visible symptom, but the CEI is capable of considerably more than any institution has caught up to yet.

The chip contains biometric data, a face photo, and two cryptographic keys backed by MAI-issued certificates. One key is for advanced electronic signatures — under Law 214/2024, a document signed with this certificate carries the same legal weight as a handwritten signature. The other is for active authentication: a challenge-response proof that the chip is genuine and not cloned.

And yet ANAF's own tax filing platform rejects the CEI's signature. It only accepts signatures from separately purchased qualified certificates sold by commercial providers. The card grants you a legally valid signature. The government's own portal won't accept it.

The card is ahead of the ecosystem. The ecosystem is catching up, institution by institution. Banks have caught up on address verification. Notaries are close. ANAF has not caught up on signatures. The same pattern will repeat for every institution that needs to interact with these cards over the next two to three years.


What Reading the Chip Actually Involves

For the technically curious: the CEI chip runs the PACE protocol (Password Authenticated Connection Establishment) using AES-256 to establish a secure channel before anything is readable. After the channel is open, reading personal data requires selecting the correct applet, completing PIN verification, and parsing the response in a Romanian-specific ASN.1 format that is not the same as the ICAO MRZ format most libraries expect.

Passive authentication — verifying that the data on the chip is signed by MAI and hasn't been tampered with — should always run before trusting anything read from the card. It chains from the data groups through the document signing certificate up to the Ministry's CSCA root.

None of this is exotic. But it is specific, and the specifics matter. The card is not something you can integrate with by reading the standard ICAO documentation and adapting a passport reader. The Romanian implementation has its own applet structure, its own data formats, and its own sequence requirements that are not publicly documented in a complete way anywhere.


From July 2025, the CEI became the only identity card model being issued nationally. Every identity document issued in Romania from here forward contains a chip the bearer cannot show to most institutions in a form they can read.

That gap will close gradually. The question for anyone building in this space is how long they are willing to wait for it to close, and whether the interim solution — user-produced PDF certificates of what's already on the card — is acceptable friction for their product.



We write about the Romanian CEI — its capabilities, its integration challenges, and the regulatory context around it. If a topic here is relevant to something you're building, feel free to reach out.