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.
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.
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.
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
libnfc ships several useful CLI tools:
nfc-list — enumerate tags in field, show UID/ATQA/SAKnfc-mfclassic — read/write MIFARE Classic sectors (with key)nfc-mfultralight — read/write MIFARE Ultralightnfc-scan-device — list available reader devicesnfc-poll — poll for any tag$ 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
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:
sudo apt-get install libfreefare-dev libfreefare-bin
CLI tools from libfreefare-bin:
mifare-classic-read-ndef — read NDEF from a MIFARE Classic card (uses MAD)mifare-desfire-info — display DESFire application treemifare-ultralight-info — display Ultralight memory layoutfreefare_list_tags — example that lists tags with type info#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);
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:
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
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 |
|---|---|
| USB (first available) | 'usb' |
| Specific USB (ACR122U) | 'usb:04e6:5591' |
| PN532 on UART | 'tty:AMA0:pn532' |
| PN532 on I2C (Linux) | 'i2c:/dev/i2c-1' |
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).
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})
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:
pip install ndeflib
UriRecord — URI (all prefix codes)TextRecord — text + language codeSmartposterRecord — URI + titles + actionBluetoothLowEnergyRecord — BLE out-of-band pairingWifiSimpleConfigRecord — Wi-Fi credentialsHandoverSelectRecord / HandoverRequestRecord — BT/Wi-Fi handoverGcActionRecord, GcTargetRecord, GcDataRecord — Generic ControlDeviceInformationRecordimport ndef
raw = bytes.fromhex("d1010b5504 6578616d706c652e636f6d".replace(" ",""))
for record in ndef.message_decoder(raw):
print(type(record).__name__, "→", record.uri)
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.
sudo apt-get install pcscd # Linux
pip install pyscard
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}")
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.
Android has built-in NFC support since API level 10. The framework is in android.nfc and android.nfc.tech.
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 |
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()
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.
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) |
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).
| 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 |
| ← Chapter 7: Hardware & Readers | Table of Contents | Chapter 9: Practical Examples → |