用 Arduino 复制 RFID 卡

힘센캥거루
2025년 11월 26일
9
arduino

今天打算写一写,如何用 Arduino 复制 RFID 卡。

写下来就不容易忘,也算是复盘记录。

用 Arduino 复制 RFID 卡-1

1.RFID 卡的内部数据结构

一般的 RFID 卡是 MIFARE Classic 1K 卡。

这张卡的存储结构如下:

- 总 1024 bytes (1KB)
- 16 个 Sector (0~15)
- 每个 Sector 有 4 个 Block (Block 0~3)
- 每个 Block 为 16 bytes

每个扇区的结构如下:

Sector n
 ├── Block 0 (Data or UID block)
 ├── Block 1 (Data)
 ├── Block 2 (Data)
 └── Block 3 (Sector Trailer: Key A, Access Bits, Key B)

在这些扇区中,最重要的是扇区 0 的第一个数据块。

UID 就存放在这里。

用 Arduino 复制 RFID 卡-2

2.UID

UID 是 Unique IDentifier 的缩写,是指分配给每一张 RFID 卡(如交通卡、门禁卡、学生证等)的唯一识别号码。

相当于人的身份证号。

UID 保存在 Sector 0 的 Block 0 内部前 4 个字节中。

[ UID0 | UID1 | UID2 | UID3 | BCC | Manufacturer Data… ]

RFID 读卡设备在读卡时,最先读取的就是这一部分。

以此为基础进行卡片识别。

用 Arduino 复制 RFID 卡-3

3. CUID 卡

关键在于,正品 MIFARE Classic 1K 的 UID 是在工厂中写死的。

NXP 原厂芯片的 UID 存在 ROM 里,无法通过任何方式修改。

因此,用普通读卡器或 MFRC522 是无法篡改 UID 的。

用 Arduino 复制 RFID 卡-4

但市面上存在可以修改 UID 的卡,这类卡被称为 CUID(俗称魔术卡)。

前面的 C 代表 Changeable。

外观与 MIFARE Classic 1K 相同,但内部芯片不同,具体如下:

① Gen1A(支持 UID/Backdoor 类型)

  • 支持 0x40 / 0x43 后门指令

  • 单独提供修改 UID 的指令

  • 可用 MFRC522 + Arduino 库的 MIFARE_SetUid() 进行修改

② CUID / Gen2(Block 0 可写)

  • 没有后门指令

  • 但设计成可以通过普通写入(WRITE)覆盖 Block 0(存 UID 的扇区)

  • 如果 MFRC522 能接受 MIFARE_Write(0…) 指令,就能修改 UID

  • 不过,并不是所有 CUID 都可以这么干;若 MFRC522 不支持,就需要 PN532/ACR122U

4. 用 Arduino 复制 RFID 卡

原理已经清楚,用 Arduino 复制 RFID 卡就很简单了。

做的事情就是复制 UID。

首先按下图连接 Arduino:

用 Arduino 复制 RFID 卡-5

Signal

MFRC522 Pin

Arduino Uno / 101

Arduino Mega

Arduino Nano v3

Arduino Leonardo / Micro

Arduino Pro Micro

RST / Reset

RST

9

5

D9

RESET / ICSP-5

RST

SPI SS

SDA (SS)

10

53

D10

10

10

SPI MOSI

MOSI

11 / ICSP-4

51

D11

ICSP-4

16

SPI MISO

MISO

12 / ICSP-1

50

D12

ICSP-1

14

SPI SCK

SCK

13 / ICSP-3

52

D13

ICSP-3

15

然后在 Arduino IDE 中搜索并安装 MFRC522 库。

用 Arduino 复制 RFID 卡-6

接着,选择 文件 -> 示例 -> MFRC522 -> ReadNUID。

用 Arduino 复制 RFID 卡-7

上传示例代码后,先读取 UID 值。

UID 一般由 4 组 16 进制数组成。

F5 5F 36 80

 如果得到如上的 UID,需要写入的 UID 则是下面这样:

数字前的 0x 表示这是 16 进制数。

0xF5 0x5F 0x36 0x80

5. 问题与解决

本想用示例里的 ChangeUID 轻松搞定,但一直失败。

0x40 后门命令总是超时。

后来才发现我的卡是 Gen2 卡,只需要直接访问 0 号扇区并替换 UID 即可。

代码如下:

#include <SPI.h>
#include <MFRC522.h>

#define RST_PIN  9
#define SS_PIN   10

MFRC522 mfrc522(SS_PIN, RST_PIN);
MFRC522::MIFARE_Key key;

// 바꾸고 싶은 새 UID (4바이트)
byte newUid[4] = { 0xF5, 0x5F, 0x36, 0x80 };  // 예시

void setup() {
  Serial.begin(9600);
  while (!Serial) {}

  SPI.begin();
  mfrc522.PCD_Init();
  Serial.println(F("CUID 카드 Block 0에 직접 UID 쓰기 예제"));

  // 기본 키 FF..FF 세팅
  for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
  }
}

void loop() {
  // 새 카드 대기
  if (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) {
    return;
  }

  Serial.print(F("현재 카드 UID: "));
  for (byte i = 0; i < mfrc522.uid.size; i++) {
    Serial.print(mfrc522.uid.uidByte[i], HEX);
    Serial.print(" ");
  }
  Serial.println();

  // === 1. 섹터 0 인증 (Block 0) ===
  byte block = 0;  // Block 0
  MFRC522::StatusCode status;

  status = mfrc522.PCD_Authenticate(
      MFRC522::PICC_CMD_MF_AUTH_KEY_A,
      block,
      &key,
      &(mfrc522.uid)
  );

  if (status != MFRC522::STATUS_OK) {
    Serial.print(F("인증 실패: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    goto HALT;
  }

  // === 2. 기존 Block 0 내용을 읽어서 manufacturer 부분 보존 ===
  byte block0[18];  // 16바이트 + 크기 정보
  byte size = sizeof(block0);
  status = mfrc522.MIFARE_Read(0, block0, &size);

  if (status != MFRC522::STATUS_OK) {
    Serial.print(F("Block 0 읽기 실패: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    goto HALT;
  }

  // block0[0..3] = 원래 UID
  // block0[4]    = BCC (UID 4바이트 XOR)
  // block0[5..15]= 제조사 데이터 등

  // === 3. 새 UID + BCC 계산해서 덮어쓰기 ===
  byte bcc = newUid[0] ^ newUid[1] ^ newUid[2] ^ newUid[3];

  block0[0] = newUid[0];
  block0[1] = newUid[1];
  block0[2] = newUid[2];
  block0[3] = newUid[3];
  block0[4] = bcc;
  // block0[5..15] 는 그대로 두면 제조사 데이터 유지

  status = mfrc522.MIFARE_Write(0, block0, 16);
  if (status != MFRC522::STATUS_OK) {
    Serial.print(F("Block 0 쓰기 실패: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    goto HALT;
  }

  Serial.println(F("새 UID 쓰기 완료. 카드를 떼었다가 다시 대보세요."));

HALT:
  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1();

  delay(1000);
} 

6. 后记

这次总算弄清楚 RFID 模块的正确用法了。

在搜索时,常能看到用门卡开门之类的项目,看起来实现起来也不难。

今后想多做一些不同方向的应用。

顺带一提,如果用这个方法卡仍然无法被识别,很大可能是频率问题(13.56MHz、125KHz 等)。

如果有问题欢迎在评论区提问。

댓글을 불러오는 중...