This SDK handles reading an NFC enabled passport using iOS 13 CoreNFC APIs to read and verify the chip of an eMRTD (electronic machine readable travel document).


Sample App

In Example there is a minimal full functional demo, that shows the usage of the SDK. To run it on the device, please adjust the bundle identifier and the development team accordingly.

Framework Installation

Apple offers XCFrameworks file format (from Xcode 11 and Swift 5.1 on) for packaging module-stable frameworks. The folder sdk contains two .xcframework files: emrtd_sdk.xcframework and openssl.xcframework.

Direct Installation in Xcode

Just put the two xcframework files into your target's dependencies via drag&drop: Xcode XCFramework usage

Via Cocoapods (1.9+ required)

There is also a emrtd-sdk.podspec file in folder sdk. If you already use Cocoapods for dependency management, you can simply add the sdks with one statement in your Podfile:

platform :ios, '13.0'

target 'emrtd-sdk-sample' do

  # local Pod for NFC passport reading
  pod 'emrtd-sdk', :path => '../sdk/'

  # ... could be also managed your own git
  #pod 'emrtd-sdk', :git => 'https://git.yourdomain.com/emrtd-sdk-ios.git'

Then, run the following command:

$ pod install

Additional hints

1. Changes in Info.plist

  <string>This app uses NFC to scan passports</string>

2. Entitlement

    <string>TAG</string>  // Application specific tag, including ISO 7816 Tags


EmrtdPassportReader instance

Generate an access key using the document number, date of birth and date of expiry. Or generate an access key using the CAN (6 digit number, printed on the front of the document)

On an instance of EmrtdPassportReader, call readAndVerify(accessKey:completedCallback:) to read and verify the passport.

Or call read(accessKey:completedCallback:) to just read data from the passport without verifying.

let passportReader = EmrtdPassportReader()
let mrzKey = MRZKey(documentNumber: "123456789", birthDateYYMMdd: "970101", expiryDateYYMMdd: "201212")

passportReader.readAndVerify(accessKey: mrzKey, completedCallback: { (passport, error) in
   if let passport = passport {
       // All good, we got a passport
   } else if let error = error {
       // Error occurred

Using the CAN Key:

let canKey = CANKey(keyString: "123456")
passportReader.readAndVerify(accessKey: canKey, completedCallback: ...)

The messages displayed during the NFC Session can be customized:

func errorLocalization(error: EmrtdPassportReaderError) -> String {
    switch error {
    case .NFCNotSupported(_):
        return ""
    case .FailedToReadMasterlistFile(_):
        return ""
    case .MoreThanOneTagFound:
        return ""
    case .WrongTag:
        return ""
    case .UserInvalidatedSession:
        return ""
    case .SessionInvalidated(let errorCode):
        return ""
    case .ConnectingFailed(let error):
        return ""
    case .ConnectionLost:
      return "Lost Connection to chip"
    case .PaceOrBacFailed(let error):
        return ""
    case .FileReadFailed(let error, let files):
        return "Failed to read file(s) \(files.map({ "\($0)" }))"
    case .IncorrectAccessKey:
        return ""
    @unknown default:
        return "Unknown Error"

func stepLocalization(step: ReadAndVerifyStep) -> String {
    switch step {
    case .waitingForPassport:
        return "Hold your iPhone near a passport"
    case .readFileAtrInfo:
        return "Reading elementary file Atr/Info"
    case .readFileCardAccess:
        return ""
    case .doPaceOrBac(_):
        return ""
    case .readFileSOD:
        return ""
    case .readFileDG14:
        return ""
    case .doChipAuthentication(_):
        return ""
    case .readFileDG15:
        return ""
    case .doActiveAuthentication(_):
        return ""
    case .readRemainingElementaryFiles:
        return ""
    case .doPassiveAuthentication:
        return ""
    case .done:
        return "Done"
    @unknown default:
        return "Unknown Step"

func fileReadProgressLocalization(fileName: ElementaryFileName,
                                  readBytes: Int,
                                  totalBytes: Int) -> String {
    return "Reading File \(fileName) (\(readBytes)/\(totalBytes)Bytes)"

let passportReader = EmrtdPassportReader(errorLocalization: errorLocalization,
                                         stepLocalization: stepLocalization,
                                         fileReadProgressLocalization: fileReadProgressLocalization)

EmrtdPassport instance

When EmrtdPassportReader finishes, the completed callback will be called. The EmrtdPassport instance holds the information about the passport-chip.

The supported Files are: Atr/Info, CardAccess, SOD, DG1, DG2, DG7, DG11, DG12, DG14, DG15

Supported Protocols are: BAC, PACE, Chip Authentication, Active Authentication, Passive Authentication

Some examples are given below:

passportReader.readAndVerify(accessKey: mrzKey) { passport, error in
   guard let passport = passport, error == nil else {
       // Error occurred

   /* Extracted Data */

   // DG1 is always present according to specification
   // DG1 contains the MRZ of the passport
   if let dg1File: DataGroup1File = passport.dg1File {
       let documentNumber: String = dg1File.documentNumber
   } else {
       // Failed to parse DG1 apparently :(

   // One face-info in DG2 must be present according to specification
   if let faceInfo: BiometricFaceImageInfo = passport.dg2File?.faceInfos?.first {
       let hairColor: HairColor = faceInfo.hairColor // Value may be unspecified
       let faceUIImage: UIImage? = faceInfo.uiImage

   // DG11 is optional
   if let dg11File: DataGroup11File = passport.dg11File {
       let placeOfBirth: String? = dg11File.placeOfBirth // Value is optional

   let notCorrectlyParsedFiles: [File] = passport.notCorrectlyParsedFiles

   /* Verification */
   let documentCertificate = passport.sodFile?.documentCertificate

    You need to perform the Document Validation Service
    Then you can validate the authenticity of the extracted data.

        // Perform Document validation request as shown below in the readme
        let validation: DocumentCertificateValidation = ...

        // Verify authenticity of extracted data
        if passport.sodFile?.signatureValid == true &&
            validation.successful &&
            passport.allHashesValid == true &&
            passport.cardAccessFileAuthenticityConfirmed != false {

            print("Document Passive Authentication Success. Data was not tampered with.")
        } else {
            print("Document Passive Authentication Failed. Data was tampered with!!")

    You can additionally verify that the chip was not cloned.
    Note that ActiveAuthentication and/or ChipAuthentication
    may not be supported by all passports.

    If the result for either one of these checks is `.Failed`,
    you need to assume that the chip was cloned.
   if passport.activeAuthenticationResult == .Success {
       // Chip was not cloned
   if passport.chipAuthenticationResult == .Success {
       // Chip was not cloned

Verify a document certificate using the Document Validation Service:

To validate the document signer certificate using the document validation service, you need to send a request to


with the Content-Type set to multipart/form-data and the following fields:

client_id      text/plain                Client ID
dsc            application/octet-stream  Document Signer Certificate
idb            application/json          IDB data from the SDK

All fields are mandatory. client_id is a literal provided by your OVDK contact. dsc is the CardData.docSigningCertificate. idb data must be generated with the IDBMessageBuilder:

let idbMessageBuilder = IntelligentDatabaseMessageBuilder(app: "Your app name")
let idbJSON = try idbMessageBuilder.encodeAsJSON(passport: passport, error: error)

The request will return a JSON formatted data block containing a Validation ID and the result of the validation, either "valid" or "invalid":

    "validation_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "result": "[valid|invalid]"

Or simply use the DocumentCertificateValidationRequest included in the sdk:

private func documentCertificateValidationRequest(passport: EmrtdPassport?,
                                                  error: EmrtdPassportReaderError?) {
    let app = "Your app name"
    let clientId = "(a client ID provided by your OVDK contact)"
    let documentValidationHost = URL(string: "https://kinegramdocval.lkis.de/v3/validate")!

    try! DocumentCertificateValidationRequest(app: app, clientId: clientId, host: documentValidationHost)
        .validate(passport: passport, error: error) { validation, error in
            guard let validation = validation, error == nil else {
                if let error = error {
                    print("Document Certificate Validation Error \(error)")
                } else {
                    print("Document Certificate Validation Error")

            let successful = validation.successful
            let id = validation.validation_id
            print("Document Certificate Validation: Success \(successful), ID \(id)")

            if passport?.sodFile?.signatureValid == true &&
                validation.successful &&
                passport?.allHashesValid == true &&
                passport?.cardAccessFileAuthenticityConfirmed != false {
                print("Document Passive Authentication Success. Data was not tampered with.")
            } else {
                print("Document Passive Authentication Failed. Data was tampered with!!")