Lesen und Schreiben von RFID-Karten

Hier werden einzelne Projekte mit MicroPython vorgestellt
Antworten
Heinrichs
Beiträge: 151
Registriert: Do 21. Okt 2010, 18:31

Lesen und Schreiben von RFID-Karten

Beitrag von Heinrichs » 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.

rfid_card_struktur_klein.jpg
Abb. 1: Datenstruktur einer RFID-Karte
rfid_card_struktur_klein.jpg (92.59 KiB) 292 mal betrachtet

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:
  1. Karte detektieren (vgl. RFID RC522 - Eine Einführung)
  2. UID ermitteln (vgl. RFID RC522 - Eine Einführung)
  3. Eingabe der Nummer des zu lesenden Blocks
  4. Authentifizierung mit der auth-Methode (Standardschlüssel [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
  5. Auslesen des Blocks mit der read-Methode
  6. Anzeige des Blocks im Terminal (dezimal oder hexadezimal, durch Auskommentierung im Programm wählbar)
Das Programm sieht dann folgendermaßen aus:

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()
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:

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']
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:
  1. Karte detektieren (vgl. RFID RC522 - Eine Einführung)
  2. UID ermitteln (vgl. RFID RC522 - Eine Einführung)
  3. 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.)
  4. Eingabe der Nummer des zu schreibenden Blocks (1 - 63, jedoch nicht 3, 7, 11, ...)
  5. Authentifizierung mit der auth-Methode (Standardschlüssel [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
  6. Schreiben des Blocks mit der write-Methode
  7. Anzeige des Blocks im Terminal (dezimal oder hexadezimal, durch Auskommentierung im Programm wählbar)
Mein zugehöriges Programm mfrc_write_text_display_1d.py sieht dann so aus:

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()
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? :lol: ). 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
Wenn Sie diesen Block mit dem Programm mfrc_read_block_display_1.py auslesen, erhalten Sie die folgende Ausgabe:

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']
Um den Block als Zeichenkette auszugeben, können Sie die folgende Funktion benutzen:

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
.
Dateianhänge
rfid3.zip
Programme
(7.5 KiB) 13-mal heruntergeladen

Antworten