Chapter 8: Software Libraries — nfcpy, libnfc, pyscard & more

With hardware in hand and the protocol theory from previous chapters, the next question is: what software do you use to talk to NFC tags from Python (or C, or Android, or iOS)?

This chapter surveys the main libraries available across platforms, with installation notes, a quick API feel for each, and a comparison table at the end.


8.1 The Software Stack

The libraries in this chapter occupy different levels of the software stack:

User application
      ↕
High-level NFC library (nfcpy, ndeflib)
      ↕
Mid-level hardware abstraction (libfreefare, pyscard / PC/SC)
      ↕
Low-level driver library (libnfc)
      ↕
OS USB / serial / I2C subsystem
      ↕
Reader hardware (PN532, ACR122U, RC522...)

You do not always need every layer. For NDEF read/write on common tags, nfcpy alone is sufficient. For MIFARE Classic authenticated access, you typically need libfreefare on top of libnfc.


8.2 libnfc (C)

Repository: https://github.com/nfc-tools/libnfc Language: C Platform: Linux, macOS, Windows (with libusb)

libnfc is the foundational open-source NFC library. It provides:

libnfc is the building block on which libfreefare and (historically) some nfcpy backends are built.

Installation

sudo apt-get install libnfc-dev libnfc-bin
# or from source:
git clone https://github.com/nfc-tools/libnfc
cd libnfc && cmake . && make && sudo make install

Command-line tools

libnfc ships several useful CLI tools:

Using nfc-list

$ nfc-list
NFC device: pn532_i2c:/dev/i2c-1 opened
1 ISO14443A passive target(s) found:
    ATQA (SENS_RES): 00  44
       UID (NFCID1): 04  6a  b3  32  a5  63  80
      SAK (SEL_RES): 00

8.3 libfreefare (C)

Repository: https://github.com/nfc-tools/libfreefare Language: C Platform: Linux, macOS Depends on: libnfc

libfreefare is a higher-level library on top of libnfc providing MIFARE-specific abstractions:

Installation

sudo apt-get install libfreefare-dev libfreefare-bin

CLI tools from libfreefare-bin:

MIFARE Classic Authentication (C snippet)

#include <freefare.h>
FreefareTag tag = ...;
MifareClassicKey key = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
mifare_classic_connect(tag);
mifare_classic_authenticate(tag, 4, key, MFC_KEY_A);
MifareClassicBlock block;
mifare_classic_read(tag, 4, &block);

8.4 nfcpy (Python)

Repository: https://github.com/nfcpy/nfcpy Language: Python 3 Platform: Linux, macOS, Windows Depends on: libusb (via pyusb) or serial (for UART PN532)

nfcpy is the most complete Python NFC library. It provides:

Installation

pip install nfcpy
# USB permissions on Linux (add user to 'plugdev' group or use udev rule):
echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="04e6", ATTRS{idProduct}=="5591", MODE="0666"' \
  | sudo tee /etc/udev/rules.d/99-nfcpy.rules
sudo udevadm control --reload-rules

Basic Usage Pattern

nfcpy uses a callback-driven event loop:

import nfc

def on_connect(tag):
    print(f"Tag type: {tag.type}")
    print(f"UID: {tag.identifier.hex()}")
    if tag.ndef:
        for record in tag.ndef.records:
            print(record)
    return True  # keep connection alive

with nfc.ContactlessFrontend('usb') as clf:
    while True:
        clf.connect(rdwr={'on-connect': on_connect})

Device String Format

Device String
USB (first available) 'usb'
Specific USB (ACR122U) 'usb:04e6:5591'
PN532 on UART 'tty:AMA0:pn532'
PN532 on I2C (Linux) 'i2c:/dev/i2c-1'

MIFARE Classic Access

def on_connect(tag):
    if isinstance(tag, nfc.tag.tt1.Type1Tag):
        return
    key = bytearray.fromhex('FFFFFFFFFFFF')
    # Authenticate sector 1, read block 4
    tag.authenticate(4, key, b'\x60')  # 0x60 = key A
    data = tag.read(4)
    print(f"Block 4: {data.hex()}")

nfcpy’s MIFARE Classic support uses the MifareClassic tag type. See the nfcpy documentation for the full API (tag.authenticate, tag.read, tag.write).

NDEF Write

import nfc, ndef

def on_connect(tag):
    records = [ndef.UriRecord("https://example.com")]
    tag.ndef.records = records
    return True

with nfc.ContactlessFrontend('usb') as clf:
    clf.connect(rdwr={'on-connect': on_connect})

8.5 ndeflib (Python)

Repository: https://github.com/nfcpy/ndeflib Language: Python 3 (pure Python, no C dependencies) Platform: Any

ndeflib is a standalone NDEF encode/decode library — it does not handle hardware at all. Use it to:

Installation

pip install ndeflib

Record Types Supported

Example: Parse NDEF bytes

import ndef

raw = bytes.fromhex("d1010b5504 6578616d706c652e636f6d".replace(" ",""))
for record in ndef.message_decoder(raw):
    print(type(record).__name__, "", record.uri)

8.6 pyscard (Python, PC/SC)

Repository: https://github.com/LudovicRousseau/pyscard Language: Python 3 Platform: Linux, macOS, Windows Depends on: pcscd (Linux), built-in PC/SC (Windows, macOS)

pyscard wraps the PC/SC API (WinSCard / pcsclite), enabling Python applications to send raw ISO 7816 APDUs to any PC/SC-compliant reader including the ACR122U.

Installation

sudo apt-get install pcscd  # Linux
pip install pyscard

Basic Usage

from smartcard.System import readers
from smartcard.util import toHexString

r = readers()[0]
connection = r.createConnection()
connection.connect()

# GET_DATA for UID (ISO 14443-3 via ACR122U pseudo-APDU)
cmd = [0xFF, 0xCA, 0x00, 0x00, 0x00]
data, sw1, sw2 = connection.transmit(cmd)
print(f"UID: {toHexString(data)}, SW: {sw1:02X}{sw2:02X}")

ACR122U-Specific Commands

The ACR122U exposes several vendor APDUs for PN532 operations:

APDU Function
FF CA 00 00 00 Get UID
FF CA 01 00 00 Get ATS (Historical bytes, ISO 14443-4)
FF 00 00 00 05 D4 60 [block] [key_type] [key_num] Load MIFARE key into key store
FF 86 00 00 05 01 00 [block] [key_type] [key_num] Authenticate MIFARE block
FF B0 00 [block] 10 Read 16-byte MIFARE block
FF D6 00 [block] 10 [16 bytes] Write 16-byte MIFARE block

For non-MIFARE cards (DESFire, Java Card), use FF 00 00 00 [len] 00 [APDU] to wrap and send raw APDUs.


8.7 Android NFC API

Android has built-in NFC support since API level 10. The framework is in android.nfc and android.nfc.tech.

Tag Technology Classes

When your activity receives an ACTION_TAG_DISCOVERED or ACTION_NDEF_DISCOVERED intent, the Tag object exposes which technology classes are available:

Class Description
Ndef NDEF read/write for any NFC Forum compliant tag
NdefFormatable Format blank ISO 14443-3A/4 tags for NDEF
MifareClassic MIFARE Classic sector auth, read, write
MifareUltralight MIFARE Ultralight page read/write
IsoDep ISO 14443-4 APDU transport (DESFire, EMV)
NfcA Low-level ISO 14443-3A (transceive raw frames)
NfcB Low-level ISO 14443-3B
NfcF Low-level NFC-F (FeliCa)
NfcV Low-level ISO 15693

Reading NDEF on Android (Kotlin)

val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
val ndef = Ndef.get(tag) ?: return
ndef.connect()
val message = ndef.ndefMessage
for (record in message.records) {
    Log.d("NFC", "TNF=${record.tnf}, type=${String(record.type)}")
}
ndef.close()

Host Card Emulation (HCE)

Android 4.4+ supports HCE: your app registers an AID, and when a reader selects that AID, Android routes ISO 7816 APDUs to your HostApduService. The NFC controller handles the RF layer; your service provides the application logic. No physical secure element is required.


8.8 iOS CoreNFC

CoreNFC (introduced in iOS 11) provides NFC tag reading. iOS 14+ expanded capabilities:

Feature iOS version
Read NFC Forum Type 1–5 NDEF tags iOS 11+
Read MIFARE Classic (with key) iOS 13+
Read MIFARE Ultralight iOS 13+
Read DESFire / ISO 7816 APDU iOS 13+ (NFCTagReaderSession)
Write NDEF iOS 13+
Card Emulation Not available (hardware SE only)

Reading NDEF on iOS (Swift)

import CoreNFC

class Reader: NSObject, NFCNDEFReaderSessionDelegate {
    var session: NFCNDEFReaderSession?

    func startScan() {
        session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: true)
        session?.begin()
    }

    func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
        for record in messages[0].records {
            print("TNF: \(record.typeNameFormat), payload: \(record.payload)")
        }
    }
}

CoreNFC requires a NFC Capability entitlement in the app, a usage description in Info.plist, and a physical device (simulator does not support NFC).


8.9 Library Comparison

Library Language Platform NDEF MIFARE Classic DESFire NTAG Hardware
libnfc C Linux/macOS/Win No Raw Raw Raw PN532, ACR122U, others
libfreefare C Linux/macOS Via MAD Full Partial Partial Via libnfc
nfcpy Python Linux/macOS/Win Full Partial No Full PN532, ACR122U
ndeflib Python Any Full N/A N/A N/A None (pure codec)
pyscard Python Linux/macOS/Win No Via APDU Via APDU Via APDU PC/SC (ACR122U, etc.)
Android NFC Java/Kotlin Android Full Full Via IsoDep Full Built-in phone NFC
CoreNFC Swift/ObjC iOS Full Full Via ISO7816 Full Built-in phone NFC

Summary


← Chapter 7: Hardware & Readers Table of Contents Chapter 9: Practical Examples →