MIFARE Classic на Arduino: ключи, сектора, блоки
MIFARE Classic 1K от NXP это самый распространённый RFID-стандарт для карт доступа, проездных и транспортных карт. На кристалле 1024 байта памяти, разбитых на 16 секторов по 4 блока по 16 байт. Каждый сектор закрыт ключами A и B: без аутентификации нельзя ни прочитать, ни записать.
Симулятор поддерживает полный MIFARE: аутентификацию, чтение и запись блоков, изменение ключей. В симуляторе RFID ридер MFRC522 показывает все 16 секторов с их содержимым, и ключ для сектора можно поменять прямо в браузере.
Для железа понадобится ридер RC522 и карта. Задача урока: считать UID, открыть сектор ключом A, прочитать блок, записать свои данные и закрыть сектор обратно.
Структура памяти MIFARE 1K
Всего 16 секторов, в каждом 4 блока по 16 байт, итого 64 блока и 1024 байта. Блок 3 каждого сектора это sector trailer: в нём хранятся ключ A (6 байт), access bits (4 байта) и ключ B (6 байт). Блоки 0, 1 и 2 отданы под данные. Блок 0 сектора 0 особый: там лежит UID карты, 4 байта у Classic 1K или 7 байт у версии 4K, плюс данные производителя, и он только читается.
Нумерация блоков сквозная: сектор 0 это блоки с 0 по 3, сектор 1 это блоки с 4 по 7 и так далее. Именно поэтому адрес блока в командах делится на 4, чтобы получить номер сектора, и это же деление делает ридер внутри PICC_Authenticate.
Factory key, то есть заводской ключ, у всех карт одинаковый: FFFFFFFFFFFF, шесть байт 0xFF. Это позволяет читать карту из коробки, но и не защищает её, потому что этот ключ известен всем. Свои ключи задают через запись в sector trailer, и тогда карта перестаёт читаться без аутентификации. Именно так и делают карты доступа: администратор привязывает карту к считывателю, а рядовой пользователь ключей не знает.
PICC_Authenticate и MIFARE_Read
#include <MFRC522.h>MFRC522 rfid(10);void setup() { Serial.begin(9600); rfid.PCD_Init();}void loop() { if (rfid.PICC_IsNewCardPresent() && rfid.PICC_ReadCardSerial()) { // 0x60 = KEY_A, блок 1 (сектор 0) byte status = rfid.PICC_Authenticate(0x60, 1, 0, 0); if (status == 1) { Serial.println("Auth OK"); byte buffer[18]; byte len = 18; status = rfid.MIFARE_Read(1, buffer, &len); if (status == 0) { Serial.print("Block 1: "); for (byte i = 0; i < 16; i++) { Serial.print(buffer[i], HEX); Serial.print(" "); } Serial.println(); } } rfid.PICC_HaltA(); rfid.PICC_StopCrypto1(); delay(1000); }}PICC_Authenticate принимает четыре аргумента: тип ключа, где 0x60 это KEY_A, а 0x61 это KEY_B, адрес блока, шесть байт ключа и UID карты. В примере ключ передан нулями, и это означает использовать заводской FFFFFFFFFFFF, так что карта читается прямо из коробки. Возвращаемое значение равно 1 при успехе и 0 при отказе. Если ключ передан неверно, аутентификация не пройдёт и следующий MIFARE_Read вернёт отказ.
MIFARE_Read принимает адрес блока, буфер и указатель на длину. В буфер кладётся 16 байт данных, а в length рекомендуют писать 18, потому что там есть запас под два байта CRC. Возвращаемое значение 0 это STATUS_OK, всё остальное означает ошибку, и код обязан это проверять. MIFARE_Write работает так же: адрес блока, буфер и длина.
Завершать работу с картой нужно вызовами PICC_HaltA и PICC_StopCrypto1. Первый переводит карту в состояние halt, чтобы она не отвечала, второй выключает криптографический блок. В симуляторе оба вызова сбрасывают аутентификацию сектора, то есть после них повторное чтение требует нового PICC_Authenticate. На реальном железе без StopCrypto1 карта может остаться в зашифрованном состоянии и не отвечать следующему считывателю.
| Сектор | Блоки | Назначение |
|---|---|---|
| 0 | 0-3 | UID (только чтение) + данные + trailer |
| 1-15 | 0-2 (данные), 3 (trailer) | Пользовательские данные |
| Sector trailer | Блок 3 каждого сектора | Ключ A (6) + access (4) + ключ B (6) |
| Block 0 | Сектор 0, блок 0 | UID + производитель (read-only) |
Что сначала не получилось
Первый скетч я написал по аналогии с примерами из интернета: вызвал PICC_IsNewCardPresent, потом PICC_ReadCardSerial и сразу MIFARE_Read. MIFARE_Read возвращал отказ, потому что я ни разу не аутентифицировал сектор. Добавил PICC_Authenticate с ключом A и заводским значением, и чтение заработало.
Второй затык был с адресом блока. Я хотел прочитать данные второго блока и передавал в MIFARE_Read значение 2, забыв, что это блок 2 сектора 0. Ошибка была неочевидной, потому что адрес 2 существует и принадлежит сектору 0, но аутентифицировал я сектор 1, вычислив его как 2 делить на 4 с округлением вниз. Когда выровнял номер блока и номер сектора, всё встало на место.
Третье, что я запомнил, это то, что запись в блок 3 молча ломает карту. Я записал в третий блок свои данные, думая, что это обычный блок данных, и карта перестала открываться заводским ключом, потому что я затёр sector trailer. Восстановить ключ после этого можно только полным сбросом карты, если он вообще возможен. Секторный трейлер это не просто блок, это замок.
Личный опыт: карта-ключ от мастерской
Первое устройство на MIFARE Classic у меня было самодельной картой-ключом от мастерской: прикладываешь карту к ридеру, и если UID есть в списке, реле открывает замок. Список разрешённых карт лежал в памяти ESP32, а сама аутентификация ключом A использовалась только чтобы проверить, что карта не подделана.
Проверял я это по цифрам: в сектор 0 в блок 1 я записывал 16 байт с идентификатором мастерской, потом этот же блок читался обратно и сравнивался с исходным массивом байт за байтом. Первый прогон дал расхождение в последнем байте, и выяснилось, что я передавал в MIFARE_Write буфер длиной 18, а длина данных должна быть ровно 16. После исправления буфер совпал полностью. Дополнительно я проверил, что после PICC_HaltA чтение возвращает отказ: это подтверждает, что защита сектора работает и данные не утекают без аутентификации.
Авторская позиция: что взять новичку
Берите RC522, а не RC532: у RC532 модуль-передатчик мощнее, но для чтения карт это лишнее, а цена выше. Начинайте с чтения UID, это пять строк кода и понятный результат, и только потом добавляйте аутентификацию и запись. И никогда не пишите в блок 3, пока не поймёте, что это ключи: заблокированная карта восстанавливается тяжело, а стоит копейки. Для реальной защиты данных MIFARE Classic не годится, его криптография взломана, и если это учебная задача, берите MIFARE DESFire с AES-128.