Las tarjetas NFC (Near Field Communication) (Comunicación de Campo Cercano) son un tipo de tarjeta inteligente sin contacto. El lector se comunica con el circuito integrado de la tarjeta y le envía energía para su funcionamiento mediante inducción magnética. También se pueden encontrar chips NFC en etiquetas, pulseras u otros objetos. Las tarjetas Mifare Classic son un tipo de tarjeta NFC que tienen 1 o 4 KiB de memoria cifrada con el algoritmo Crypto-1. Hace años se descubrió que este algoritmo de cifrado no es seguro, por lo que no es recomendable utilizar tarjetas Mifare Classic en nuevos desarrollos que requieran seguridad.
Para leer y escribir en las tarjetas con un ordenador necesitamos un lector como el Identiv SCL3711. Para la comunicación con lectores de tarjetas existe el estándar PC/SC (Personal Computer/Smart Card) (Ordenador Personal/Tarjeta Inteligente). Este estándar permite realizar programas que funcionen con múltiples lectores en diferentes sistemas operativos. En Linux lo implementa el software PCSC Lite.
El lector SCL3711 utiliza el chip PN533. Es necesario deshabilitar la carga de los módulos del kernel de este chip para que no interfieran con PCSC Lite. Algunos lectores utilizan el protocolo CCID y funcionan con el controlador genérico incluido con PCSC Lite. El lector SCL3711 en cambio necesita un controlador del fabricante que debemos descargar e instalar. Este controlador depende de la biblioteca libusb-0.1. Por último debemos reiniciar PCSC Lite y conectar el lector.
# vi /etc/modprobe.d/blacklist.conf blacklist pn533 blacklist pn533_usb # apt-get install pcscd pcsc-tools libusb-0.1-4 # wget https://files.identiv.com/products/logical-access-control/scl3711/SCx371x_Linux_2.18_Driver.zip # unzip SCx371x_Linux_2.18_Driver.zip # tar xjpf scx371x_2.18_linux_64bit_release.tar.bz2 # cd scx371x_2.18_linux_64bit_release # ./install.sh # systemctl restart pcscd
Al conectar el lector se le debe encender una luz. Para comprobar que funciona podemos ejecutar el programa pcsc_scan. Este programa detectará el lector, nos mostrará información sobre él y quedará a la espera de que acerquemos una tarjeta. Al acercar una tarjeta podremos ver como el lector la detecta y se produce la comunicación entre ambos. El programa también intentará identificar el tipo de tarjeta.
# pcsc_scan PC/SC device scanner V 1.6.2 (c) 2001-2022, Ludovic Rousseau <[email protected]> Using reader plug'n play mechanism Scanning present readers... 0: SCL3711 Reader and NFC Device 00 00 Tue Feb 13 22:54:26 2024 Reader 0: SCL3711 Reader and NFC Device 00 00 Event number: 154 Card state: Card removed,
Tue Feb 13 22:54:32 2024 Reader 0: SCL3711 Reader and NFC Device 00 00 Event number: 155 Card state: Card inserted, ATR: 3B 8F 80 01 80 4F 0C A0 00 00 03 06 03 00 02 00 00 00 00 69 ATR: 3B 8F 80 01 80 4F 0C A0 00 00 03 06 03 00 02 00 00 00 00 69 + TS = 3B --> Direct Convention + T0 = 8F, Y(1): 1000, K: 15 (historical bytes) TD(1) = 80 --> Y(i+1) = 1000, Protocol T = 0 ----- TD(2) = 01 --> Y(i+1) = 0000, Protocol T = 1 ----- + Historical bytes: 80 4F 0C A0 00 00 03 06 03 00 02 00 00 00 00 Category indicator byte: 80 (compact TLV data object) Tag: 4, len: F (initial access data) Initial access data: 0C A0 00 00 03 06 03 00 02 00 00 00 00 + TCK = 69 (correct checksum) Possibly identified card (using /usr/share/pcsc/smartcard_list.txt): 3B 8F 80 01 80 4F 0C A0 00 00 03 06 03 00 02 00 00 00 00 69 3B 8F 80 01 80 4F 0C A0 00 00 03 06 .. 00 02 00 00 00 00 .. MIFARE Classic 4K (as per PCSC std part3) 3B 8F 80 01 80 4F 0C A0 00 00 03 06 03 00 02 00 00 00 00 69 3B 8F 80 01 80 4F 0C A0 00 00 03 06 03 .. .. 00 00 00 00 .. RFID - ISO 14443 Type A Part 3 (as per PCSC std part3) 3B 8F 80 01 80 4F 0C A0 00 00 03 06 03 00 02 00 00 00 00 69 RFID - ISO 14443 Type A - NXP Mifare card with 4k EEPROM OV Chipkaart https://www.ov-chipkaart.nl/home.htm
En Java se utilizan las clases del módulo javax.smartcardio para interactuar con el sistema PC/SC. Con la clase TerminalFactory podemos listar los lectores conectados al ordenador y elegir uno de ellos. Cada lector se controla con una clase CardTerminal. Esta clase nos permite esperar a que se acerque una tarjeta al lector mediante el método waitForCardPresent y conectar a la tarjeta con el método connect. Este método nos devuelve una clase Card para interactuar con la tarjeta. Una de las primeras cosas que podemos hacer es leer el ATR (Answer To Reset) de la tarjeta con el método getATR para conocer información sobre esta. A continuación podemos usar el método getBasicChannel para abrir un canal de comunicación y enviar comandos a la tarjeta.
CardTerminal terminal = TerminalFactory.getDefault().terminals().list().get(0); terminal.waitForCardPresent(0); Card card = terminal.connect("*"); ATR atr = card.getATR()); CardChannel channel = card.getBasicChannel();
La comunicación con la tarjeta se realiza mediante el envío de comandos APDU. Estos comandos los puede responder la tarjeta o puede ser el lector o el controlador del lector el que responda a los comandos haciendo de intermediario entre la tarjeta y el sistema PC/SC. Los comandos se envían con el método transmit del canal y codificados en una clase CommandAPDU. Como respuesta obtenemos una clase ResponseAPDU. Esta clase contiene el estado de la ejecución del comando indicando si tuvo éxito o hubo algún error. En comandos de lectura también contiene los datos solicitados.
En la documentación del lector y la tarjeta encontraremos los comandos que debemos utilizar. Con PC/SC tenemos un canal de comunicación estándar pero los comandos pueden variar dependiendo del lector y la tarjeta. Para los parámetros de los comandos y el estado de ejecución se utiliza el sistema hexadecimal. En este sistema cada dígito equivale a 4 bits y dos dígitos a 8 bits / 1 byte. A continuación se muestra el comando necesario para leer el UID (Unique Identifier) (Identificador Único) de una tarjeta Mifare Classic con el lector SCL3711.
int CLASS = 0xFF; int GET_UID = 0xCA; ResponseAPDU response = channel.transmit(new CommandAPDU(CLASS, GET_UID, 0x00, 0x00, 0x100)); int status = response.getSW(); byte[] data = response.getData();
Las tarjetas tienen 1 o 4 KiB de memoria divididos en sectores y bloques. Los bloques tienen 16 bytes. Las tarjetas de 1 KiB tienen 16 sectores de 4 bloques (16 x 4 x 16 = 1024 KiB) y las tarjetas de 4 KiB tienen 32 sectores de 4 bloques y 8 sectores de 16 bloques (32 x 4 x 16 + 8 x 16 x 16 = 4096 KiB). En el primer bloque del primer sector se encuentra el UID e información del fabricante de la tarjeta. El último bloque de cada sector recibe el nombre de "sector trailer" y en él se encuentran dos claves de 6 bytes y los permisos que dan esas claves sobre los bloques del sector, incluido el bloque "sector trailer". Descontando los 16 bytes del "sector trailer" quedan 48 o 240 bytes por sector para la escritura de datos. Las claves reciben los nombres "A" y "B".
La memoria está cifrada mediante el algoritmo Crypto-1 y para poder leer y escribir en la tarjeta necesitamos cargar las claves de los sectores que queramos leer o escribir. Por defecto las claves suelen ser 0xFFFFFFFFFFFF. En el siguiente ejemplo se muestra un comando APDU que carga una clave A con el valor por defecto.
int CLASS = 0xFF; int KEY_A = 0x60; int KEY_B = 0x61; int LOAD_KEY = 0x82; byte[] key = new byte[] {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}; channel.transmit(new CommandAPDU(CLASS, LOAD_KEY, 0x00, KEY_A, key));
Después de cargar las claves necesarias podemos ejecutar los comandos de lectura y escritura. En la documentación del lector se indica que antes de leer y escribir es necesario ejecutar el comando "AUTHENTICATE" sobre los bloques que se quieren leer y escribir pero yo no he necesitado ejecutarlo. Puede que con otro lector o en alguna situación determinada sea necesario ejecutar este comando.
int CLASS = 0xFF; int READ_BLOCK = 0xB0; int WRITE_BLOCK = 0xD6; int BLOCK_BYTES = 0x10; //Leer Bloque byte[] data = channel.transmit(new CommandAPDU(CLASS, READ_BLOCK, 0x00, block, BLOCK_BYTES)).getData(); //Escribir Bloque int status = channel.transmit(new CommandAPDU(CLASS, WRITE_BLOCK, 0x00, block, data)).getSW();
Además de poder escribir datos en los bloques, las tarjetas Mifare Classic también permiten que podamos utilizar un bloque para almacenar un número entero de 32 bits con signo y aumentar o disminuir su valor con un comando. Los bloques usados de esta forma reciben el nombre de "value block".
int CLASS = 0xFF; int VALUE_BLOCK_COMMAND = 0xF0; int DECREMENT_VALUE_BLOCK = 0xC0; int INCREMENT_VALUE_BLOCK = 0xC1; byte[] commandBytes = new byte[] {(byte) INCREMENT_VALUE_BLOCK, (byte) block}; byte[] valueBytes = ByteBuffer.allocate(4) .order(ByteOrder.LITTLE_ENDIAN) .putInt(value).array(); channel.transmit(new CommandAPDU(CLASS, VALUE_BLOCK_COMMAND, 0x00, block, concatArrays(commandBytes, valueBytes)));
Estos son los comandos básicos necesarios para utilizar las tarjetas Mifare Classic. Sobre estos comandos podemos programar funciones de más alto nivel de abstracción que nos permitan hacer cosas más complejas de forma sencilla. Yo he creado un pequeño programa con varias funciones. Si instalamos el paquete binfmt-support, copiamos el archivo JAR a un directorio incluido en la variable PATH, le quitamos la extensión .jar y le damos permisos de ejecución, podremos ejecutarlo como cualquier otro programa del sistema.
# wget https://github.com/alejsanc/mifare-classic-reader-writer/releases/download/1.0/mcrw-1.0.jar # apt-get install binfmt-support # chmod +x mcrw-1.0.jar # cp mcrw-1.0.jar /usr/local/bin/mcrw
Al ejecutar el programa sin ningún parámetro nos mostrará información de como usarlo. La entrada y salida de los datos se realiza en sistema hexadecimal, a excepción de la lectura y escritura de cadenas de texto.
# mcrw Usage: mcrw a|b key action block|sector data|value echo $data | mcrw a|b key action block|sector Actions: read-block block read-block-string block write-block block data write-block-string block data clear-block block format-value-block block read-value-block block increment-value-block block value decrement-value-block block value read-sector sector read-sector-string sector read-sector-info sector write-sector sector data write-sector-string sector data clear-sector sector read-sector-trailer sector write-sector-trailer sector data read-card-info Examples: mcrw a 08429a71b536 write-block 4 4578616d706c6520537472696e670000 mcrw b 05c4f163e7d2 write-block-string 5 "Example String" mcrw b 05c4f163e7d2 increment-value-block 6 10
Ejecutando la acción read-card-info podremos ver la información del lector y de la tarjeta. También podremos ver los datos de todos los sectores de la tarjeta en sistema hexadecimal. En el primer bloque del primer sector se puede ver el UID e información del fabricante de la tarjeta.
# mcrw a ffffffffffff read-card-info Terminal: PC/SC terminal SCL3711 Reader and NFC Device 00 00 Card: PC/SC card in SCL3711 Reader and NFC Device 00 00, protocol T=1, state OK Card ATR: 3B8F8001804F0CA0000003060300020000000069 Card Type: Mifare Classic 4K Card UID: 59BE5C7B Card Data: Sector 0: 0:59BE5C7BC01802004644533730563031 - <UID - Manufacturer Data> 1:00000000000000000000000000000000 - 2:00000000000000000000000000000000 - 3:000000000000FF078069FFFFFFFFFFFF - <Sector Trailer> Sector 1: 4:00000000000000000000000000000000 - 5:00000000000000000000000000000000 - 6:00000000000000000000000000000000 - 7:000000000000FF078069FFFFFFFFFFFF - <Sector Trailer> ... Sector 39: 240:00000000000000000000000000000000 - 241:00000000000000000000000000000000 - 242:00000000000000000000000000000000 - 243:00000000000000000000000000000000 - 244:00000000000000000000000000000000 - 245:00000000000000000000000000000000 - 246:00000000000000000000000000000000 - 247:00000000000000000000000000000000 - 248:00000000000000000000000000000000 - 249:00000000000000000000000000000000 - 250:00000000000000000000000000000000 - 251:00000000000000000000000000000000 - 252:00000000000000000000000000000000 - 253:00000000000000000000000000000000 - 254:00000000000000000000000000000000 - 255:000000000000FF078069FFFFFFFFFFFF - <Sector Trailer>
Al final de los sectores podemos ver el bloque "sector trailer" con las claves y los permisos que conceden esas claves sobre los bloques del sector. A la izquierda se encuentra la clave A y a la derecha la clave B. En el centro hay 4 bytes. Los tres primeros se utilizan para codificar los permisos y el cuarto queda libre para cualquier otro uso. Los 3 bytes de los permisos equivalen a 24 bits. En estos bits se guardan dos copias de los permisos con valor inverso. Esto sirve para hacer una suma de verificación y detectar errores. Si los bits de permisos de un bloque tienen el mismo valor que sus inversos es que ha habido errores y la tarjeta no permitirá el acceso a ese bloque. La clave A nunca es visible y la visibilidad de la clave B depende de los permisos.
Clave A Permisos Clave B ------------ -------- ------------ 000000000000 FF0780 69 FFFFFFFFFFFF 3 Bytes 24 Bits ------ ------------------------ FF0780 -> 111111110000011110000000
El sistema de permisos de las tarjetas Mifare Classic es complejo. Si queremos cambiar los permisos es conveniente usar una calculadora de permisos a la que indiquemos los permisos que queremos dar a cada bloque y nos calcule los bytes que debemos escribir. Una vez que tengamos estos bytes podemos modificar el bloque "sector trailer" con los nuevos permisos. La calculadora también nos puede servir para decodificar los permisos actuales de los bloques.
La escritura de este bloque se realiza como la de cualquier otro bloque. Para cambiar los permisos o las claves debemos escribir el bloque completo con las claves y permisos que queramos utilizar. Si solo queremos cambiar los permisos, las claves o una de las claves debemos escribir en el resto del bloque los valores actuales. Es muy importante escribir este bloque correctamente, si hay fallos puede quedar el sector inutilizable.
Las acciones del programa permiten acceder por separado a los bloques "sector trailer" y el resto de bloques. Esto evita la lectura o escritura por error en bloques "sector trailer" y facilita la lectura y escritura de datos en el resto de bloques. Las acciones read-block* y read-sector* no leen los bloques "sector trailer" y las acciones write-block* y write-sector* no escriben en ellos. Solo las acciones read-sector-info y read-card-info muestran también los bloques "sector trailer". Para leer y escribir estos bloques es necesario utilizar las acciones read-sector-trailer y write-sector-trailer.
A continuación se muestra como primero se cambian solo los permisos y luego se cambian las claves. Para cambiar las claves se utiliza la clave B porque el cambio de permisos ha hecho que solo se pueda escribir con esta clave. Finalmente se lee el "sector trailer" con la nueva clave A. Se puede ver como los nuevos permisos no permiten ver la clave B.
# mcrw a ffffffffffff write-sector-trailer 1 ffffffffffff78778800ffffffffffff # mcrw b ffffffffffff write-sector-trailer 1 acacacacacac78778800bcbcbcbcbcbc # mcrw a acacacacacac read-sector-trailer 1 00000000000078778800000000000000
En el siguiente ejemplo se escribe una cadena de texto usando la nueva clave B y la acción write-sector-string. Esta acción se encarga de repartir la cadena de texto entre varios bloques del sector sin escribir sobre el "sector trailer". Con la acción read-sector-info podemos ver el resultado. Si queremos leer solo la cadena de texto podemos usar la acción read-sector-string. Esta acción lee la cadena de texto escrita en el sector sin incluir los ceros finales y el "sector trailer".
# mcrw b bcbcbcbcbcbc write-sector-string 1 "https://www.cuadernoinformatica.com/" # mcrw a acacacacacac read-sector-info 1 Sector 1: 4:68747470733A2F2F7777772E63756164 - https://www.cuad 5:65726E6F696E666F726D61746963612E - ernoinformatica. 6:636F6D2F000000000000000000000000 - com/ 7:00000000000078778800000000000000 - <Sector Trailer> # mcrw a acacacacacac read-sector-string 1 https://www.cuadernoinformatica.com/
Para utilizar un bloque como "value block" debemos formatearlo con el valor "00000000FFFFFFFF0000000000FF00FF". Para ello podemos utilizar la acción format-value-block. El número entero ocupa 4 bytes (32 bits) y se almacena en formato little endian y complemento a 2. Se escriben 3 copias, una de ellas invertida, para detectar errores mediante suma de verificación. Si las copias iguales tienen algún bit distinto o las copias inversas tienen algún bit igual es que ha habido algún error.
Con las acciones increment-value-block y decrement-value-block podemos aumentar o disminuir el número en la cantidad que queramos. Con las acciones read-sector-info o read-block podemos ver como cambia el bloque. Con la acción read-value-block podemos leer el valor del número.
# mcrw a ffffffffffff format-value-block 8 # mcrw a ffffffffffff read-sector-info 2 Sector 2: 8:00000000FFFFFFFF0000000000FF00FF - 9:00000000000000000000000000000000 - 10:00000000000000000000000000000000 - 11:000000000000FF078069FFFFFFFFFFFF - <Sector Trailer> # mcrw a ffffffffffff increment-value-block 8 10 # mcrw a ffffffffffff read-sector-info 2 Sector 2: 8:0A000000F5FFFFFF0A00000000FF00FF - 9:00000000000000000000000000000000 - 10:00000000000000000000000000000000 - 11:000000000000FF078069FFFFFFFFFFFF - <Sector Trailer> # mcrw a ffffffffffff read-block 8 0A000000F5FFFFFF0A00000000FF00FF # mcrw a ffffffffffff read-value-block 8 10
Si se tiene un sistema de lectores y tarjetas Mifare Classic, se necesita más seguridad y no es posible migrar a otro tipo de tarjeta más segura por diferentes razones, como puede ser el coste económico, se puede aumentar la seguridad de diferentes formas. Por ejemplo podemos cifrar la información antes de escribirla en la tarjeta con un algoritmo más seguro como AES. A continuación se muestra un ejemplo de cifrado y descifrado de la información con OpenSSL. La comunicación con el programa mcrw se realiza mediante tuberías y se utiliza el programa xxd para codificar y decodificar en sistema hexadecimal.
# echo "https://www.cuadernoinformatica.com/" | openssl enc -aes-256-cbc -pbkdf2 | xxd -p -c 0 | mcrw a ffffffffffff write-sector 39 enter AES-256-CBC encryption password: Verifying - enter AES-256-CBC encryption password: # mcrw a ffffffffffff read-sector 39 | sed -E "s/0+$//" | xxd -r -p | openssl enc -d -aes-256-cbc -pbkdf2 enter AES-256-CBC decryption password: https://www.cuadernoinformatica.com/ # mcrw a ffffffffffff read-sector-info 39 Sector 39: 240:53616C7465645F5FC13CE96914A2517E - Salted__ < i Q~ 241:15F0AD0A41C6279197A5C06B25FACFA6 - A ' k% Ϧ 242:75EE7AA3091B7F6C4F41A2628B9E74B9 - u z lOA b t 243:5490D3926406132BF2EBE8B75D0ECF39 - T Ӓd + ] 9 244:00000000000000000000000000000000 - 245:00000000000000000000000000000000 - 246:00000000000000000000000000000000 - 247:00000000000000000000000000000000 - 248:00000000000000000000000000000000 - 249:00000000000000000000000000000000 - 250:00000000000000000000000000000000 - 251:00000000000000000000000000000000 - 252:00000000000000000000000000000000 - 253:00000000000000000000000000000000 - 254:00000000000000000000000000000000 - 255:000000000000FF078069FFFFFFFFFFFF - <Sector Trailer>
Esto impedirá que cualquiera pueda acceder a la información pero no evitará que se pueda clonar la tarjeta. Para impedir el uso de tarjetas clonadas se puede solicitar un PIN/contraseña. Esto también evita el uso de tarjetas robadas o perdidas.
Las clases que componen el programa mcrw se pueden utilizar para el desarrollo de otros programas. Debemos crear un objeto de la clase MifareClassicReaderWriter y ejecutar el método readCard para esperar a que una tarjeta se acerque al lector y abrir un canal de comunicación. Una vez abierto el canal se pueden cargar las claves y ejecutar los métodos de lectura y escritura. La clase solo está probada con el lector SCL3711, para otros lectores puede ser necesario crear una subclase y reescribir los métodos getUID, loadKey, readBlock, writeBlock y valueBlockCommand para modificar los comandos APDU.
MifareClassicReaderWriter device = new MifareClassicReaderWriter(); device.readCard(); device.loadKey(KEY_A, "ffffffffffff"); String block = device.readBlockHexString(4);
En próximos artículos espero poder añadir al programa la escritura de datos en formato NDEF. También intentaré crear una versión del programa para tarjetas NTAG21x.