nfctester 是一个专为 RFID/NFC 卡片与读卡器设计的自动化测试框架。项目采用严谨的分层架构设计,实现了硬件通信、芯片驱动、卡片逻辑与加密算法的深度解耦,旨在为 RFID 技术研究、漏洞分析及产品测试提供一个健壮且易于扩展的基础平台。支持 PN532 和 CLRC663 两种读卡器,通过注册表机制实现无缝切换。
- 分层架构: 清晰的 9 层体系结构,模块化程度高,易于维护与扩展。
- 广泛的协议支持:
- 卡片: Mifare Classic, ISO14443A, NFC Forum Type 2 Tag (NTAG21x/22x 等)。
- 芯片: 深度优化 PN532 HSU 和 CLRC663 UART 驱动,均支持位帧 (Bit-framing) 收发。
- 强大加密支持: 内置 AES-128 (CBC)、Mifare Crypto1 算法引擎,支持 NTAG22x AES 互认证。
- 可视化跟踪: 跟踪控制层与协议解析层,提供结构化的通信日志输出(hex + 摘要标签),完美还原协议交互细节。
- 协议解析器自动切换: 通过
ParserRegistry注册 ATQA/SAK → 解析器映射,寻卡时自动切换协议解析器。 - 插件化扩展: 通过 Registry 模式,外部只需
.py文件 + 装饰器即可接入自定义读卡器和协议解析器,无需打包。
项目遵循高度抽象的设计模式,分为以下九层:
- 硬件传输层 (Hardware): 负责底层字节流传输(如
SerialTransport)。 - 驱动层 (Driver): 实现特定芯片(如 PN532、CLRC663)的协议封装与寄存器操作。
- 注册表与会话 (Registry): 类注册(Transport/CardReader/Card/Parser)、会话管理,贯穿硬件层与驱动层。
- 卡片逻辑层 (Card): 定义各种 RFID 标签与智能卡的协议行为(Mifare, NTAG 等)。
- 加密算法层 (Crypto): 提供原子级的加密/解密操作(AES, Crypto1)。
- 通用工具层 (Utility): 包含 CRC 校验、位操作等基础辅助函数。
- 跟踪控制层 (Trace): 中心化的日志管理系统,实现业务逻辑与通信监控的分离。
- 协议解析层 (Parsers): 将字节流解析为结构化字段,供格式化输出使用。
- 脚本/CLI 层 (CLI): 提供开箱即用的命令行工具(如
aes128-cli、pn532-scanner)。
推荐使用 uv 进行环境管理:
# 克隆仓库
git clone https://github.com/crthu/nfctester.git
cd nfctester
# 同步依赖
uv syncfrom nfctester.registry import CardReaderRegistry, CardRegistry
# 1. 一行创建读卡器(自动创建 transport 并注入)
reader = CardReaderRegistry.create("pn532", transport="serial", port="COM20")
reader.open()
# 2. 寻卡(自动切换协议解析器)
card_info = reader.active()
if card_info:
# 假设已知卡片类型为 mifare_classic
card = CardRegistry.create("mifare_classic", reader=reader)
print(f"UID: {card_info.uid.hex(' ').upper()}")
reader.close()from nfctester.registry import session
# 自动管理 reader 的生命周期,类似 C# 的 using
with session("pn532", transport="serial", port="COM20") as s:
card_info = s.active()
if card_info:
res = s.transceive_bits(b"\x26", last_tx_bits=7, tx_crc=False, rx_crc=False)
if res.data:
print(f"ATQA: {res.data.hex(' ').upper()}")
# 退出时自动 closewith session("pn532", transport="serial", port="COM20") as s:
card_info = s.active()
if card_info:
# 使用 reader 级别的硬件认证,uid 来自 active() 返回的 CardInfo
if s.mf_auth(block=4, key_type=0x60, key=b'\xff\xff\xff\xff\xff\xff', uid=card_info.uid):
# 认证后 transceive 自动走加密通道
res = s.transceive(b'\x30\x04') # READ block 4
if res.data:
print(f"Block 4: {res.data.hex(' ').upper()}")框架通过 Registry 模式支持外部读卡器扩展。只需继承 CardReader 基类并用装饰器注册即可。
如果你的硬件不是串口,先注册一个 Transport:
from nfctester.registry import TransportRegistry
from nfctester.hardware.base import Transport
@TransportRegistry.register("tcp")
class TCPTransport(Transport):
def __init__(self, host="127.0.0.1", port=5000):
import socket
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((host, port))
def write(self, data: bytes):
self.sock.sendall(data)
def read(self, size: int) -> bytes:
return self.sock.recv(size)
def flush_input(self):
self.sock.setblocking(False)
try:
while self.sock.recv(4096):
pass
except BlockingIOError:
pass
finally:
self.sock.setblocking(True)
def close(self):
self.sock.close()from nfctester.registry import CardReaderRegistry
from nfctester.drivers.card_reader import CardReader, CardInfo, TransceiveBits
@CardReaderRegistry.register("acr122u")
class ACR122UReader(CardReader):
def __init__(self, transport):
self.transport = transport
def open(self):
self.transport.write(b"\xFF\x00\x00\x00\x00")
self.transport.flush_input()
def close(self):
self.transport.close()
def get_version(self) -> list[int]:
self.transport.write(b"\xFF\x00\x48\x00\x00")
return list(self.transport.read(10))
@property
def rf_field(self) -> bool:
return True
@rf_field.setter
def rf_field(self, enabled: bool):
pass
def _do_active(self) -> CardInfo | None:
cmd = b"\xD4\x4A\x01\x00"
frame = bytes([0xFF, 0x00, 0x00, 0x00, len(cmd)]) + cmd
self.transport.write(frame)
res = self.transport.read(20)
if res and len(res) >= 10:
return CardInfo(uid=list(res[6:10]), atq=list(res[2:4]), sak=res[4])
return None
@property
def mf_crypto(self) -> bool:
return False
def mf_auth(self, block: int, key_type: int, key: list[int], uid: list[int]) -> bool:
return False
def transceive(self, data: list[int], last_tx_bits: int = 0, tx_crc: bool = True, rx_crc: bool = True) -> TransceiveBits:
frame = bytes([0xFF, 0x00, 0x00, 0x00, len(data)]) + bytes(data)
self.transport.write(frame)
res = self.transport.read(262)
return TransceiveBits(data=list(res) if res else [], bits=0)import my_reader # import 即自动注册
from nfctester.registry import CardReaderRegistry
reader = CardReaderRegistry.create("acr122u", transport="serial", port="COM3")
reader.open()
card_info = reader.active()
reader.close()注册自定义协议解析器,寻卡时自动切换:
from nfctester import ParserRegistry
from nfctester.parsers.base_parser import BaseParser, ParsedFrame
@ParserRegistry.register(atqa=0x1234, sak=0x56, name="My Custom Protocol")
class MyProtocolParser(BaseParser):
def can_parse(self, data: list[int]) -> bool:
return len(data) > 0 and data[0] == 0xAA
def parse(self, data: list[int]) -> ParsedFrame:
# 解析 TX 命令
...
def parse_rx(self, data: list[int], tx: list[int] | None = None) -> ParsedFrame | None:
# 解析 RX 响应
...from nfctester.registry import CardRegistry
from nfctester.cards.base_card import BaseCard
@CardRegistry.register("my_custom_card")
class MyCustomCard(BaseCard):
# 实现 BaseCard 定义的抽象方法
...from nfctester.registry import TransportRegistry, CardReaderRegistry, CardRegistry
from nfctester.parsers.registry import ParserRegistry
print("Transports:", TransportRegistry.list())
print("Readers:", CardReaderRegistry.list())
print("Cards:", CardRegistry.list())
print("Parsers:", ParserRegistry.list())更多示例见 examples/ 目录。
框架内置了多个实用的 CLI 工具:
- PN532 扫描器:
uv run pn532-scanner
- AES-128 加密工具:
uv run aes128-cli -m encrypt -i <hex_data> -k <hex_key>
# 运行单元测试(默认,无需硬件)
uv run pytest
# 运行硬件在环测试(需连接读卡器)
uv run pytest -m hil --port COM4 --reader clrc663
# 按卡片类型过滤 HIL 测试
uv run pytest -m "hil and mifare" --port COM4
uv run pytest -m "hil and ntag224" --port COM20 --reader pn532
# 运行全部测试(单元 + HIL)
uv run pytest -m ""本项目基于 Apache License 2.0 协议开源。详见 LICENSE 文件。