Karty RFID
Czytelników zainteresowanych tematyką kart zbliżeniowych odsyłam do normy ISO/IEC 14443 (opens new window), dokument ten składa się z czterech części:
- ISO/IEC 14443-1:2008 – Parametry fizyczne karty, takie jak wymiary, właściwości elektryczne, poziomy pamięć.
- ISO/IEC 14443-2:2010 – Częstotliwości komunikacji radiowej.
- ISO/IEC 14443-3:2011 – Algorytm wyboru karty, algorytm unikania kolizji.
- ISO/IEC 14443-4:2008 – Protokół komunikacyjny.
Termin Mifare odnosi się do kilku różnych typów kart zbliżeniowych produkowanych przez firmę NXP. Niniejszy wpis będzie poświęcony dwóm z nich.
MIFARE Classic oraz MIFARE Ultralight
Karty te nie są całkowicie zgodne ze specyfikacją ISO/IEC 14443. Podlegają części 1, 2, 3, ale już nie części 4. Komunikacja z kartą odbywa się przy pomocy dedykowanego protokołu firmy NXP, natomiast dla wygody użytkowników wiele czytników emuluje komunikaty zgodne z ISO/IEC 14443-4.
MIFARE Classic
To karta pamięciowa z pewnymi funkcjami kontroli dostępu. Na chwile obecną nadal szeroko stosowana, pomimo faktu że algorytm kontroli dostępu do karty został złamany. Pierwszy blok danych służy tylko do odczytu, jest tam przechowywany numer seryjny karty. Tę funkcjonalność często wykorzystuje się do identyfikacji użytkownika. Ponieważ karta nie zapewnia w chwili obecnej poprawnej weryfikacji praw dostępu, funkcje weryfikacji integralności danych muszą być zapewnione przez warstwę aplikacji, np. poprzez kryptograficzne sumy kontrolne (tam gdzie to możliwe). Nota katalogowa (opens new window) karty zawiera szczegółowe informacje o prawach dostępu, strukturze pamięci oraz API karty. Oto mapa pamięci karty 1K:
Na diagramie widzimy:
- 16 sektorów, każdy po 4 bloki o rozmiarze 16 bajtów,
- pierwszy blok tylko do odczytu,
- ostatni blok każdego sektora zawiera dwa klucze oraz prawa dostępu do danego sektora.
MIFARE Ultralight
To znacznie prostsza karta nieposiadająca żadnego systemu weryfikacji dostępu. Wyposażona jest w 512 bitów pamięci, fabrycznie ustawiony numer seryjny oraz bity bezpiecznikowe, umożliwiające trwałe i nieodwracalne zablokowanie możliwości zapisywania na kartę. Dokumentacja karty (opens new window) przedstawia mapę pamięci:
Na diagramie widzimy:
- Numer seryjny karty zapisany 7 bajtów plus 2 na sumę kontrolną,
- 2 bajty bitów bezpiecznikowych pozwalających blokować możliwość zapisu do pewnych części karty. Zapisywane wartości do tych obszarów pamięci będą poddane operacji OR z wartościami już obecnymi w pamięci. Jeśli jakikolwiek bit ustawimy ‘1’, to ta wartość będzie niemodyfikowalna.
- 4 bajty danych typu OTP. Są to podobnie jak bity bezpiecznikowe dane, gdzie możemy zapisywać tylko jedynki. Jeśli ustawimy jakikolwiek bit, to nie będziemy mieli żadnej możliwości, aby go wyzerować.
- 12 bloków po 4 bajty na dane użytkownika.
Chipy MIFARE mogą występować pod postacią kart plastikowych bez interfejsu stykowego, mogą być dodane jako dodatkowy układ do innych kart, jako breloczki, naklejki, itp. Poniżej kilka przykładów kart MIFARE.
Czytnik kart zbliżeniowych
Gdy chcemy komunikować się z kartami zbliżeniowymi, potrzebujemy czytnika. Ja korzystam z ACR122.
To tani czytnik na USB działający bardzo dobrze w środowisku Windows. Czytnik zakłada, że komunikujemy się z kartami zgodnymi z ISO/IEC 14443-4, a w przypadku kart Mifare sam emuluje poszczególne rozkazy karty.
Szczegółowe informacje o tym czytniku można znaleźć w tym dokumencie (opens new window).
Komunikacja z kartą
System Windows obsługuje karty inteligentne bardzo dobrze (tylko te zgodne z ISO). Smart Card API Windows (opens new window) pozwala nam wymieniać APDU z kartą dzięki dowolnemu kompatybilnemu czytnikowi np. ACR122. Wszystkie czytniki USB, jakie znam, nie posiadają dedykowanych sterowników, a są one obsługiwane przez standardowe sterowniki Windows.
W poniższym przykładzie zainstalowałem bibliotekę pomocniczą do obsługi kart inteligentnych w środowisku Python pyscard (opens new window). Biblioteka pyscard (opens new window) jest tylko wygodną nakładką na standardowe API Windows, zawiera również kilka opcji dodatkowych.
Wszystko, co robimy dzięki tej bibliotece, możemy zrobić również przy pomocy Smart Card API Windows (opens new window). Jeśli nie chcemy ręcznie kompilować biblioteki pyscard, możemy pobrać gotowy instalator msi (opens new window).
Poniżej klasa wywołująca wybrane komendy czytnika ACR122.
from smartcard.System import readers
from smartcard.ATR import ATR
from smartcard.util import toHexString
import smartcard.Exceptions
class NoACR122ReaderException(Exception): pass
class CardException(Exception): pass
class UnknownReaderException(Exception): pass
class OperationFailedException(Exception): pass
class FunctionNotSupportedException(Exception): pass
class FunctionArgumentException(Exception): pass
class ACR122_Reader:
def __init__(self):
try:
self.readers = readers()
self.acr122_readers = [a for a in self.readers if str(a).find('ACR122')]
if len(self.acr122_readers) == 0:
raise NoACR122ReaderException()
self.selected_reader = self.acr122_readers[0]
self.connection = self.selected_reader.createConnection()
self.connection.connect()
except smartcard.Exceptions.NoCardException:
raise CardException
def showATRInfo(self):
atr = ATR(self.connection.getATR())
print('ATR : {}'.format(toHexString(self.connection.getATR())))
print('historical bytes : {}'.format(toHexString(atr.getHistoricalBytes())))
print('checksum : {} ok? {}: '.format(hex(atr.getChecksum()), atr.checksumOK))
print('support for [T0, T1, T15] : {}'.format([atr.isT0Supported(), atr.isT1Supported(), atr.isT15Supported()]))
def getReadersTotalCount(self):
return len(self.readers)
def getReader(self):
return self.selected_reader
def close(self):
if self.connection:
self.connection.disconnect()
def sendAPDU(self, apdu):
return self.connection.transmit( apdu )
#==================================
def getFirmwareVersion(self):
APDU = [0xFF, 0x00, 0x48, 0x00, 0x00]
data, sw1, sw2 = self.sendAPDU(APDU)
if sw1 != 49 or sw2 != 48: raise OperationFailedException()
return "".join(map(chr, data))
def setBuzzerStatus(self, value):
new_status = [0x00,0xff][int(value)]
APDU = [0xFF, 0x00, 0x52, new_status, 0x00]
data, sw1, sw2 = self.sendAPDU(APDU)
if sw1 != 0x90: raise OperationFailedException()
#==================================
def ParseResponce(self, sw1, sw2):
if sw1 == 0x63 and sw2 == 0x00: raise OperationFailedException()
if sw1 == 0x6A and sw2 == 0x081: raise FunctionNotSupportedException()
if sw1 != 0x90 and sw2 == 0x00: raise UnknownReaderException()
def GetSerialNumber(self):
APDU = [0xFF, 0xCA, 0x00, 0x00, 0x04]
data, sw1, sw2 = self.sendAPDU(APDU)
self.ParseResponce(sw1, sw2)
return data
def LoadAuthenticationKeys(self, key, key_location = 0):
if not isinstance(key, list): raise FunctionArgumentException()
if len(key) != 6: raise FunctionArgumentException()
if not key_location in [0x00,0x01]: raise FunctionArgumentException()
APDU = [0xFF, 0x82, 0x00, key_location, 0x06] + key
data, sw1, sw2 = self.sendAPDU(APDU)
self.ParseResponce(sw1, sw2)
def Authentication(self, block, use_key_A, key_location = 0):
if not isinstance(block, int): raise FunctionArgumentException()
if block < 0 or block > 255: raise FunctionArgumentException()
if not isinstance(use_key_A, bool): raise FunctionArgumentException()
if not key_location in [0x00,0x01]: raise FunctionArgumentException()
KEY_TYPE = [0x60, 0x61][int(not use_key_A)]
AUTHENTICATION_DATA = [0x01, 0x00, block, KEY_TYPE, key_location]
APDU = [0xFF, 0x86, 0x00, 0x00, 0x05] + AUTHENTICATION_DATA
data, sw1, sw2 = self.sendAPDU(APDU)
self.ParseResponce(sw1, sw2)
def TryAuthentication(self, block, use_key_A, key_location = 0):
KEY_TYPE = [0x60, 0x61][int(not use_key_A)]
AUTHENTICATION_DATA = [0x01, 0x00, block, KEY_TYPE, key_location]
APDU = [0xFF, 0x86, 0x00, 0x00, 0x05] + AUTHENTICATION_DATA
data, sw1, sw2 = self.sendAPDU(APDU)
if sw1 != 0x90 and sw2 == 0x00:
return False
return True
def ReadBinaryBlocks(self, block, bytesNum):
if not isinstance(block, int): raise FunctionArgumentException()
if block < 0 or block > 255: raise FunctionArgumentException()
if not isinstance(bytesNum, int): raise FunctionArgumentException()
if bytesNum < 0: raise FunctionArgumentException()
APDU = [0xFF, 0xB0, 0x00, block, bytesNum]
data, sw1, sw2 = self.sendAPDU(APDU)
self.ParseResponce(sw1, sw2)
return data
def WriteBinaryBlocks(self, block, bytes):
if not isinstance(block, int): raise FunctionArgumentException()
if block < 0 or block > 255: raise FunctionArgumentException()
if not isinstance(bytes, list): raise FunctionArgumentException()
if not len(bytes) in [4, 16]: raise FunctionArgumentException()
APDU = [0xFF, 0xD6, 0x00, block, len(bytes)] + bytes
data, sw1, sw2 = self.sendAPDU(APDU)
self.ParseResponce(sw1, sw2)
Info
Nie rozmawiamy bezpośrednio z kartą. Wszystkie pakiety APDU mają klasę 0xFF, co zgodnie z dokumentacją czytnika oznacza, że będą zinterpretowane i przetworzone przez czytnik.
Zaraz po wybraniu karty otrzymujemy pakiet ATR (Answer to reset). Ten ciąg bajtów pozwala określić podstawowe właściwości karty (zarówno stykowej, jak i bezstykowej), zawiera informacje o wspieraniu niskopoziomowych protokołów (T0/T1), maksymalne rozmiary ramek oraz ciąg bajtów umożliwiający identyfikację karty. Poniżej kilka linków do analizy ATR kart inteligentnych:
- Aplikacja internetowa. Po wklejeniu ATR otrzymujemy informacje o karcie, (opens new window)
- Biblioteka Pythona do identyfikacji kart na podstawie ATR, (opens new window)
- ISO 14443-4, analiza ATR zaczyna się w rozdziale 5.1. (opens new window)
Info
Aby wysłać jakikolwiek pakiet APDU do czytnika, nawet taki, który sprawdza wersje firmware, urządzenie musi być połączone z kartę. Po szczegóły odsyłam do dokumentacji czytnika (opens new window).
Przykładowa sesja
Poniżej przedstawiam program demonstracyjny zrzucający zawartość dwóch pierwszych sektorów karty. Zakładamy, że jest to karta, do której pasuje domyślny klucz. W przypadku kart MIFARE Ultralight, które nie posiadają mechanizmów kontroli dostępu, powinniśmy usunąć poniższe linie.
reader.LoadAuthenticationKeys(key)
reader.Authentication(start_block, True)
Powinniśmy także zmienić rozmiar bloku zgodnie z dokumentacją (opens new window). Karta Ultralight posiada 512 bitów pamięci podzielonej na 16 stron/bloków po 4B każdy.
Odczyt pierwszych sektorów karty MIFARE Classic
from __future__ import print_function
from ACR122_Reader import *
DEFAULT_KEY = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
def dump_mifare_sectors(reader, sectors, key = DEFAULT_KEY):
for sector in sectors:
dump_mifare_sector(reader, sector, key)
def dump_mifare_sector(reader, sector, key = DEFAULT_KEY):
print("=========== sector {} ===============".format(sector))
start_block = sector * 4
number_of_blocks = 4
if sector > 31:
start_block = 32 * 4 + 16 * (sector - 32)
number_of_blocks = 16
reader.LoadAuthenticationKeys(key)
reader.Authentication(start_block, True)
for i in range(number_of_blocks):
block_num = start_block + i
data = reader.ReadBinaryBlocks(block_num, 16)
data = ' '.join('%02x'%i for i in data)
print("block [{0:02d} | {1:02d}] {2}".format(block_num, i, data))
def main():
try:
reader = ACR122_Reader()
print()
print("Selected reader is: {} ver. {} serial: {}".format(reader.getReader(), reader.getFirmwareVersion(),reader.GetSerialNumber()))
reader.showATRInfo()
dump_mifare_sectors(reader, [0, 1])
reader.close()
except CardException:
print('ERROR card problem')
except NoACR122ReaderException:
print('ERROR no ACR122 reader connected')
except OperationFailedException:
print('ERROR Operation Failed')
except FunctionNotSupportedException:
print('ERROR Function Not Supported')
except UnknownReaderException:
print('ERROR Unknown Reader Exception')
except FunctionArgumentException:
print('ERROR Wrong argument')
if __name__ == "__main__":
main( )
Wynik dla czystej karty
Selected reader is: ACS ACR122 0 ver. ACR122U2 serial: [32, 209, 75, 132]
ATR : 3B 8F 80 01 80 4F 0C A0 00 00 03 06 03 00 01 00 00 00 00 6A
historical bytes : 80 4F 0C A0 00 00 03 06 03 00 01 00 00 00 00
checksum : 0x6a ok? True:
support for [T0, T1, T15] : [True, True, False]
=========== sector 0 ===============
block [00 | 00] 20 d1 4b 84 3e 88 04 00 c1 85 14 98 61 80 48 12
block [01 | 01] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
block [02 | 02] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
block [03 | 03] 00 00 00 00 00 00 ff 07 80 69 ff ff ff ff ff ff
=========== sector 1 ===============
block [04 | 00] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
block [05 | 01] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
block [06 | 02] 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
block [07 | 03] 00 00 00 00 00 00 ff 07 80 69 ff ff ff ff ff ff
Co więcej o kartach inteligentnych?
Poniżej postanowiłem wypisać tematy, którymi również chciałbym się zając w przyszłości. Jeśli wśród z nich znajduje się ten, który interesuje Cię bardziej, niż pozostałe, napisz o tym w komentarzu, a ja specjalnie dla Ciebie stworzę wpis, w którym omówię detale wybranego zagadnienia.
- Przykład bardziej zaawansowanych kryptograficznie kart zbliżeniowych, np. MIFARE DESFire.
- Analiza protokołu komunikacyjnego, komunikacja bezpośrednio z kartą, bez żadnej logiki zaszytej w firmware czytnika.
- Łamanie zabezpieczeń karty MIFARE Classic, jak złamać klucz dostępu.
- Kilka słów o kartach płatniczych i standardzie EMV..
- Pisanie własnej aplikacji na kartę inteligentną.