Martedì, 05 Febbraio 2013 07:06

Lettura dei dati della Tessera Sanitaria con Python

Scritto da
Vota questo articolo
(2 Voti)

Obbiettivo: accedere ai record anagrafici della smartcard (TSN) rilasciata dal Servizio Sanitario Nazionale (SSN) senza dover conoscere per forza tutta la ISO 7816.

Scrivo questo articolo perché non ho ancora trovato niente che spieghi come farlo in modo semplice ed intuitivo.

Strumenti:

  • linguaggio di programmazione python (versione 2.7 ma anche la 3 dovrebbe andar bene)
  • libreria python pyscard per accedere parlare con la smartcard
  • un lettore di smartcard "conosciuto"
  • l'editor di testo del cuore
  • una ventina di documenti sulle smart card e l'APDU, in più lingue, da tenere conunque a portata di mano

Procediamo!

La prima parte del codice che segue serve solo per aprire la comunicazione tra il lettore di smart card (con card già inserita).

from smartcard.System import readers
import array

r = readers()
reader = r[0]
print "Sto usando: ", reader
connection = reader.createConnection()
connection.connect()

Il codice è solo a scopo dimostrativo. Non fa alcun test sull'esito della connessione e si collega al primo reader che dovesse trovare.

Se tutto è andato bene fino a qui, possiamo proseguire. Ora bisogna aprire il file contenente le informazioni anagrafiche. Per farlo sarà necessario inviare un comando APDU (Application Protocol Data Unit).
Quello che segue è la teoria semplificata dell'accesso alla lettura delle smart card. Per lo scopo dell'articolo, si può allegramente saltare.

I comandi APDU sono formati da 7 campi:
-CLA: Classe dell'istruzione
-INS: Istruzione
-P1 e P2: parametri 1 e 2
-Lc: Dimensione dei dati del comando o Lenght of the Command data
-Data: I dati del comando
-Le: Dimensione attesa della risposta o Length of the Expected response

La classe di istruzioni che ci interessa è la 0 (o 0x00 in esadecimale).
Le istruzioni che ci serviranno sono la 0xA4 (selezionare un file) e la 0xB0 (leggere il contenuto di un file.

Il filesystem della smartcard è del tutto simile a quello del computer.
Prevede un MF o Master Folder o radice del filesystem. All'interno dell'MF si trovano sia EF o Elementary File o file semplici sia DF o Directory File o sotto cartelle.

Il file che ci interessa si trova al seguente path:
MF/DF1/EF_Dati_personali
Cioè, la cartella DF1, che sta all'interno della radice, ospita il file EF_Dati_personali che contiene le nostre informazioni.
Cartelle e file hanno un ID ben definito, univoco e uguale in tutte le TSN del SSN.
Per l'MF, l'id è 0x3F00, per DF1 è 0x1100 e per il file EF_Dati_personali è 0x1102.

Probabilmente esiste un sistema per accedere al filesystem in modo semplice come nei normali filesysem ma, fino a che non lo trovo, l'unico modo che sono riuscito ad usare è stato quello di spostarmi passo passo
con gli id.
Ecco come procedere (qui i pigri possono ricominciare a leggere):

#Seleziona del MF
#CLS 00, istruzione A4 (seleziona file), P1 = P2 = 0 (seleziona per ID),
#Lc: 2, Data: 3F00 (id del MF)
SELECT_MF = [0x00, 0xA4, 0x00, 0x00, 0x02, 0x3F, 0x00]
data, sw1, sw2 = connection.transmit(SELECT_MF)
#se tutto è andato a buon fine sw1 e sw2 contengono
#rispettivamente i valori 0x90 e 0x00 il corrispettivo del 200 in HTTP

#Seleziona del DF1...vedi sopra
SELECT_DF1 = [0x00, 0xA4, 0x00, 0x00, 0x02, 0x11, 0x00]
data, sw1, sw2 = connection.transmit(SELECT_DF1)

#Seleziona del file EF.Dati_personali... vedi sopra sopra
SELECT_EF_PERS = [0x00, 0xA4, 0x00, 0x00, 0x02, 0x11, 0x02]
data, sw1, sw2 = connection.transmit(SELECT_EF_PERS)

#leggiamo i dati
#CLS 00, istruzione B0 (leggi i dati binari contenuti nel file
READ_BIN = [0x00, 0xB0, 0x00, 0x00, 0x00, 0x00]
data, sw1, sw2 = connection.transmit(READ_BIN)
#data contiene i dati anagrafici in formato binario
#trasformiamo il tutto in una stringa
stringa_dati_personali = array.array('B', data).tostring()

La stringa restituita è un mix di esadecimali e testo ascii. Eccone un esempio:

000063080460020803042012080104201804SABA04LUCA08010197001M00....

I primi 6 caratteri contengono la dimensione in numero di byte del dato espresso in esamedicmale.
Quindi:

dimensione = int(stringa_dati_personali[0:6],16)
print "Dimensione in byte dei dati: %d" % dimensione

scriverà la dimensione del dato (99 byte in questo esampio: 0x63=99).
Il resto del testo è da leggersi nel seguente modo:
i primi due caratteri esprimono la dimensione del campo sucessivo (sempre
in numero di byte in esadecimale). Se il dato non c'è, come nel caso dell'altezza,
la dimensione è 0.

I campi estraibili dal file dei dati personale sono i seguenti:

  • Codice Emettitore
  • Data di emissione del documento Formato GGMMAAAA
  • Data di scadenza del documento Formato GGMMAAAA
  • Cognome (Max 26 caratteri)
  • Nome (Max 26 caratteri)
  • Data di Nascita Formato GGMMAAAA
  • Sesso 'M' per maschio, 'F' per femmina
  • Statura (cm)
  • Codice fiscale
  • Cittadinanza
  • Comune di Nascita
  • Stato estero di Nascita
  • Estremi atto di nascita
  • Comune di residenza al momento dell’emissione
  • Indirizzo di residenza
  • Eventuale annotazione in caso di non validità del documento per l’espatrio

Perciò, ecco il codice (parziale) per leggere il resto dei dati:

prox_field_size = int(stringa_dati_personali[6:8], 16)
da = 8
a = da + prox_field_size
if prox_field_size > 0:
  codice_emettitore = stringa_dati_personali[da:a]
  print "Codice emettitore: ", codice_emettitore

da = a
a +=2
prox_field_size = int(stringa_dati_personali[da:a], 16)
da=a
a += prox_field_size
if prox_field_size > 0:
  data_rilascio_tessera = stringa_dati_personali[da:a]
  print "Data rilascio tessera: ", data_rilascio_tessera[0:2]+"/"+data_rilascio_tessera[2:4]+"/"+data_rilascio_tessera[-4:]

da = a
a +=2
prox_field_size = int(stringa_dati_personali[da:a], 16)
da=a
a += prox_field_size
if prox_field_size > 0:
  data_scadenza_tessera = stringa_dati_personali[da:a]
  print "Data scadenza tessera: ", data_scadenza_tessera[0:2]+"/"+data_scadenza_tessera[2:4]+"/"+data_scadenza_tessera[-4:]

da = a
a +=2
prox_field_size = int(stringa_dati_personali[da:a], 16)
da=a
a += prox_field_size
if prox_field_size > 0:
  cognome = stringa_dati_personali[da:a]
  print "Cognome: ", cognome

Riferimenti:

Letto 30300 volte Ultima modifica il Mercoledì, 28 Dicembre 2016 08:54

1 commento

  • Link al commento SC Giovedì, 20 Novembre 2014 12:43 inviato da SC

    ciao luca ho provato ad usare il codice che hai messo con la mia tessera sanitaria e un leccore scm 3310, ma non mi funziona ossia non mi stampa nulla si connette ma non preleva alcun dato

    in particolare stringa_dati risulta vuoto

    Rapporto

Lascia un commento

Assicurati di aver digitato tutte le informazioni richieste, evidenziate da un asterisco (*). Non è consentito codice HTML.

Login Form