오늘은 아두이노로 RFID카드를 복제하는 방법에 대해 써보려고 한다.
한번 글을 쓰면 잊어버리지 않기에, 복기하는 차원에서 남긴다.

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가 들어있다.

2.UID
Unique IDentifier의 약자로, RFID 카드(예: 교통카드, 출입카드, 학생증 등)에 각 카드마다 부여된 고유 식별 번호를 뜻한다.
사람으로 치면 주민등록번호와 비슷한 역할이다.
UID는 Sector 0의 Block 0 내부 앞 4바이트에 저장된다.
[ UID0 | UID1 | UID2 | UID3 | BCC | Manufacturer Data… ]RFID 기계가 카드를 읽을 때 가장 먼저 읽는 곳이 이 부분이다.
이걸 바탕으로 카드의 식별이 이루어진다.

3. CUID 카드
중요한 것은 MIFARE Classic 1K(정품)의 UID는 공장에서 고정된다는 것이다.
NXP 정품 칩은 UID가 ROM에 저장되어 어떤 방법으로도 수정할 수 없다.
따라서 일반 카드 리더기나 MFRC522로는 UID 변조가 불가능하다.

하지만 시장에는 UID 변경이 가능한 카드가 있는데 이런 카드를 CUID(일명 매직카드)라고 한다.
앞쪽의 C는 Changeable을 뜻한다.
외형은 MIFARE Classic 1K와 동일하지만, 내부 칩이 다음과 같이 다르다.
① Gen1A (UID/Backdoor 지원형)
0x40 / 0x43 백도어 명령을 지원
UID 변경 명령을 별도로 제공
MFRC522 + Arduino 라이브러리의 MIFARE_SetUid()로 변경 가능
② CUID / Gen2 (Block 0 Writable)
백도어 명령 없음
대신 Block 0(UID가 들어있는 섹터)을 일반 쓰기(WRITE) 로 덮어쓸 수 있게 설계
MFRC522가 MIFARE_Write(0…) 명령을 받아주면 UID 변경 가능
단, 모든 CUID가 되는 건 아니다. MFRC522가 지원 안 하면 PN532/ACR122U 필요
4. 아두이노로 RFID카드 복제하기
이제 원리는 알았으니 아두이노로 RFID카드를 복제하는 방법은 간단하다.
UID를 복제해주면 되는 것.
먼저 아두이노를 아래와 같이 연결해준다.

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 라이브러리를 찾아 설치해준다.

이제 파일 -> 에제 -> MFRC522에서 ReadNUID를 선택해준다.

해당 코드를 업로드 후 UID 값을 먼저 읽는다.
UID는 일반적으로 16진수 4개로 이루어저 있다.
F5 5F 36 80만일 위와 같은 UID를 얻었다면, 업로드 할 UID는 아래와 같다.
숫자 앞의 0x는 이 숫자가 16진수라는 뜻이다.
0xF5 0x5F 0x36 0x805. 문제점, 그리고 해결
예제에 있는 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 등)일 가능성이 크다.
혹시 질문이 있다면 댓글 바란다.
댓글을 불러오는 중...