Open-source project
AndyQ/NFCPassportReader avatar
AndyQ/NFCPassportReader

NFCPassportReader reads the chip, but the author calls it early days

NFCPassportReader for iOS 13

866 stars336 forksSwiftMIT

At a glance

What is it?
NFCPassportReader is a Swift package that talks to an NFC enabled e-passport over CoreNFC, handling Basic Access Control, secure messaging and passive authentication, with the MRZ key built by hand as the entry point. The author describes it as still early, with real gaps in PACE and JPEG2000 support, and CocoaPods support is on its way out.
Who is it for?
NFCPassportReader suits an iOS team building a passport scanning feature that needs passive authentication and chip data on a device, and that accepts Swift Package Manager, since the CocoaPods path is documented as deprecated and planned to end with Cocoapods becoming read-only at the end of 2026.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 87 days ago.
What is it written in?
Mainly Swift, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

CoreNFC plus a hand-built MRZ key, not a camera and OCR

The job is narrow and unusual: read the chip inside a passport booklet and return the data groups. NFCPassportReader does it with the iOS 15 CoreNFC APIs, which is worth noticing because the repository description still says the package is for iOS 13 while the readme states the package handles reading an NFC enabled passport using iOS 15 CoreNFC APIS. Those two statements point at different deployment targets, so an integrator should confirm which minimum system version the code actually needs rather than trusting the shorter description. The design puts the MRZ key at the centre of the API. To read anything you first construct the key from the passport number, date of birth and expiry date including their checksums, and the dates are in YYMMDD format. Everything else in the library hangs off that one string, which means the caller already knows the values printed on the identity page.

Version 2 replaced the callbacks with Swift concurrency

The main branch and version 2 use Swift Async/Await for communication, and the readme says so in bold: if you need an earlier version, use 1.1.9 or below. That is a hard cut for existing integrations, since the completion block API described later in the usage section is what most tutorials show. Two API shapes are therefore in circulation, and the choice of version decides which one you inherit. This matters more than a typical version bump, because the concurrency change touches every call site. The readme also flags that Cocoapods will continue to receive releases until it changes to read-only at the end of 2026, and asks that Cocoapods be considered deprecated and unsupported in favour of Swift Package Manager. The repository reflects that split in its layout, with a Package.swift file at the root, a NFCPassportReader.podspec for the CocoaPods route, and separate example projects under Examples/Example_SPM and Examples/Example_CocoaPods.

Building the mrzKey from the printed identity page

The readme walks through the key construction with a concrete example, which is the fastest way to understand the format. The layout is passport number, passport number checksum, date of birth, date of birth checksum, expiry date, expiry date checksum:

swift
mrzKey = "12345678898012772508315"

The worked example in the readme uses passport number 12345678, date of birth 27-Jan-1998 and expiry 30-Aug-2025, giving a birth component of 980127 and an expiry component of 250830, each with its own check digit. The traps here are documented rather than guessed at. If your passport number contains a less-than sign, that character has to be included in the MRZ key, and the checksum still works out. The readme shows a two line MRZ example ending in a run of filler characters, where the number 12345678 is padded with an additional <, and the key to use in that case becomes 12345678<870052332507237. Getting this wrong shows up as a specific error: an initial Mutual Authenticate challenge failing with an SW1 code of 0x63, SW2 code of 0x00 and the reason No information given is usually a wrong MRZ key rather than a chip problem.

readPassport takes the key, the tag list and a completion block

With a key in hand, the call itself is short. You create a PassportReader instance, then call readPassport with the key, the data groups you want and a completion block:

swift
passportReader.readPassport(mrzKey: mrzKey, tags: [.COM, .DG1, .DG2], completed: { (error) in
   ...
}

The completion block receives a TagError when something went wrong and nil on success, and after a successful read the reader object holds valid data in its passportMRZ and passportImage fields. The datagroups you can request are COM, DG1, DG2, DG7, DG11, DG12, DG14, which the readme marks as partial, DG15 and SOD. Two options modify the session. A customDisplayMessage callback lets you replace the strings the NFC session reader shows, for instance overriding the initial request to present passport message:

swift
passportReader.readPassport(mrzKey: mrzKey, tags: [.COM, .DG1, .DG2],
    customDisplayMessage: { (displayMessage) in
        switch displayMessage {
            case .requestPresentPassport:
                return "Hold your iPhone near an NFC enabled passport."
            default: 
                return nil
    }, completed: { (error) in
        ...
}

And the useExtendedMode flag raises the number of bytes that can be read in a single call, which some passports need, the readme citing long AA keys on some Australian passports as an example.

What the chip protocol layer covers and where it stops

The feature list is substantial on paper. Basic Access Control is the session key establishment that the MRZ key feeds into, and Secure Messaging protects the data groups once the session is up. On the cryptographic side the package implements Passive Authentication, which checks that the passport is genuine and has not been tampered with, Active Authentication, and Chip Authentication with ECDH DES and AES keys tested and DH DES AES keys implemented but not tested. PACE is present but limited, the readme states that currently only Generic Mapping (GM) is supported, and the to do list names finishing PACE authentication with IM and CAM as unfinished work. Active Authentication also supports a custom challenge, so the app can prove the challenge and response was executed in that session and not replayed, then send the activeAuthenticationSignature to a backend along with the chip data for validation. Passive Authentication needs CSCA certificates in PEM format from a master list, either published by a country or taken from the ICAO PKD repository, and the scripts folder explains how to obtain and build that file.

JPEG2000 portraits are not decoded, and the sample certificate verifies nothing

Two limitations deserve a mention before you commit to the package. The first is image format. The readme states that JPEG2000 images are currently unsupported, and that access to the raw data is available if you need to implement support for those. DG2 is listed among the supported data groups and the feature list mentions reading DG2 in both JPEG and JPEG2000 formats, so the data group can be retrieved, but a JPEG2000 portrait will not arrive as a usable image without your own decoder. The second is the certificate material in the sample app. The masterList.pem file included there is described as purely there to ensure no compiler warnings, and it contains only a single PEM file that was self-generated and will not be able to verify anything. Any passive authentication you rely on needs a real master list built from CSCA certificates.

The author's own caveats about maturity and logging

The readme is unusually blunt about the state of the code, and that candour is the best signal available when there are no tagged releases to inspect. The author writes that this is still very early days, that the code is by no means perfect, that there are still some rough edges, and that there are most definitely bugs. He adds that it reads and verifies his passport and others he was able to test fine, but that your mileage may vary. The same honesty shows up in the logging section, where the readme notes that verbose logging is currently just printing out to the console, with better logging using something like SwiftyBeaver wanted later:

swift
let reader = PassportReader(logLevel: .debug)

For a project handling identity documents, that framing matters more than usual. If you are shipping passport verification, the honest reading is that you are adopting an early implementation whose author expects to keep fixing it, and your own testing burden goes up rather than down. The repository also has a CHANGELOG file and a CODE_OF_CONDUCT.md, but it publishes no GitHub releases, so the changelog and the commit history are where you look for what changed.

Installing through Swift Package Manager or CocoaPods

The recommended path is Swift Package Manager, and the readme describes installation by pointing to the repository URL from the package manager, with no Podfile edits required. The CocoaPods route still exists for existing projects and adds a Podfile entry:

ruby
use_frameworks!
pod 'NFCPassportReader', git:'https://github.com/AndyQ/NFCPassportReader.git'

After that you run the standard install command:

bash
$ pod install

One warning applies to that route: do not use Bitcode, because it is not supported here and has been deprecated by Apple. Beyond installation, the underlying protocol work in this package is based on pypassport, by Jean-Francois Houzard and Olivier Roger, and the author credits that work explicitly. The alternative to using this package is to base a project on that earlier code directly, or to implement against ICAO 9303 Part 11 yourself, which the readme cites for the MRZ rules. Those routes give you full control over the protocol code, at the cost of maintaining the chip interaction, the secure messaging layer and the passive authentication verification yourself.

Editorial conclusion

NFCPassportReader suits an iOS team building a passport scanning feature that needs passive authentication and chip data on a device, and that accepts Swift Package Manager, since the CocoaPods path is documented as deprecated and planned to end with Cocoapods becoming read-only at the end of 2026. It does not suit a product needing full PACE, JPEG2000 portrait extraction, or a guarantee that the code is bug free, because the author states plainly that it is early and rough edged. Verify first on real hardware with your own passports, build the CSCA master list with the scripts folder as the documentation instructs rather than reusing the sample app's self-generated file, and check the CHANGELOG since the repository publishes no tagged GitHub releases.

Frequently asked questions

How do I install NFCPassportReader?

The recommended route is Swift Package Manager, by pointing the package manager at this repository's URL. The CocoaPods route is documented as deprecated and unsupported, with updates continuing until Cocoapods becomes read-only at the end of 2026.

How do I build the mrzKey for Basic Access Control?

It is the passport number, its checksum, the date of birth in YYMMDD format with its checksum, and the expiry date in YYMMDD format with its checksum, concatenated. The readme example for passport 12345678, born 27-Jan-1998 and expiring 30-Aug-2025 is 12345678898012772508315.

Which data groups can NFCPassportReader read?

The supported datagroups are COM, DG1, DG2, DG7, DG11, DG12, DG14, which the readme marks as partial, DG15 and SOD. You pass the list as tags when calling readPassport.

Does NFCPassportReader support PACE?

Partly. The readme states that currently only Generic Mapping (GM) is supported, and the to do list names finishing PACE authentication with IM and CAM as remaining work.

Why does passive authentication fail with the sample app files?

The masterList.pem in the sample app contains a single self-generated PEM file that will not be able to verify anything, and it is there to avoid compiler warnings. You need CSCA certificates in PEM format from a country master list or the ICAO PKD repository, with the scripts folder explaining how to build that file.

Can NFCPassportReader read a JPEG2000 passport photo?

Not as a decoded image. The readme states that JPEG2000 images are currently unsupported, and that access to the raw data is available if you need to implement support for those.

Official sources

  1. AndyQ/NFCPassportReader on GitHub
  2. Issues
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/andyq-nfcpassportreader.svg)](https://hysenlabs.com/projects/andyq-nfcpassportreader)

Was this useful?