ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

MF卡读取全指南:从Mifare Classic协议分析到Python NFC编程实现

MF卡读取全指南:从Mifare Classic协议分析到Python NFC编程实现 简介资源包围绕MF卡协议分析与Android NFC读取实现展开面向物联网开发、移动应用及智能卡技术学习者解决从MIFARE Classic/Ultralight协议理解到Android端实际调用NFC读写MF卡的核心需求。包体含245个文件以Java源码、XML配置、Gradle构建脚本及C/C底层实现为主另包含可直接安装的APK、so动态库及若干调试用bin/json/txt文件压缩后约10.12MB结构清晰便于二次开发。已有447人学习参考。内容包括NFC读写完整工程、MF卡扇区认证与读写逻辑、Android Studio项目配置及多平台编译产物并结合MIFARE Classic容量划分、密钥机制以及SELECT、AUTHENTICATE、READ、WRITE等命令交互流程。资源内附有可运行APK与完整工程目录展示检测NFC、注册enableReaderMode监听以及使用MifareClassic完成connect、authenticate、readBlock等关键调用对排查认证失败、扇区越界等实际调试问题有直接帮助尤其适合门禁、公交等领域嵌入式与移动协同开发人员参考。 MF卡这个词在网上搜代码的帖子里出现频率特别高。我第一次听到也以为是某种存储卡后来才搞明白它通常指的是Mifare Classic系列射频卡也就是日常说的IC卡——手里的门禁卡、校园卡、部分公交卡很多都用的是这套芯片。这篇文章就围绕“MF卡协议分析、NFC读卡、代码实现”这三个核心点把“如何用NFC读卡器把MF卡里的数据读出来”这件事完整讲清楚从协议原理到能跑的Python代码都有适合刚接触射频卡开发、或者正在被门禁卡读取问题卡住的读者。1. 先搞清楚MF卡到底是什么从卡片结构到安全模型1.1 一张MF卡的内部扇区、块与16字节MF卡本身没有内置电池属于无源非接触卡能量来自读卡器发射的13.56MHz射频场。它最常用的型号是Mifare Classic 1K也就是说内部有1Kbit容量的EEPROM按8比特算就是1024个字节的存储空间。但这1024字节不是连续的一块“空地”而是被划分成了16个扇区每个扇区里又有4个块每个块固定16字节。这么一算正好是16乘以4乘以16等于1024字节。平时我们说“读MF卡”实际上的最小操作单位就是一个块——一次READ指令最多能取回16字节想读一个扇区里的出厂信息就得把块逐个读出来。块和块的地位完全不一样。每个扇区的第0块到第2块是数据块可以被业务数据占用第3块是尾部块用于存放这个扇区的密钥A、访问位和密钥B。第一扇区有点特殊它的第0块头部是UID、校验位和厂商代码这些信息出厂时就被锁定普通指令改不了。所以代码里遍历整个卡的时候我会跳过每个扇区的尾部块数据块也只读掉前3个不然读到一堆密钥和权限控制信息既没意义还会让你误以为卡坏了。1.2 认证与权限为什么有些卡直接读不出来MF卡的读卡不是“放上去就能看数据”这种程度。每个扇区都有一把钥匙孔钥匙分两种类型Key A和Key B。当读卡器要访问某个扇区的数据块时必须先向卡片发起认证请求密钥对了才给开门密钥不对就返回错误状态码。这种设计是Mifare Classic当年能大量用在门禁、考勤机上的基础但现在来看它的安全级别并不高。CRYPTO1流密码在公开文献里已经被分析得很透了很多卡片默认密钥也早就不是秘密。这篇文章只讲“怎么用已知合法密钥完成读取”过程中你会看到密钥是以明文形式参与认证的。说句实在话正规应用场景下我们手里拿到的卡一般是单位配发的测试卡或者发卡方给了明确的读卡权限。做开发调试时最常用的默认密钥就是FF FF FF FF FF FF有时候厂商也会用A0 A1 A2 A3 A4 A5这种出厂值。如果你手里的卡密钥被改过且没有授权那就涉及权限问题了别想办法绕过直接用合规流程找卡主拿密钥。1.3 读取之前先明确边界我在排查NFC读取问题时遇到过一些新手拿张完全不知道来源的卡就想“破解”。这种事我必须泼冷水。非授权读取别人卡片里的数据在多数地区都涉嫌违法而且绝大多数门禁卡系统其实都做过安全加固。把目标限定在自己持有、回收、已经获得授权的卡片上既安全又能把技术链路研究透。2. 读卡前必须搞懂的协议链路2.1 从物理层到数据帧ISO/IEC 14443-AMF卡的空口通信遵循ISO/IEC 14443-A标准。读卡器作为主设备PCD卡片作为从设备PICC载波频率13.56MHz调制方式和编码规则都由这套标准约束。读卡器向卡片发送指令时用的是Miller编码和ASK调制卡片回传数据时则切到曼彻斯特编码速率标称是106kbps。不要被这些术语吓到。在应用层面我们不需要手写波形理解一条核心逻辑就够了读卡器与卡片之间是问答式通信读卡器说一句卡片答一句。比如读卡器发出REQA卡片回ATQA再往下走卡片会响应防碰撞指令交出UID然后读卡器选中这张卡之后的读写指令才能继续。这个选卡流程就是日常说的“防碰撞”。一叠卡放在读卡器上时读卡器要先通过UID逐个锁定目标防止同时和两张卡说话。防碰撞完成之后卡片才会进入可认证状态这时候才轮到密钥认证。2.2 指令从PC到卡片的旅程PC/SC与APDU写代码的时候真正需要关注的是PC/SC协议栈。PC/SC是Windows、Linux、macOS上通用的智能卡接口标准很多USB读卡器都实现了这个标准。你的代码通过系统API跟读卡器通信读卡器再按14443-A的时序把指令发给卡片。于是数据链路分成两层。PC到读卡器之间发的是APDU格式的指令读卡器到卡片之间则是RF层面的命令帧。大多数情况下读卡器驱动会帮你把APDU转换成卡片能识别的命令。所以我们写Python控制ACR122U时主要代码都是在拼APDU命令字节、参数、数据区一个字节都不能错。2.3 认证与读操作的核心指令读MF卡时绕不开三条指令获取UID、认证扇区、读取块数据。获取UID用FF CA 00 00 00这个指令是PC/SC标准里的Get UID读卡器收到后会跟卡片完成一轮防碰撞和选择然后把卡片ID返回给超级终端。认证一个扇区用FF 86 00 00 05 01 00 块号 密钥类型 6字节密钥块号填该扇区里的任意块都行因为认证粒度是整个扇区。读块数据用FF B0 00 块号 10最后的10表示要读16字节。我建议第一次上手时先别急着跑大段代码直接用读卡器自带的测试软件或者一个Python终端把这三条指令逐条发一遍。看到返回的数据和状态字之后再去写循环遍历这样对指令含义的理解会牢固得多。3. 硬件选型与Python代码实现3.1 硬件怎么选ACR122U还是PN532网上问MF卡读取代码的人手里通常是两类硬件ACR122U这类PC/SC读卡器或者PN532模块。我的建议是入门首选ACR122U它一个USB口就能解决供电和通信Windows下有官方驱动而且原生支持PC/SCPython里调用非常顺畅。PN532便宜接线多适合嵌入式项目或树莓派场景。它的底层通过I2C/SPI/UART跟主机通信在Linux下要装libnfc再用nfcpy之类的库操作流程相对折腾。如果你不是要做手持设备或需要把NFC模组嵌进现有板子没必要一开始就买PN532。我之前给一个项目调读卡流程时先用ACR122U把协议和代码全部跑通再换成PN532端口前后只改变了连接层代码认证和读取逻辑完全一致。3.2 环境准备Windows/Linux下的PC/SC栈Windows下装好读卡器驱动后基本就能用了但Linux下记得先安装pcscd和libpcsclite。Debian系的命令是sudo apt install pcscd libpcsclite-dev libpcsclite1 sudo systemctl enable pcscd sudo systemctl start pcscdPython侧需要pyscard安装一行搞定pip install pyscard装完后可以先跑一段最简代码列出读卡器确认系统能识别到设备from smartcard.System import readers all_readers readers() if not all_readers: raise SystemExit(没有发现PC/SC读卡器) for i, reader in enumerate(all_readers): print(i, reader)输出里能看到ACR122U或者类似名称就说明PC/SC栈已经通了。这一步解决后后面所有的调错效率都会高很多。3.3 完整代码从枚举读卡器到dump整个1K卡片下面这段代码是我实际在门禁卡测试项目里用过的做了简化保留了核心流程。它能完成整个Mifare Classic 1K卡的数据读取并把每个数据块以十六进制和ASCII两种形式打印出来。from smartcard.System import readers from smartcard.util import toHexString def connect_reader(): all_readers readers() if not all_readers: raise SystemExit(没有发现PC/SC读卡器) reader all_readers[0] connection reader.createConnection() connection.connect() return connection def get_uid(connection): cmd [0xFF, 0xCA, 0x00, 0x00, 0x00] data, sw1, sw2 connection.transmit(cmd) if sw1 0x90: return data return None def authenticate(connection, block, key_type, key): # key_type: 0x60表示Key A0x61表示Key B cmd [0xFF, 0x86, 0x00, 0x00, 0x05, 0x01, 0x00, block, key_type] list(key) data, sw1, sw2 connection.transmit(cmd) return sw1 0x90 and sw2 0x00 def read_block(connection, block): cmd [0xFF, 0xB0, 0x00, block, 0x10] data, sw1, sw2 connection.transmit(cmd) if sw1 0x90: return data return None def to_ascii(data): return .join(chr(b) if 32 b 127 else . for b in data) KEY_A_DEFAULT bytes([0xFF] * 6) # 默认Key A def dump_mf_classic_1k(): conn connect_reader() uid get_uid(conn) if uid is None: print(获取UID失败请确认卡片靠近读卡器) return print(UID:, toHexString(uid)) for sector in range(16): base_block sector * 4 # 每个扇区认证一次然后读前三个数据块 if not authenticate(conn, base_block, 0x60, KEY_A_DEFAULT): print(f扇区 {sector:02d} 认证失败) continue for offset in range(3): block base_block offset data read_block(conn, block) if data is None: print(f扇区 {sector:02d} 块 {block:02d} 读取失败) else: hex_part toHexString(data) ascii_part to_ascii(data) print(fS{sector:02d}B{block:02d}: {hex_part} | {ascii_part}) if __name__ __main__: dump_mf_classic_1k()运行后默认密钥能过的扇区都会打印出来过不了的就提示认证失败。到这里读取MF卡内容的核心目标就算达成了。3.4 代码关键点讲解为什么这样设计有几个地方容易踩坑展开说一下。第一个是认证粒度。Mifare Classic的认证是按扇区来的不是按块。所以我在代码里对每个扇区的起始块做一次认证然后连续读3个块。有些新手把认证放在每一块读取之前增加了无意义的射频交互读卡时间会明显变慢。第二个是密钥类型。0x60和0x61两个参数很容易写反。0x60对应Key A0x61对应Key B。部分卡片的Key B读取权限受到访问位控制实际上用Key A认证更通用这也是我默认用Key A的原因。第三个是返回值判断。transmit返回的状态字90 00代表指令执行成功。如果返回63 00多数是认证失败返回69 82可能是权限不够返回6B 00则要检查APDU参数长度。看到这些错误码不要慌后文有排查表。4. 实操演示一步步读出MF卡数据4.1 从连接读卡器到跑通首个命令这里我以ACR122U为例把实操过程拆开走一遍。先确保读卡器已经插入电脑然后打开终端运行上面的简单枚举脚本。正常情况下你会看到类似下面的输出0: ACS ACR122U PICC Interface 0然后拿一张已知默认密钥的测试卡放到读卡器正面的硬币感应区域。放卡有个小技巧卡片不要垂直悬空要尽量平贴读卡器logo位置因为ACR122U的天线就在那个圆形区域距离太远或者角度偏移会导致软复位失败。接下来单独发一条读取UID命令模拟一下卡片放上去后的返回效果conn connect_reader() print(UID:, toHexString(get_uid(conn)))如果一切正常会输出类似04 A3 12 5B这样的数据。这个UID不是随机数它包含在卡片第0扇区第0块前4个字节里是卡片最基础的身份标识。4.2 实际读取输出与分析继续跑完整的dump脚本你可能会看到类似下面的输出UID: 04 A3 12 5B S00B00: 04 A3 12 5B EE 08 04 00 62 63 64 65 66 67 68 69 | .…....bcdefghi S00B01: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ S00B02: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ S01B00: 01 02 03 04 00 00 00 00 00 00 00 00 00 00 00 00 | ................第一行的04 A3 12 5B就是UID第5个字节EE是UID校验位它必须等于前4个字节的异或结果否则卡片会被读卡器判定为无效卡。08和04 00是SAK和ATQA等应答参数再往后的8个字节是厂商代码不同厂家的数据会不同。从第1扇区开始才通常是门禁系统真正存数据的地方比如用户ID、时间段权限等。如果你拿到的卡里面是纯业务数据ASCII区域偶尔能看到数字或字母。数据块全是00也很正常不代表读取有问题可能就是卡还没写入记录。读取时如果某个扇区提示“认证失败”通常有两种可能密钥不是默认值或者这一块本来就设置了不同权限。我会建议换Key B再试一次把0x60改成0x61密钥也换成该扇区的Key B。如果还是失败那就是这张卡的密钥被发行方修改过合规做法就是去拿授权。4.3 读卡速度与效率优化对一张1K卡做完整dump如果逐块认证需要认证64次就算每次只要几十毫秒累计起来也会让操作显得拖沓。所以我在代码里把认证次数降到了16次每个扇区只认证1次。实际跑下来完整读取一张卡的时间可以控制在1秒左右。如果项目里需要频繁读卡还有两个优化方向。一是缓存已经认证过的扇区列表避免重复认证二是用读卡器自身的扩展APDU一次读多个块ACR122U的FF B1指令就可以实现不过具体支持情况取决于固件版本要自己测。5. 高频问题与排查实录5.1 常见错误码与解决思路我在帮别人调试NFC读取代码时问题基本集中在几个地方。下面这张表基本能覆盖绝大多数场景。现象错误码/提示可能原因排查方向读卡器灯亮但程序报“没有发现读卡器”枚举不到设备PC/SC服务未启动或驱动问题Windows重新装ACS驱动Linux检查pcscd状态卡片放上去没有响应获取UID失败卡片没有靠近天线区域调整卡片位置确认读卡器频率是13.56MHz认证失败63 00密钥错误或密钥类型选错换Key B使用正确的扇区密钥认证成功但读块失败69 82当前扇区的访问位禁止该操作查阅访问控制字节检查Key B权限指令被拒绝6B 00APDU长度或参数不对核对指令字节数确认块号是否在0~63之间读出来的全是FFFF无报错数据确实没写入或你读的是尾部块跳过每个扇区的第3块只读数据块5.2 几个容易踩的坑和独家技巧这些坑都是我实际踩过或者看别人踩过的特别值得注意。第一不要试图修改第0扇区第0块。这个块是厂商信息区UID一旦被锁定强行写入通常会导致卡片变砖或者不能再认证。调试阶段就老实读取别手痒。第二读卡器对卡片放上去的时序很敏感。ACR122U在Linux下偶尔会出现首次连接报复位失败的情况我的土办法是先断开连接等2秒再重新connect多半就正常了。这是读卡器固件和系统驱动的兼容性问题跟卡片没关系。第三十六进制和ASCII对照打印是个好习惯。有一回我调门禁系统发现读出来的数据块看起来全是二进制乱码想当然以为是加密数据。后来打印了ASCII列才发现其实是厂商用ASCII码存的卡号只是读的时候就该先看右侧文本列。数据是否是明文、是否需要二次解析必须结合业务场景判断。第四不同厂家的Mifare Classic卡访问位默认值不一样。虽然默认密钥大多是FF FF FF FF FF FF但访问位如果被改过即使密钥对了也可能写不进去。调试时先拿一张全新的测试卡做基准测试能省掉很多排查时间。第五靠近读卡器时不要同时放多张卡。MF卡本身支持防碰撞但你手里的卡和交通卡叠在一起很容易导致读卡器选择到错误的UID尤其在大批量卡片测试时要注意。每次只放一张最稳妥。6. 从读取到理解一次完整的上手路径如果你刚开始接触MF卡读取我建议按这个顺序走一遍先用ACR122U把PC/SC链路跑通然后用最简代码获取UID再单独验证认证指令和读取指令最后才跑完整dump脚本。每一步都确认返回值正确再去读下一阶段。不要一上来就复制一个大脚本跑出了错都不知道是协议问题、驱动问题还是代码问题。我自己最初接触这块也是从一张无法读取的旧门禁卡开始的。当时连APDU是什么意思都不知道对着错误码一头雾水。后来把卡片结构、认证粒度、FF 86和FF B0的指令格式理解清楚之后突然就通了——读卡器、PC/SC、APDU和卡片四个环节其实都有迹可循。现在回头再看这类问题大部分卡壳点其实不是代码本身而是对协议栈和卡片结构没有一个整体认识。最后再分享一个小技巧如果你要长期做NFC相关开发最好准备一张专门用于测试的Mifare Classic 1K空卡用软件给它写入一段有规律的数据比如从第1扇区第0块开始写01 02 03 ... 0F 10。每次调试读卡流程时拿这张卡做基准读出来对不上就知道是代码问题而不是在纠结卡里面到底是什么数据。有了这套基准卡整个开发效率会提升一大截。本文还有配套的精品资源点击获取
返回列表