Lesen und Schreiben von RFID-Karten
Verfasst: Di 3. Mai 2022, 16:25
1. Die Struktur des RFID-Karten-Speichers
Auf RFID-Karten lassen sich je nach Typ zwischen 8 Byte und 8 kByte an Daten speichern. Ich benutze hier eine gängige Karte mit 1 kByte Speicherplatz. Ihr Speicherplatz ist wie in Abb. 1 dargestellt strukturiert: Jeweils 16 Byte sind zu einem Block zusammengefasst; vier Blöcke bilden jeweils einen Sektor. Insgesamt gibt es 16 Sektoren (mit den Nummern 0 bis 15). Somit gibt es 16 * 4 = 64 Blöcke (mit den Nummern 0 bis 63). Da jeder Block 16 Bytes besitzt, sind auf der Karte 64 * 16 = 1024 Byte, also genau 1 kByte.
Sämtliche hier angegebenen Daten können von der Karte gelesen werden. Allerdings kann man nicht jede Zelle beschreiben: Bei den rot hinterlegte Daten handelt es sich um Daten des Herstellers; insbesondere bilden die ersten 4 (bzw. 7 Bytes) dieses Blocks den UID. Wenn diese Herstellerdaten verändert werden, ist ein weiterer Zugriff ggf. nicht mehr möglich.
Die gelb hinterlegten Blöcke mit den Nummern 3, 7, 11, ... werden auch Access-Blöcke genannt; sie beinhalten Daten über die Zugriffsrechte auf die zugehörigen Sektoren. Bei diesen Access-Blöcken bilden z. B. die Bytes mit den Nummern 0 bis 5 und die Bytes mit den Nummern 10 bis 15 jeweils einen Zugriffsschlüssel: KeyA bzw. KeyB. KeyA und KeyB bestehen werksseitig beide aus 6 0xFF-Bytes. Über diese Schlüssel erfolgt die Authentifizierung bei einem Schreib- oder Lesevorgang. Das Überschreiben eines Access-Blocks ist möglich, sollte aber unbedingt nur bei hinreichenden Kenntnissen vorgenommen werden.
Beachten Sie: Jeder Sektor hat seine eigenen Schlüssel KeyA und KeyB; auf ein und derselben Karte können also Daten mit unterschiedlichem Schlüssel gespeichert werden. In diesem Beitrag werden wir aber immer mit den werksmäßig vorgegebenen Schlüsseln [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] arbeiten.
2. Lesen eines Blocks
Das Lesen eines Blocks erfolgt in mehreren Schritten:
Dieses Programm finden Sie auch in der Anlage rfid3.zip unter dem Namen mfrc_read_block_display_1.py. Hier die Protokollierung des Lesens von Block 0:
Und wie erwartet: Die ersten vier Bytes von Block 0 bilden gerade den UID.
Schauen Sie sich auch einmal die Inhalte von anderen Blöcken an. Sollte Ihre Karte fabrikneu sein, wird das Ergebnis recht langweilig sein. Abgesehen vom Block 0 und den Access-Blöcken werden Sie nur 0x00-Bytes sehen. Eine Überraschung bieten die Access-Blöcke: Die im Terminal ausgegebenen Bytes für den KeyA sind sämtlich 0x00. Nun besteht - wie oben bereits erwähnt - die Werkseinstellung für den KeyA aus sechs 0xFF-Bytes; zudem ist ein Zugriff auf die Daten eines Blocks auch nur mit dem Schlüssel key = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] möglich, auf keinen Fall erhält man einen Zugriff mit key = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]. Wie kann das sein? Der Widerspruch löst sich auf, wenn man weiß, dass die Karte beim Auslesen von KeyA (aus Sicherheitsgründen) immer 0x00-Bytes ausgibt - unabhängig davon, wie der auf der Karte gespeicherte Schlüssel wirklich ist. Für KeyB gilt dies übrigens nicht.
3. Schreiben eines Blocks
Das Schreiben eines Blocks verläuft ganz ähnlich wie das Lesen. In unserem Beispiel-Programm wollen wir einen Text von maximal 16 Zeichen in einen Block schreiben. Dazu gehen wir ganz ähnlich wie beim Lesen vor:
Auch dieses Programm finden Sie in der Anlage rfid3.zip.
Nun sollten Sie das Programm austesten: Schreiben Sie z. B in Block 4 den Text "Hallo Welt!" (Was sonst? ). Das zugehörige Protokoll im Terminal ist:
Wenn Sie diesen Block mit dem Programm mfrc_read_block_display_1.py auslesen, erhalten Sie die folgende Ausgabe:
Um den Block als Zeichenkette auszugeben, können Sie die folgende Funktion benutzen:
Fügen Sie diese Funktion in der Funktionen-Rubrik von mfrc_read_block_display_1.py ein und ersetzen Sie die Zeile
print('Block['+str(hex(block)) + ']: ', [hex(n) for n in newdata]) # Hexadezimal
durch
show_as_string(block, newdata) # Zeichenkette
3. Alle Datenblöcke ausgeben
In dem Anhang rfid3.zip befindet sich auch eine Datei namens mfrc_read_all_blocks_1.py. Mit diesem Programm werden sämtliche Daten der RFID-Karte Block für Block angezeigt. Durch Auskommentierungen im Programm kann man die Darstellungsform bestimmen:
Auf RFID-Karten lassen sich je nach Typ zwischen 8 Byte und 8 kByte an Daten speichern. Ich benutze hier eine gängige Karte mit 1 kByte Speicherplatz. Ihr Speicherplatz ist wie in Abb. 1 dargestellt strukturiert: Jeweils 16 Byte sind zu einem Block zusammengefasst; vier Blöcke bilden jeweils einen Sektor. Insgesamt gibt es 16 Sektoren (mit den Nummern 0 bis 15). Somit gibt es 16 * 4 = 64 Blöcke (mit den Nummern 0 bis 63). Da jeder Block 16 Bytes besitzt, sind auf der Karte 64 * 16 = 1024 Byte, also genau 1 kByte.
Sämtliche hier angegebenen Daten können von der Karte gelesen werden. Allerdings kann man nicht jede Zelle beschreiben: Bei den rot hinterlegte Daten handelt es sich um Daten des Herstellers; insbesondere bilden die ersten 4 (bzw. 7 Bytes) dieses Blocks den UID. Wenn diese Herstellerdaten verändert werden, ist ein weiterer Zugriff ggf. nicht mehr möglich.
Die gelb hinterlegten Blöcke mit den Nummern 3, 7, 11, ... werden auch Access-Blöcke genannt; sie beinhalten Daten über die Zugriffsrechte auf die zugehörigen Sektoren. Bei diesen Access-Blöcken bilden z. B. die Bytes mit den Nummern 0 bis 5 und die Bytes mit den Nummern 10 bis 15 jeweils einen Zugriffsschlüssel: KeyA bzw. KeyB. KeyA und KeyB bestehen werksseitig beide aus 6 0xFF-Bytes. Über diese Schlüssel erfolgt die Authentifizierung bei einem Schreib- oder Lesevorgang. Das Überschreiben eines Access-Blocks ist möglich, sollte aber unbedingt nur bei hinreichenden Kenntnissen vorgenommen werden.
Beachten Sie: Jeder Sektor hat seine eigenen Schlüssel KeyA und KeyB; auf ein und derselben Karte können also Daten mit unterschiedlichem Schlüssel gespeichert werden. In diesem Beitrag werden wir aber immer mit den werksmäßig vorgegebenen Schlüsseln [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] arbeiten.
2. Lesen eines Blocks
Das Lesen eines Blocks erfolgt in mehreren Schritten:
- Karte detektieren (vgl. RFID RC522 - Eine Einführung)
- UID ermitteln (vgl. RFID RC522 - Eine Einführung)
- Eingabe der Nummer des zu lesenden Blocks
- Authentifizierung mit der auth-Methode (Standardschlüssel [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
- Auslesen des Blocks mit der read-Methode
- Anzeige des Blocks im Terminal (dezimal oder hexadezimal, durch Auskommentierung im Programm wählbar)
Code: Alles auswählen
# mfrc_read_block_display_1.py
# Anregungen durch: https://github.com/Tasm-Devil/micropython-mfrc522-esp32
# liest
# - die UID des Tags (RFID-Karte/Chip) und gibt sie auf dem Display und dem Terminal aus
# - einen Block des Tags und gibt ihn (als Liste) auf dem Terminal aus
# Achtung: Vor dem Start des Programms muss das Modul mfrc522.py auf den ESP32 hochgeladen werden.
from machine import Pin, SPI, SoftSPI
import st7789
import vga2_16x16 as font1
import vga1_8x16 as font2
from os import uname
release = uname().release[0:4]
id = 2 # z. B. für V 1.12 und V 1.14
if release == '1.18':
id = 1
spi = SPI(id, baudrate=20000000, polarity=1, sck=Pin(18), mosi=Pin(19)) # 1. Parameter = 1 für Firmware v1.18
display = st7789.ST7789(spi, 135, 240, reset=Pin(23, Pin.OUT), cs=Pin(5, Pin.OUT), dc=Pin(16, Pin.OUT), backlight=Pin(4, Pin.OUT), rotation=3)
display.init()
display.fill(0)
display.text(font1, 'RFID-RC522', 10, 10)
from time import sleep_ms
from mfrc522 import MFRC522
sck = Pin(17, Pin.OUT)
mosi = Pin(15, Pin.OUT)
miso = Pin(12, Pin.OUT)
softspi = SoftSPI(baudrate=100000, polarity=0, phase=0, sck=sck, mosi=mosi, miso=miso)
sda = Pin(25, Pin.OUT) # (SDA = ChipSelect)
def umlaute_ersetzen(zk):
zk = zk.replace(b'Ä', b'\x8e')
zk = zk.replace(b'ä', b'\x84')
zk = zk.replace(b'Ö', b'\x99')
zk = zk.replace(b'ö', b'\x94')
zk = zk.replace(b'Ü', b'\x9a')
zk = zk.replace(b'ü', b'\x81')
zk = zk.replace(b'ß', b'\xe1')
return zk
def show_as_string(l):
if l[0] != 124: # Liste nur dann ausgeben, wenn erstes Zeichen nicht | = chr(124) ist.
for i in l:
print(chr(i), end = '')
def do_read():
try:
while True:
rdr = MFRC522(softspi, sda)
uid = ""
(stat, tag_type) = rdr.request(rdr.REQIDL) # liefert stat (stat = 2 > Err; stat = 0 > OK)
if stat == rdr.OK: # Karte/Chip vorhanden
(stat, raw_uid) = rdr.anticoll()
# UID lesen und anzeigen:
if stat == rdr.OK:
uid = ('0x%02x%02x%02x%02x' % (raw_uid[0], raw_uid[1], raw_uid[2], raw_uid[3])) # setzt 4 Bytes zu einem "HEX-String" zusammen
# print('raw_uid: ', raw_uid) # dezimal
print('raw_uid: ', [hex(n) for n in raw_uid]) # hexadezimal
display.text(font1, uid, 10, 50)
sleep_ms(100)
# Block lesen und anzeigen
block = int(input('Block-Nr (0 - 63): '))
if block >= 64:
block = block % 64 # Blocknummer darf max. 63 betragen
print('Korrigierte Blocknummer: ', block)
if rdr.select_tag(raw_uid) == rdr.OK: # Karte selektieren
key = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] # Schlüssel für Authentifizierung
if rdr.auth(rdr.AUTHENT1B, block, key, raw_uid) == rdr.OK: # wenn Authentifizierung (KeyA) erfolgreich ist...
newdata = rdr.read(block) # Block mit der der Blocknummer "block" lesen und in newdata speichern
# print('Block['+str(block) + ']: ', newdata) # Ausgabe dezimal
print('Block['+str(hex(block)) + ']: ', [hex(n) for n in newdata]) # Ausgabe hexadezimal
show_as_string(newdata)
rdr.stop_crypto1()
else:
print('Authentifizierungsfehler') # falls Authentifizierung nicht erfolgreich
else:
print('Tag nicht vorhanden')
print()
except KeyboardInterrupt:
print()
print("Tschüss")
display.text(font1, umlaute_ersetzen(b'Tschüss! '), 5, 90)
# Hauptprogramm...:
display.text(font1, 'RFID-RC522', 5, 10)
display.text(font1, 'Karte!', 5, 50)
display.text(font2, 'Exit mit Strg-C', 5, 90)
do_read()
Code: Alles auswählen
Karte!
raw_uid: ['0x86', '0x4', '0xfa', '0x29', '0x51']
Block-Nr (0 - 63): 0
Block[0x0]: ['0x86', '0x4', '0xfa', '0x29', '0x51', '0x8', '0x4', '0x0', '0x62', '0x63', '0x64', '0x65', '0x66', '0x67', '0x68', '0x69']
Schauen Sie sich auch einmal die Inhalte von anderen Blöcken an. Sollte Ihre Karte fabrikneu sein, wird das Ergebnis recht langweilig sein. Abgesehen vom Block 0 und den Access-Blöcken werden Sie nur 0x00-Bytes sehen. Eine Überraschung bieten die Access-Blöcke: Die im Terminal ausgegebenen Bytes für den KeyA sind sämtlich 0x00. Nun besteht - wie oben bereits erwähnt - die Werkseinstellung für den KeyA aus sechs 0xFF-Bytes; zudem ist ein Zugriff auf die Daten eines Blocks auch nur mit dem Schlüssel key = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] möglich, auf keinen Fall erhält man einen Zugriff mit key = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]. Wie kann das sein? Der Widerspruch löst sich auf, wenn man weiß, dass die Karte beim Auslesen von KeyA (aus Sicherheitsgründen) immer 0x00-Bytes ausgibt - unabhängig davon, wie der auf der Karte gespeicherte Schlüssel wirklich ist. Für KeyB gilt dies übrigens nicht.
3. Schreiben eines Blocks
Das Schreiben eines Blocks verläuft ganz ähnlich wie das Lesen. In unserem Beispiel-Programm wollen wir einen Text von maximal 16 Zeichen in einen Block schreiben. Dazu gehen wir ganz ähnlich wie beim Lesen vor:
- Karte detektieren (vgl. RFID RC522 - Eine Einführung)
- UID ermitteln (vgl. RFID RC522 - Eine Einführung)
- Eingabe des Textes (maximal 16 Zeichen) (Zu lange Texte werden automatisch gekürzt; Texte mit weniger Zeichen werden zu einer Zeichenkette mit 16 Zeichen ergänzt.)
- Eingabe der Nummer des zu schreibenden Blocks (1 - 63, jedoch nicht 3, 7, 11, ...)
- Authentifizierung mit der auth-Methode (Standardschlüssel [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
- Schreiben des Blocks mit der write-Methode
- Anzeige des Blocks im Terminal (dezimal oder hexadezimal, durch Auskommentierung im Programm wählbar)
Code: Alles auswählen
# mfrc_write_text_display_1d.py für TTGO (01.06.2022)
# liest die UID des Tags (RFID-Karte/Chip) und gibt sie auf dem Display und dem Terminal aus
# schreibt einen Text (max. 16 Zeichen) in einen Block (Blocknummer NICHT 0, 3, 7, 11, ... und gibt ihn (als Liste) auf dem Terminal aus
# Quelle: https://github.com/Tasm-Devil/micropython-mfrc522-esp32; ergänzt und modifiziert für TTGO-T-Display
# Achtung: Vor dem Start des Programms muss das Modul mfrc522.py auf den ESP32 hochgeladen werden.
# Initialisierungen...
from machine import Pin, SPI, SoftSPI
import st7789
import vga2_16x16 as font1 # mit Umlauten
import vga2_8x16 as font2
from os import uname
release = uname().release[0:4]
id = 2 # z. B. für V 1.12 und V 1.14
if release == '1.18':
id = 1
spi = SPI(id, baudrate=20000000, polarity=1, sck=Pin(18), mosi=Pin(19)) # 1. Parameter = 1 für Firmware v1.18
display = st7789.ST7789(spi, 135, 240, reset=Pin(23, Pin.OUT), cs=Pin(5, Pin.OUT), dc=Pin(16, Pin.OUT), backlight=Pin(4, Pin.OUT), rotation=3)
display.init()
from time import sleep_ms
from mfrc522 import MFRC522
sck = Pin(17, Pin.OUT)
mosi = Pin(15, Pin.OUT)
miso = Pin(12, Pin.OUT)
softspi = SoftSPI(baudrate=100000, polarity=0, phase=0, sck=sck, mosi=mosi, miso=miso)
sda = Pin(25, Pin.OUT) # (SDA = ChipSelect)
# Funktionen...
def umlaute_ersetzen(zk):
zk = zk.replace(b'Ä', b'\x8e')
zk = zk.replace(b'ä', b'\x84')
zk = zk.replace(b'Ö', b'\x99')
zk = zk.replace(b'ö', b'\x94')
zk = zk.replace(b'Ü', b'\x9a')
zk = zk.replace(b'ü', b'\x81')
zk = zk.replace(b'ß', b'\xe1')
return zk
def komplettiere(b):
while len(b) < 16:
b = b + b' '
return b
def do_write():
rdr = MFRC522(softspi, sda)
try:
while True:
(stat, tag_type) = rdr.request(rdr.REQIDL)
if stat == rdr.OK:
(stat, raw_uid) = rdr.anticoll()
if stat == rdr.OK:
print("New card detected")
print(" - tag type: 0x%02x" % tag_type)
uid = '0x%02x%02x%02x%02x' % (raw_uid[0], raw_uid[1], raw_uid[2], raw_uid[3])
print(' - uid: ', uid)
print()
# Eingaben
text = input('Text (max. 16 Zeichen, keine deutschen Sonderzeichen): ')
new_data = bytes(text, 'UTF-8') # rdr.write erwartet Bytes-Objekt
# Zeichenkette soll genau 16 Zeichen besitzen...
if len(new_data) > 16:
print('Text wird gekürzt!')
new_data = new_data[:16]
else:
new_data = komplettiere(new_data)
# print(len(new_data))
# Block-Nummer eingeben
block = int(input('Blocknummer (1 - 63, jedoch nicht 3, 7, 11, ...: '))
if block >= 64:
block = block % 64 # Blocknummer darf max. 63 betragen
print('Korrigierte Blocknummer: ', block)
if block == 0: # Der Block 0 darf nicht beschrieben werden
print('Block 0 darf nicht beschrieben werden!')
break
elif (block % 4) == 3: # Die Blöcke mit den Nummern 3, 7, 11, 15, ... sind reserviert
print('Block reserviert')
break
elif rdr.select_tag(raw_uid) == rdr.OK: # Karte selektieren
key = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] # Schlüssel für Authentifizierung
if rdr.auth(rdr.AUTHENT1A, block, key, raw_uid) == rdr.OK: # wenn Authentifizierung (KeyA) erfolgreich ist, dann ...
stat = rdr.write(block, new_data) # Zeichenkette in den Block schreiben
if stat == rdr.OK:
print("Daten auf Karte geschrieben")
else:
print("Daten konnten nicht auf Karte geschrieben werden")
else:
print("Authentifikationsfehler")
rdr.stop_crypto1()
print()
ende = input('noch einmal? j/n ')
if ende == 'n':
print()
print('Tschüss')
display.text(font1, umlaute_ersetzen(b'Tschüss! '), 5, 90)
break
else:
print('Tag nicht vorhanden')
except KeyboardInterrupt:
print()
print("Tschüss")
display.text(font1, umlaute_ersetzen(b'Tschüss! '), 5, 90)
# Hauptprogramm...
display.fill(0)
display.text(font1, 'RFID-RC522', 5, 10)
display.text(font1, 'Karte!', 5, 50)
display.text(font2, 'Exit mit Strg-C', 5, 90)
do_write()
Nun sollten Sie das Programm austesten: Schreiben Sie z. B in Block 4 den Text "Hallo Welt!" (Was sonst? ). Das zugehörige Protokoll im Terminal ist:
Code: Alles auswählen
New card detected
- tag type: 0x10
- uid: 0x8604fa29
Text (max. 16 Zeichen): Hallo Welt!
Blocknummer (1 - 63, jedoch nicht 3, 7, 11, ...: 4
Daten auf Karte geschrieben
noch einmal? j/n n
Tschüss
Code: Alles auswählen
raw_uid: ['0x86', '0x4', '0xfa', '0x29', '0x51']
Block-Nr (0 - 63): 4
Block[0x4]: ['0x48', '0x61', '0x6c', '0x6c', '0x6f', '0x20', '0x57', '0x65', '0x6c', '0x74', '0x21', '0x20', '0x20', '0x20', '0x20', '0x20']
Code: Alles auswählen
def show_as_string(b, l):
print('Block['+str(b) + ']: ', end = '')
for i in l:
if i >= 32 and i <= 126:
print(chr(i), end = '')
else:
print('.', end = '')
print()
Fügen Sie diese Funktion in der Funktionen-Rubrik von mfrc_read_block_display_1.py ein und ersetzen Sie die Zeile
print('Block['+str(hex(block)) + ']: ', [hex(n) for n in newdata]) # Hexadezimal
durch
show_as_string(block, newdata) # Zeichenkette
3. Alle Datenblöcke ausgeben
In dem Anhang rfid3.zip befindet sich auch eine Datei namens mfrc_read_all_blocks_1.py. Mit diesem Programm werden sämtliche Daten der RFID-Karte Block für Block angezeigt. Durch Auskommentierungen im Programm kann man die Darstellungsform bestimmen:
- Liste von Dezimalzahlen
- Liste von Hexadezimalzahlen
- Zeichenkette