Auslesen der eGK mit Python

30. Januar 2020
Mit der Einführung der ePA (elektronische Patientenakte) schreitet die Digitalisierung des deutschen Gesundheitssystems mit großen Schritten voran. Einer der wichtigsten Schritte im Rahmen des Rollouts ist die Ausgabe der eGK (elektronische Gesundheitskarte) an alle Kunden der gesetzlichen Krankenkassen. Wofür die eGK verwendet wird, welche Daten auf ihr gespeichert sind und wie man die Versicherungsstammdaten mit einem Python Skript ausliest, soll in diesem Blogpost behandelt werden.

Was ist eine elektro­nische Patienten­akte/Gesund­heits­karte?

Im Gegensatz zu ihrem Vorgänger, der KVK (Krankenversicherungskarte), kann die eGK mehr als nur für die Abrechnung verwendet zu werden. Sie dient zur Authentifizierung von Patienten in Praxen und Kliniken. Mithilfe der Karte und einer zugehörigen PIN ist es Patienten möglich, ihrem behandelnden Mediziner Zugriff auf die eigene ePA zu geben. 
Hierbei wird es zu Beginn so sein, dass ein Arzt Zugriff auf die komplette Akte bekommt, später ist ein beschränkter denkbar. Außerdem können vom behandelnden Mediziner Daten bzw. Dokumente wie Röntgenbilder, MRTs oder Arztbriefe in der elektronischen Akte des Patienten gespeichert werden. Die eigene Akte kann der Patient dann beispielsweise bei einer Überweisung an einen anderen Arzt auch via Gesundheitskarte freigeben.

Die elektronischen Patientenakten werden in der Telematikinfrastruktur (TI) gespeichert. Zugriff auf diese erhält man nur mit entsprechendem Heilberufsausweis. Dieser kommt wie die eGK in Form einer Chipkarte und dient zur Identifizierung eines Arztes. 

Mehr Details zu dem Aufbau, der Funktionsweise und den Problemen der TI sind anschaulich in diesen beiden Vorträgen erklärt:
 

„Hacker hin oder her“: Die elektronische Patientenakte kommt! 

(Quelle: YouTube-Kanal media.ccc.de; Martin Tschirsich cbro, Dr. med. Christian Brodowski, Dr. André Zilch)


15 Jahre deutsche Telematikinfrastruktur (TI)

(Quelle: YouTube-Kanal media.ccc.de; Christoph Saatjohann)

 

Technische Betrachtung der eGK

Vor der Implementierung eines Python Skripts zum Auslesen der Gesundheitskarte sollte ein Verständnis über die technischen Grundlagen entwickelt werden. Betrachtet werden also die physische Karte sowie die Datenstrukturen auf dem Chip selbst.


Äußerlichkeiten

Die eGK ist eine einfache Chipkarte, eine sogenannte „Smart Card“ oder ICC (Integrated Circuit Card). Neben dem Chip sind Informationen über den Karteninhaber auf die Oberfläche gedruckt.

Ein weitere interessante Information stellt das kleine „G2“ in der oberen rechten Ecke der Karte da. Dieses steht für „Generation 2“ und stellt somit die Version der Karte dar. Darüber hinaus befindet sich auf der Vorderseite sichtbar der Chip, auf welchem alle Daten gespeichert sind.


So sieht beispielsweise eine Vorderseite und eine Rückseite aus:
 

Muster: Gesundheitskarte


Auf der Rückseite findet man den Namen des Besitzers, den Firmennamen der Versicherung, eine Kennziffer für Besitzer und eine für die Karte, sowie das Geburtsdatum des Besitzers und das Ablaufdatum der Karte.


Daten auf der eGK

Wie bereits erwähnt handelt es sich im eine sogenannte „Smart Card“. Diese werden hauptsächlich als ROM  (Read Only Memory) verwendet - also um Zugriff auf Daten zu geben, nicht jedoch um neu beschrieben zu werden. Die eGK ist jedoch nicht nur als ROM ausgelegt, sondern kann auch neu beschrieben werden, beispielsweise um die Versicherungsstammdaten des Besitzers zu aktualisieren (z. B. bei Umzug oder nach Namensänderung).

Daten auf der eGK sind hierarchisch abgelegt. Dabei wird in Verzeichnisse (DF) und Datenspeicher (EF) unterschieden. Interessant ist an dieser Stelle vorrangig das Verzeichnis HCA (Health Care Application). In diesem befinden sich die Datenspeicher PD (persönliche Versichertendaten) und VD (Versicherungsdaten). (Quelle: Fachportal gematik )

Neben diesen sind noch weitere Datenspeicher und Verzeichnisse verfügbar - diese sind hervorragend in einem Artikel des Fraunhofer-Instituts für offene Kommunikationssysteme erklärt:  BMG AsK

 

Daten auf der eGK

 

Daten von Smart Cards lesen

Um Daten von einer Smart Card zu lesen, benötigt man zunächst ein Kartenlesegerät. Man benötigt jedoch kein spezielles Lesegerät für die Gesundheitskarte - eine kurze Suche im Internet nach einem „Chipkartenleser“ oder einem „Smart Card Reader“ liefert viele Ergebnisse.

Die Kommunikation zwischen dem Lesegerät und der Karte erfolgt mittels APDU (Application Protocol Data Unit). Die Struktur von APDU ist in der Norm ISO/IEC 7816-4 definiert. Man unterscheidet zwischen Command APDU und Response APDU.

Command APDU übertragen ein Kommando oder einen Befehl von dem Kartenlesegerät an die Chipkarte. Dieser besteht aus einem Header und einem optionalen Body. Response APDU dienen als Antwort der Karte auf ein Command APDU. Dieser besteht wiederum aus einem Trailer und einem optionalen Body. Der Aufbau im Detail ist im folgenden Wikipedia Artikel gut erklärt:  Application Protocol Data Unit – Wikipedia

 

Auslesen via Python Skript

Zum Auslesen der elektronischen Gesundheitskarte wird ein Kartenlesegerät sowie ein funktionstüchtiger Python Interpreter benötigt. Zudem legen wir uns den „Implementierungsleitfaden zur Einbindung der eGK in die Primärsysteme der Leistungserbringer“ der gematik parat: Fachportal gematik


APDU via Python

Sucht man nach Paketen, um APDU Kommandos via Python abzusetzen oder mit Smart Cards zu kommunizieren, kommt man nicht an dem Paket pyscard Python Smart Card Library vorbei. Die Bibliothek ermöglicht uns genau das, was wir wollen - via Python APDU Kommandos abzusenden und zu empfangen. Also erstellen wir ein virtuelle Umgebung (VirtualEnv) und installieren das Paket dort.

virtualenv -p python3 ~/egk
pip3 install pyscard

Nach der Installation erzeugen wir eine Python Datei - /egk.py/:

touch egk.py

In dieser wird nun der Code zum Auslesen der Karte untergebracht. Als erstes gilt es nun die Verbindung zu Lesegerät herzustellen:

from smartcard.System import readers
class HealthCardReader:
    def init(self):
        r = readers()
        if len(r) < 1:
        raise Exception('No reader found.')
        self.reader = r[0]
        self.connection = self.reader.createConnection()

        self.connection.connect()

healthcard_reader = HealthCardReader()

An dieser Stelle nutzen wir die `readers` Funktion der pyscard-Bibliothek. Sie gibt uns eine Liste von verfügbaren Kartenlesegeräten zurück. Sollte keines verfügbar sein, wird eine Exception ausgelöst. Ansonsten erhält unser `HealthCardReader` Objekt eine `connection`. Mithilfe dieser können wir nun Command APDUs an die Karte übertragen:

...
healthcard_reader = HealthCardReader()
healthcard_reader.connection.transmit(apdu)


Das weitere Vorgehen ergibt sich aus der Spezifikation der gematik:

Dieses Diagramm stellt den Programmablauf zum Auslesen von persönlichen Daten bzw. Versicherungsdaten dar.

Daten auf der eGK


Die entsprechenden APDU- Kommandos können ebenfalls aus der Spezifikation entnommen werden:

COMMANDS = {
    'SELECT_MF': [0x00, 0xA4, 0x04, 0x0C, 0x07, 0xD2, 0x76, 0x00, 0x01, 0x44, 0x80, 0x00],
    'SELECT_HCA': [0x00, 0xA4, 0x04, 0x0C, 0x06, 0xD2, 0x76, 0x00, 0x00, 0x01, 0x02],
    'EF_VERSION_1': [0x00, 0xB2, 0x01, 0x84, 0x00],
    'EF_VERSION_2': [0x00, 0xB2, 0x02, 0x84, 0x00],
    'EF_VERSION_3': [0x00, 0xB2, 0x03, 0x84, 0x00],
    'SELECT_FILE_PD': [0x00, 0xB0, 0x81, 0x00, 0x02],
    'SELECT_FILE_VD': [0x00, 0xB0, 0x82, 0x00, 0x08]
}

Die Begriffe Root und MF (Masterfile) können hier als äquivalent angesehen werden. Zuerst erweitern wir unsere Klasse um ein paar Utilities:

class HealthCardReader:
    def init(self):
        r = readers()
        if len(r) < 1:
        raise Exception('No reader found.')
        self.reader = r[0]
        self.connection = self.reader.createConnection()
        self.connection.connect()

  def create_read_command(self, pos, length):
    bpos = [pos >> 8 & 0xFF, pos & 0xFF]
    return [0x00, 0xB0, bpos[0], bpos[1], length]

    def read_file(self, offset, length):
    data = []
    max_read = 0xFC
    pointer = offset
    while len(data) < length:
        bytes_left = length - len(data)
        readlen = bytes_left if bytes_left < max_read else max_read
        data_chunk = self.run_command(self.create_read_command(pointer, readlen))
        pointer += readlen
        data.extend(data_chunk)
    return data

    def run_command(self, apdu):
        data, sw1, sw2 = self.connection.transmit(adpu)
        if (sw1, sw2) == (0x90, 0x00):
            return data
        raise Exception('Bad Status')

Mithilfe der neuen Utils können wir bequem Kommandos absetzen und Dateien von der Karte lesen. `create_read_command` erlaubt es uns ein Command APDU zu erstellen, um einen bestimmten Bereich einer Datei zu lesen. `read_file` wiederum erlaubt es uns eine komplette Datei auszulesen. `run_command` erlaubt es uns jedes mal automatisch zu testen, ob unser Kommando erfolgreich war und wenn dies der Fall ist, das Ergebnis zurückzugeben. Nun können wir loslegen, um die Version der Karte zu erkennen und die PD auszulesen:

healthcard_reader = HealthCardReader()
healthcard_reader.run_command(COMMANDS['SELECT_MF'])
ef_v_1 = healthcard_reader.run_command(COMMANDS['EF_VERSION_1'])
ef_v_2 = healthcard_reader.run_command(COMMANDS['EF_VERSION_2'])
ef_v_3 = healthcard_reader.run_command(COMMANDS['EF_VERSION_3'])

if ef_v_1 == '3.0.0' and ef_v_2 == '3.0.0' and ef_v_3 == '3.0.2':
    generation = 'G1'
elif ef_v_1 == '3.0.0' and ef_v_2 == '3.0.1' and ef_v_3 == '3.0.3':
    generation = 'G1 plus'
elif ef_v_1 == '4.0.0' and ef_v_2 == '4.0.0' and ef_v_3 == '4.0.2':
    generation = 'G2'

# Selektieren der PD
healthcard_reader.run_command(COMMANDS['SELECT_HCA'])
healthcard_reader.run_command(COMMANDS['SELECT_FILE_PD'])

# Auslesen der ersten beiden Bytes - diese enthalten die Länge der PD
data = healthcard_reader.run_command(healthcard_reader.create_read_command(0x00, 0x02))
pd_length = (data[0] << 8) + data[1]
# Abziehen der ersten beiden Bytes
pd_length -= 0x02

# Nochmaliges Selektieren der PD
healthcard_reader.run_command(COMMANDS['SELECT_MF'])
healthcard_reader.run_command(COMMANDS['SELECT_HCA'])
healthcard_reader.run_command(COMMANDS['SELECT_FILE_PD'])

# Auslesen der komprimierten Daten nach den ersten beiden Bytes, mit der emittelten Länge
patient_data_compressed =  healthcard_reader.read_file(0x02, pd_length)


Entpacken der Daten mit Python-Standardfunktionen

Nachdem Rohdaten von der Karte extrahiert sind müssen diese noch entpackt werden, da diese aktuell noch via gzip komprimiert vorliegen.

# Vorbeugen von zlib.error (truncated stream)
patient_data_compressed.extend([0x00] * 16)
patient_data_compressed = bytearray(patient_data_compressed)
patient_data_compressed = bytes(patient_data_compressed)
# Dekomprimieren der Rohdaten
patient_data_xml = zlib.decompress(patient_data_compressed, 15 + 16)

Damit liegen die persönlichen Daten des Patienten im XML-Format vor. Diese können nun nach belieben via `lxml` oder einer anderen XML-Bibliothek verarbeitet werden.

 

Blueshoe veröffentlicht python-healthcard

Alle Erfahrungen, die wir als Blueshoe während unserer Arbeit mit der eGK gemacht haben sowie alle zu beachtenden Tücken, haben wir in einem Paket zum Auslesen der elektronischen Gesundheitskarte zusammengefasst. Das Paket python-healthcard ist OpenSource aus dem Hause Blueshoe und wird auch fortlaufend betreut.

python-healthcard gewährt dem Benutzer Zugang  zu den persönlichen Daten sowie den Versicherungsdaten, welche auf einer eGK gespeichert sind. In unserem Projekt “Ofa Smart Scan” - einem digitalen Körperscanner für den Sanitätsfachhandel - findet die python-healthcard aktuell bereits Anwendung. Die Technologie erlaubt es, neue Kunden eines Sanitätshauses mitsamt ihrer persönlichen Daten innerhalb von Sekunden anzulegen und direkt einen Messvorgang für beispielsweise Kompressionsstrümpfe anzustoßen.

Wir freuen uns über Verbesserungsvorschläge, Lob und Kritik - gerne via GitHub oder per Mail an healthcard@blueshoe.de