Tag-Speicher lesen

Speicherorganisation:

Der EEPROM-Speicher eines 1 kByte RFID-Tag aus der Produktfamilie MIFARE ist in 16 Sektoren (0 bis 15) organisiert. Jeder Sektor besteht aus 4 Blöcken zu je 16 Byte (0 bis 15), wovon der Block mit der höchsten Blocknummer je Sektor der sogenannte "Sector Trailer" ist. Sector Trailer sind also die Blöcke 3, 7, 11, ... 59 und 63, sie beinhalten je 2 Schlüssel (KeyA und KeyB) und "Access Bits" (Zugriff-Bits).

Mit Ausnahme des Sektors 0 beinhalten jeweils 3 Blöcke vor dem Sector Trailer die Datenbytes. Der Block 0 im Sektor 0 enthält Herstellerdaten (Manufacturer Data). Wie im nachfolgenden Programm 2 zu sehen ist, ist dort z.B. die UID-Nummer des RFID-Tag gespeichert.

 

    Bild: Speicherorganisation eines 1 kByte RFID-Tags aus der Produktfamilie MIFARE


Datenblöcke:

Die 16-Byte großen Datenböcke der RFID-Tags können wahlweise als Read/Write Blocks oder als Value Blocks verwendet werden. Als Read/Write Blocks können Blöcke nur mit Zeichen beschrieben werden und die Zeichen wieder gelesen werden.

Datenblöcke die als Value Blocks definiert sind, können je einen Zahlenwert enthalten. Value Blocks können nicht nur gelesen und geschrieben werden, sondern auch inkrementiert, dekrementiert, in einen volatilen Speicher gelegt und von dort wieder zurückgespeichert werden (siehe Value Blocks).


Sector Trailer:

KeyA und KeyB:

Die Sector Trailer je Block enthalten zur Verschlüsselung des Sektorinhalts die Schlüssel KeyA (Byte 0 bis 5) und KeyB (Byte 10 bis 15). Die Bytes 6 bis 9 beinhalten die sogenannten Access Bits (Zugriffs-Bits).

Die Bytes der Schlüssel KeyA und KeyB sind im Neuzustand herstellerseitig jeweils auf 0xFF eingestellt. Jeder Sektor kann wahlweise mit KeyA oder KeyB verschlüsselt werden. Für die ersten Tests ist es sinnvoll, die Schlüssel KeyA und KeyB nicht zu verändern.

Access Bits:

Die Access Bits sind in den Bytes 6 bis 9 hinterlegt, die werkseitige Einstellung der Access Bytes 6 bis 9 ist 0xFF, 0x07, 0x80 und 0x69. Verwendet werden allerdings nur die ersten drei Bytes (Byte 6 bis 8) als Access Bits, das Byte 9 kann für Anwendungen genutzt werden. Die werkseitige Einstellung erlaubt im Sector Trailer das Schreiben des KeyA und KeyB, das Lesen und Schreiben der Access Bits und das Lesen des KeyB. Der KeyA kann nicht nur in der werkseitigen Einstellung der Access Bits nicht gelesen werden, sondern kann in keiner Einstellung gelesen werden!

In der Werkseinstellung können die Datenblöcke als Read/Write Blocks oder als Value Blocks verwendet werden. Durch Änderung der Access Bits können die Datenblöcke entweder nur als Read/Write Blocks oder nur als Value Blocks definiert werden.

Achtung: Ohne Kenntnisse über die Funktionalität der Access Bits sollten diese nicht verändert werden. Eine unsachgemäße Änderung der Access Bits eines Sektors kann zur irreversiblen Blockade des gesamten Sektors führen!

Informationen zu den Access Bits findet ihr hier: Access Bits


Programmbeispiel 2: Tag-Speicher lesen aus 1 kByte RFID-Tags

Funktionsbeschreibung:

Nach Eingabe einer gültigen Sector Trailer-Nummer (die Gültigkeit wird im Programm überprüft) und dem Erkennen eines RFID-Tags gibt das Programm neben den Tag-Daten (Typ,  und UID- und SAK-Nummer) den Inhalt des jeweiligen Sektors am Seriellen Monitor aus.

(Zum Kennenlernen des Umgangs mit der Library MFRC522 verwende ich die vorhandenen Funktionen zum Auslesen von Tag-Daten und Tag-Inhalt  "PCD_DumpVersionToSerial()" und "PICC_DumpToSerial()" absichtlich nicht).


Verwendete Libraries:

Neben den Libraries SPI und MFRC522 benötigt das Programm auch meine Library MySerialRead (Version 2.0), die hier heruntergeladen werden kann: Serieller Monitor


Hinweis: Das Programm funktioniert nur mit RFID-Tags mit 1 kByte Speicher, da Tags mit 4 kByte Speicher eine andere Speicherorganisation haben (32 Sektoren mit je 4 Blöcken und 8 Sektoren mit je 16 Blöcken).

 

Im Beispiel 2 finden folgende Funktionen der MFRC522-Library Verwendung:

(Es werden nur die neuen Funktionen gegenüber dem Beispiel 1 vorgestellt)

MFR.PCD_Authenticate(byte, byte, MIFARE_Key, Uid);

MFR.MIFARE_Read(byte, byte*, byte*);

MFR.GetStatusCodeName(byte);

MFR.PICC_HaltA();

MFR.PCD_StopCrypto1();


Beschreibung der neuen Funktionen:


byte PCD_Authenticate(byte command, byte blockAdd, MIFARE_Key* key, Uid* uid)

Funktion: Authentifizierung, um eine gesicherte Kommunikation zu MIFARE Classic Tags zu ermöglichen

Parameter: comand: Kommando, ob Verschlüsselung mit KeyA oder KeyB erfolgen soll:

                                PICC_CMD_MF_AUTH_KEY_A oder

                                PICC_CMD_MF_AUTH_KEY_B

                  blockAdd: Blockadresse des Sector Trailer

                  key: Pointer auf den Schlüssel (MIFARE_Key-Struktur in der Library)

                  uid: Pointer auf die Uid-Struktur in der Library

Rückgabe: StatusCode 0 bis 8 (= Aufzählungstyp enum in der Library)

                    0 ... STATUS_OK

                    1 ... STATUS_ERROR

                    2 ... STATUS_COLLISION

                    3 ... STATUS_TIMEOUT

                    4 ... STATUS_NO_ROOM

                    5 ... STATUS_INTERNAL_ERROR

                    6 ... STATUS_INVALID

                    7 ... STATUS_CRC_WRONG

                    8 ... STATUS_MIFARE_NACK

Bemerkung: PICC_CMD_MF_AUTH_KEY_x ist in der Library MFRC522 vom Aufzählungstyp enum

Beispiel:

Im Deklarationsteil:

MFRC522::MIFARE_Key key; //MIFARE_key ist eine Struktur in der Library

//  Strukturdefinition in der Library:

//  MF_KEY_SIZE = 6;

//

//  typedef struct {

//    byte keyByte[MF_KEY_SIZE];

//  } MIFARE_Key


myKey[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; //Festlegung des Schluessels

Im setup:

//Kopieren des festgelegten Schluessels in das Array keyByte[i];

for (byte i = 0, i < 6; i++) key.keyByte[i] = myKey[i]; //keyByte[i] ist eine Variable der Structur MIFARE_Key

Im loop:

//Nachfolgende Funktion erst nach Erkennen des RFID-Tags

//mit PICC_IsNewCardPresent() und PICC_ReadCardSerial() verwenden!

byte status =

MFR.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, blockAdd, &key, &(MFR.uid));


const string GetStatusCodeName(byte status)

Funktion: Rückgabe des Status-Code als Klartext

Parameter: status: StatusCode 0 bis 8 (= Aufzählungstyp enum in der Library)

Rückgabe: Klartext des StatusCode

                           StatusCode                                     Klartext

                    0 ... STATUS_OK                       -> "Success."

                    1 ... STATUS_ERROR                 -> "Error in communication."

                    2 ... STATUS_COLLISION           -> "Collission detected."

                    3 ... STATUS_TIMEOUT              -> "Timeout in communication."

                    4 ... STATUS_NO_ROOM            -> "A buffer is not big enough."

                    5 ... STATUS_INTERNAL_ERROR -> "Internal error in the code. Should not happen."

                    6 ... STATUS_INVALID               -> "Invalid argument."

                    7 ... STATUS_CRC_WRONG        -> "The CRC_A does not match."

                    8 ... STATUS_MIFARE_NACK      -> "A MIFARE PICC responded with NAK."

Beispiel:

Serial.println(MFR.GetStatusCodeName(status));


byte MIFARE_Read(byte blockAdd, byte* readbuf, byte* sizeOfBuf)

Funktion: Liest einen Block (= 16 Byte) vom RFID-Tag und speichert die Daten in einem Array

Parameter: blockAdd: Blockadresse (0 <= blockAdd <= (Blockanzahl - 1))

                  readbuf: Zeiger auf ein Array, in welchem die gelesenen Bytes gespeichert werden

                  sizeOfBuf: Zeiger auf ein Byte, das die Größe des Arrays (18 Byte!!) beinhaltet

                                    Arraygröße 18 Byte = 1 Kommandobyte + 1 Adressbyte + 16 Datenbytes

Rückgabe: StatusCode 0 bis 8 (= Aufzählungstyp enum in der Library), Bedeutung siehe Funktion "PCD_Authenticate()"

Beispiel:

//Ausgabe des Sector Trailer  0

byte readBuf[18];

byte sizeOfBuf = sizeof(readBuf);

byte blockAdd = 3; //= Sector Trailer 0

byte status = MFR.MIFARE_Read(blockAdd, &readbuf, &sizeOfBuf);

if (status == MFRC522::STATUS_OK)

{

  for (byte i = 0; i < 16; i++)

  {

    Serial.print(readBuf[i]);

    Serial.print(" ");

  }

}

else Serial.println(MFR.GetStatusCodeName(status));

Bemerkung: Vor dem Lesen des Blocks muss der Sektor, der den Block enthält, zuerst mit der PCD_Authenticate()-Funktion authentifiziert werden!


byte PICC_HaltA()

Funktion: Ändert den Status eines PICCs von "Aktiv" auf "Halt"

Parameter: keine

Rückgabe: StatusCode 0 bis 8 (= Aufzählungstyp enum in der Library), Bedeutung siehe Funktion "PCD_Authenticate()"

Beispiel:

MFR.PICC_HaltA();


void PCD_StopCrypto1()

Funktion: Beendet den Authentifizierungs-Status

Parameter: keine

Rückgabe: keine

Beispiel:

MFR.PCD_StopCrypto1();


Hier nun das vollständige Programm:

//RFID Programm 2
//Anzeigen eines Sektors
//Code fuer Arduino
//Author Retian
//Version 1.1


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


#define rstPin 9
#define ssPin 10


MFRC522 MFR(ssPin, rstPin);
MFRC522::MIFARE_Key key;


MySerialRead sread;


byte sectorTrailer;
byte readBuf[18];
byte myKey[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};


void setup() {
  Serial.begin(115200);
  SPI.begin();
  MFR.PCD_Init();

  for (byte i = 0; i < 6; i++) key.keyByte[i] = myKey[i];
}


void loop() {
  Serial.print("\nWarte auf Sector Trailer: ");
  byte inWert;
  if (sread.readByte(&inWert, SERIAL_WAIT)) Serial.println(inWert);
  else
  {
    Serial.println("Fehler bei der Eingabe");
    return;
  }

  if ((inWert + 1) % 4 > 0) //Pruefe, ob inWert gueltiger Sector Trailer ist
  {
    Serial.print(inWert);
    Serial.println(" ist kein SectorTrailer!");
    return;
  }
  else sectorTrailer = inWert;

  Serial.print("Warte auf RFID-Tag ... ");
  while (!MFR.PICC_IsNewCardPresent() || !MFR.PICC_ReadCardSerial());
  Serial.println("gelesen!");

  MFRC522::PICC_Type piccType = MFR.PICC_GetType(MFR.uid.sak);
  Serial.print("Type: ");
  Serial.println(MFR.PICC_GetTypeName(piccType));
  Serial.print("UID: ");
  for (byte i = 0; i < MFR.uid.size; i++)
  {
    if (MFR.uid.uidByte[i] < 16) Serial.print("0"); //Fuehrende Null anzeigen
    Serial.print(MFR.uid.uidByte[i], HEX);
    Serial.print(" ");
  }
  Serial.println();
  Serial.print("SAK: ");
  if (MFR.uid.sak < 16) Serial.print("0"); //Fuehrende Null anzeigen
  Serial.println(MFR.uid.sak, HEX);

  byte status =

  MFR.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, sectorTrailer, &key, &(MFR.uid));
  Serial.print("Status Authentifizierung: ");
  Serial.println(MFR.GetStatusCodeName(status));

  byte sizeOfreadBuf = sizeof(readBuf);
  Serial.print("Lese Sektor ");
  Serial.print((sectorTrailer + 1) / 4 - 1); //Berechne Sektornummer aus Sector Trailer
  Serial.println("\n");
  Serial.print("Byte: ");
  for (byte i = 0; i < 16; i++)
  {
    if (i < 10) Serial.print("0"); //Fuehrende Null anzeigen
    Serial.print(i);
    Serial.print(" ");
  }
  Serial.println();
  for (byte i = 0; i < 53; i++) Serial.print("-");
  Serial.println();
  for (byte blockAdd = sectorTrailer + 1; blockAdd > sectorTrailer - 3; blockAdd--)
  {
    status = MFR.MIFARE_Read(blockAdd - 1, readBuf, &sizeOfreadBuf);
    if (status == MFRC522::STATUS_OK)
    {
      if (blockAdd - 1 < 10) Serial.print("0"); //Fuehrende Null anzeigen
      Serial.print(blockAdd - 1);
      Serial.print(":   ");
      for (byte i = 0; i < 16; i++)
      {
        if (readBuf[i] < 16) Serial.print("0"); //Fuehrende Null anzeigen
        Serial.print(readBuf[i], HEX);
        Serial.print(" ");
      }
      Serial.println();
    }
    else
    {
      Serial.print("Status Lesen: ");
      Serial.println(MFR.GetStatusCodeName(status));
      break;
    }
  }
  //  Aendert den Status von "Aktiv" auf "Halt"
  MFR.PICC_HaltA();
  // Beendet die Authentifizierung
  MFR.PCD_StopCrypto1();
}

Ausgabe am Seriellen Monitor:

Beispiel 1:

Falsche Eingabe: 12 ist kein Sector Trailer!

Block 11 ist der Sector Trailer des Sektor 2 mit den Datenblöcken 8 bis 10 (siehe Bild oben: Speicherorganisation eines 1 kByte RFID-Tags)


Beispiel 2:

Block 3 ist der Sector Trailer von Sektor 0, dieser beinhaltet die Herstellerdaten (Block 0).

Die Bytes 0 bis 3 des Block 0 enthalten die UID-Nummer des RFID-Tags.

Die Bytes von KeyA und KeyB sind im Neuzustand auf 0xFF vorgegeben. KeyA ist nicht auslesbar und wird als 0x00 angezeigt!

Vor der unsachgemäßen Änderung der Access Bits habe ich schon gewarnt. Dadurch kann ein Sektor irreversibel blockiert werden!



Zurück zu UID-SAK-Type

Weiter zu Tag-Speicher schreiben