Zugangskontrolle mit RFID-Karte und Passwort

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

Zugangskontrolle mit RFID-Karte und Passwort

Beitrag von Heinrichs » So 29. Mai 2022, 14:46

Schon in dem Beitrag RFID RC522 - Eine Einführung war eine einfache Zugangskontrolle mit der RDFID-Karte vorgestellt worden. Bei dieser wurde der UID der RFID-Karte ausgelesen; befand sich diese in einer entsprechenden Liste im Programm, so wurde ein Zugang gewährt, sonst nicht. Diese Vorgehensweise ist natürlich nicht sehr sicher: Jeder, der die Karte an sich bringt, kann damit Zugang erlangen.

aufbau_access.jpg
Abb. 1
aufbau_access.jpg (152.78 KiB) 1398 mal betrachtet

Besser ist es, wenn der Zugang durch ein Passwort (oder eine PIN), die nur der Eigner der Karte kennt, geschützt ist. Dieses Passwort darf nicht auf der Karte vorliegen (zumindest nicht in einem ungeschützten Speicherbereich). Deswegen gehen wir folgendermaßen vor:

Der "Zugangsadministrator" startet das Programm mfrc_write_name_hash_1.py (s. u.) und lässt den Karten-Nutzer seinen Namen und sein gewünschtes Passwort am Terminal eingeben. Das Programm schreibt den Namen in Block 4 der Karte. Aus dem Passwort berechnet es nun mit einem Hash-Algorithmus einen so genannten Hash-Wert, welcher nun in Block 5 der Karte gespeichert wird. Außerdem fügt der Zugangsadministrator (der das Passwort NICHT erfährt) diesen Hash-Wert zusammen mit dem Namen in die Datenliste des Zugangs-Programms ein.

Wichtig ist: Das Passwort kann nicht aus dem Hash-Wert rekonstruiert werden. Zudem führen selbst Passwörter, die sich nur in einem einzigen Zeichen unterscheiden, zu sehr unterschiedlichen Hash-Werten. Umgekehrt kann aber mit demselben Algorithmus aus dem Passwort der Hash-Wert neu berechnet werden. Das nutzt das für die Zugangskontrolle eingesetzte Programm mfrc_access_control_2.py aus: Sobald der Kunde seine RFID-Karte vor das RFID-Modul hält, liest es den Namen und den Hash-Wert aus. Nun fragt es nach dem Passwort. Dieses muss der Kunde nun am Terminal eingeben. (Wer möchte, kann statt des Terminals ein anderes Gerät zur Eingabe benutzen; so könnte z. B. über das TM1638-Anzeige-Taster-Modul numerische Passwörter eingegeben werden. Damit würde die Einheit TTGO - RFID-Modul - TM1638 unabhängig von einem PC als Zugangskontrolle arbeiten können. Der Einfachheit halber verfolgen wir diesen Weg an dieser Stelle aber nicht weiter.) Unser Programm für die Zugangskontrolle berechnet nun aus diesem Passwort mit demselben Hash-Algorithmus den Hashwert und vergleicht ihn mit dem Wert, der in der Datenliste vorliegt. Stimmen beide Werte überein, so wird ein Zugang gewährt (Grüne LED leuchtet auf, vgl. Abb. 1.) oder nicht (Rote LED leuchtet auf.).

Hier der Source-Code von mfrc_write_name_hash_1.py:

Code: Alles auswählen

# mfrc_write_name_hash_1.py für TTGO

# liest die UID des Tags (RFID-Karte/Chip) und gibt sie auf dem Display und dem Terminal aus
# speichert einen eingegebenen Benutzer-Namen (maximal 16 Zeichen, keine deutschen Sonderzeichen) auf der RFID-Karte ab (Block 4).
# verwandelt ein eingegebenes Passwort in einen 16-Byte-Hashwert und speichert diesen auf der RFID-Karte ab (Block 5).

# Quelle: https://github.com/Tasm-Devil/micropython-mfrc522-esp32; ergänzt und modifiziert für TTGO-T-Display

# Initialisierungen...

from sha256 import make_short_hash256
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(data):
    rdr = MFRC522(softspi, sda)
    block = 4 # mit Block 4 starten
    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)
                    display.text(font1, uid, 5, 50)
                    # print('BlockNr: ', block)
                    display.text(font1, 'Block: ' + str(block), 5, 90)
                    new_data = data[block-4]
                    # print('block: ', block, ' new_data: ', new_data)
                    if rdr.select_tag(raw_uid) == rdr.OK:
                        key = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]
                        if rdr.auth(rdr.AUTHENT1A, block, key, raw_uid) == rdr.OK:
                            stat = rdr.write(block, new_data)
                            rdr.stop_crypto1()
                            if stat == rdr.OK:
                                print('Daten von Block', str(block), 'auf Karte geschrieben')
                            else:
                                print('Daten konnten nicht auf Karte geschrieben werden')
                        else:
                            print('Authentifikationsfehler')
                        block += 1
                        if block > 5:
                            break    
                    else:
                        print('Tag nicht vorhanden')
            print('Karte!')            
        print('Datenübertragung abgeschlossen')

    except KeyboardInterrupt:
        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)

# Namen
name = input('Name (max. 16 Zeichen, keine deutschen Sonderzeichen): ')
name = bytes(name, 'UTF-8') # rdr.write erwartet Bytes-Objekt
# Zeichenkette soll genau 16 Zeichen besitzen...
if len(name) > 16:
    print('Text wird gekürzt!')
    name = name[:16]
else:
    name = komplettiere(name)
    

# Passwort/Hash
passwort = input('Passwort (max. 16 Zeichen, keine deutschen Sonderzeichen): ') # später: Hash-Wert
hash_str = make_short_hash256(passwort)
print('Hash: ', hash_str)
hash_str = bytes(hash_str, 'UTF-8') # rdr.write erwartet Bytes-Objekt
    
data = [name, hash_str]

do_write(data) 
Es folgt der Source-Code für das Zugangsprogramm mfrc_access_control_2.py:

Code: Alles auswählen

# mfrc_access_control_2.py für TTGO

# liest Block 4 (Name) und Block 5 (Hash-Wert) von der Karte*)
# zeigt Namen auf dem Display an
# verlangt Passwort-Eingabe (über die Tastatur) und bildet davon den Hash-Wert
# vergleicht die beiden Hashwerte und gewährt Zugang (Grüne LED leuchtet für 3 Sekunden), ansonsten wird er verweigert (Rote LED leuchtet für 3 Sekunden).

# *) zum Schreiben dieser Karte das Programm mfrc_write_name_hash.py benutzen (generiert automatisch aus dem eingegeben Passwort einen Hashwert)  

# Initialisierungen...

from sha256 import make_short_hash256
from time import sleep_ms
from machine import Pin, SPI, SoftSPI
from mfrc522 import MFRC522

# Display initialisieren
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)

# RFID initialisieren
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)

# LEDs initialisieren
gruene_LED = Pin(27, Pin.OUT)
rote_LED = Pin(26, Pin.OUT)

# Daten
datenliste = [['Anne            ', 'f3a000752ccc201a'], # Passwort???
              ['Bert            ', 'cd89a4f160666ba1'], # Passwwort: qwert123
              ['Georg           ', '958d51602bbfbd18']  # Passwort: 123456
             ] # Die Zeichenketten müssen jeweils aus 16 Zeichen bestehen

# 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 access(name, hashwert, datenliste):
    if [name, hashwert] in datenliste: # falls name mit hashwert in datenliste auftaucht
        print('Zugang!')
        gruene_LED.value(1) # gruene LED einschalten
    else: # sonst ...
        print('Kein Zugang')
        rote_LED.value(1) # rote LED einschalten
    print('Karte entfernen!')    
    sleep_ms(3000)
    gruene_LED.value(0) # LEDs ausschalten
    rote_LED.value(0)

def make_to_string(l):
    bs = bytes(l)
    return str(bs, 'UTF-8')

def do_read(block):
    weiter = True
    try:
        while weiter:
            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
                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.AUTHENT1A, 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
                        # print('Block['+str(block) + ']: ', make_to_string(newdata)) # Ausgabe als Zeichenkette
                        return make_to_string(newdata)
                        rdr.stop_crypto1()
                        weiter = False
                    else:
                        print('Authentifizierungsfehler') # falls Authentifizierung nicht erfolgreich
                else:
                    print('Tag nicht vorhanden')    
                print()
            else:
                print('Karte!')

                
    except KeyboardInterrupt:
        print()
        print("Tschüss")
        display.text(font1, umlaute_ersetzen(b'Tschüss!     '), 5, 90)

# Hauptprogramm...

print('Warte auf Karte')
print('Exit mit Strg-C')
print()

display.text(font1, 'RFID-RC522', 30, 10)
display.text(font1, 'Karte!', 5, 50)
display.text(font2, 'Exit mit Strg-C', 5, 90)

try:
    name = do_read(4)
    print('Name: ', name)
    display.text(font2, 'Hallo ' + name, 5, 50)
    hashwert = do_read(5)
    passwort = input('Passwort: ')
    access(name, make_short_hash256(passwort), datenliste)

except KeyboardInterrupt:
    print()
    print("Tschüss")
    display.text(font1, umlaute_ersetzen(b'Tschüss!     '), 5, 90)

Beide Programme befinden sich auch im Anhang, zusammen mit dem für die Hash-Funktion benötigten Modul sha256.py.

.
Dateianhänge
rfid4.zip
Programme
(6.75 KiB) 176-mal heruntergeladen

Antworten