用 Python + PN532 模块克隆一张 NFC 门禁卡

起因

小区门禁卡丢了,补办要 50 块还要等一周。我有 PN532 模块和几张空白卡——不如自己来。

硬件准备

  • PN532 NFC 模块(约 25 元)
  • USB 转 TTL 模块(约 8 元)
  • Mifare Classic 1K 空白卡(约 2 元/张)
  • 杜邦线若干

接线

PN532 → USB转TTL
VCC   → 5V
GND   → GND
TX    → RX
RX    → TX

注意 PN532 的拨码开关要切到 UART 模式。

Python 读写代码

import serial
import time

def send_command(ser, cmd):
    ser.write(cmd)
    time.sleep(0.1)
    return ser.read(ser.in_waiting)

def read_card(ser):
    # 读取所有 16 个扇区(每扇区 4 个块)
    sectors = {}
    for sector in range(16):
        # 先用默认密钥认证
        auth_cmd = build_auth_command(sector, key_type='A')
        resp = send_command(ser, auth_cmd)
        if b'\x00' not in resp:
            print(f'扇区 {sector} 认证失败,尝试其他密钥...')
            continue
        blocks = []
        for block in range(4):
            read_cmd = build_read_command(sector * 4 + block)
            data = send_command(ser, read_cmd)
            blocks.append(data)
        sectors[sector] = blocks
    return sectors

扇区结构分析

Mifare Classic 1K 卡有 16 个扇区,每扇区 4 个块(每块 16 字节):

扇区 0, 块 0: UID + 厂商数据(只读)
扇区 0, 块 1-2: 数据区
扇区 0, 块 3: 密钥A + 控制位 + 密钥B

扇区 1, 块 0-2: 用户数据(通常存卡号)
扇区 1, 块 3: 密钥A + 控制位 + 密钥B
...以此类推

大部分门禁系统只用扇区 0 的 UID 做验证——这意味着你只需要复制 UID。但也有系统会读扇区 1 的数据,所以要全部复制。

安全性思考

这次实验让我意识到 Mifare Classic 的安全性有多弱:

  • 默认密钥 FFFFFFFFFFFF 在大量卡片上依然有效
  • CRYPTO-1 加密算法在 2008 年就被完全破解
  • 50 元不到的设备就能完整克隆一张卡

如果你的门禁系统还在用 Mifare Classic,尽早升级到 DESFire 或 Ultralight C。安全不是"够用就行"的东西。

完整代码和 GUI 版本(Tkinter 写的,带读写进度条和验证功能)已开源在 GitHub。