desfire package

Submodules

desfire.android module

class desfire.android.AndroidDevice(iso_dep)[source]

Bases: desfire.device.Device

DESFire protocol wrapper for pyscard interface.

Iso_dep:android.nfc.tech.IsoDep Java class wrapped as jnius object.
transceive(bytes)[source]

Note

Android API may return byte array memory views that are easily corrupted by native APIs, so we just copy all incoming bytes to a proper list as soon as possible on Android.

desfire.device module

class desfire.device.Device[source]

Bases: object

Abstract base class which uses underlying device communication channel.

transceive(bytes)[source]

Send in APDU request and wait for the response.

Parameters:bytes – Outgoing bytes as list of bytes or byte array
Returns:List of bytes or byte array from the device.

desfire.dummy module

class desfire.dummy.DummyDevice[source]

Bases: desfire.device.Device

Dummy device mock implementation.

transceive(bytes)[source]

desfire.pcsc module

class desfire.pcsc.PCSCDevice(card_connection)[source]

Bases: desfire.device.Device

DESFire protocol wrapper for pyscard interface.

Card_connection:
 smartcard.pcsc.PCSCCardConnection.PCSCCardConnection instance. Call card_connection.connect() before calling any DESFire APIs.
transceive(bytes)[source]
exception desfire.pcsc.PCSCNotConnected[source]

Bases: exceptions.Exception

Tried to transmit to non-open connection.

desfire.protocol module

MIFARE DESFire communication protocol for Python.

For DESFire overview:

For list of DESFire commands see:

class desfire.protocol.DESFire(device, logger=None)[source]

Bases: object

MIFare DEefire EV1 communication protocol for NFC cards.

Parameters:
  • devicedesfire.device.Device implementation
  • logger – Python logging.Logger used for logging output. Overrides the default logger. Extensively uses INFO logging level.
authenticate(app_id, key_id, private_key=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])[source]

Hacked together Android only DESFire authentication.

Desfire supports multiple authentication modes, see:

Here we use legacy authentication (0xa0)

Parameters:
  • app_id – 24-bit app id
  • key_id – One of 0-16 keys on the card as byte
  • private_key – 8 or 16 bytes of private key
commit()[source]

Commit all write changes to the card.

Example

def top_up(self, tag, added_value):
    device = deviceDep.get(tag)
    device.connect()

    desfire = DESFire(device, Logger)

    try:

        desfire.select_application(XXX_APP_ID)
        old_value = desfire.get_value(0x01)
        desfire.credit_value(0x01, added_value)
        desfire.commit()
        return old_value
    finally:
        device.close()
communicate(apdu_cmd, description, allow_continue_fallthrough=False)[source]

Communicate with a NFC tag.

Send in outgoing request and waith for a card reply.

TODO: Handle additional framing via 0xaf

Parameters:
  • apdu_cmd – Outgoing APDU command as array of bytes
  • description – Command description for logging purposes
  • allow_continue_fallthrough – If True 0xAF response (incoming more data, need mode data) is instantly returned to the called instead of trying to handle it internally
Raise:

desfire.protocol.DESFireCommunicationError on any error

Returns:

tuple(APDU response as list of bytes, bool if additional frames are inbound)

create_value_file(file_id, communication_settings, access_permissions, min_value, max_value, current_value, limited_credit_enabled)[source]

Create a new value file.

Parameters:
  • file_id – (byte)
  • communication_settings – (byte) See FILE_COMMUNICATION
  • access_permissions – (word) 0xeeee Everybody has read write access
  • current_value – (dword)
  • max_value – (dword)
  • min_value – (dword)
  • limited_credit_enabled – (byte) Allows limited increase in value file without having full credit permission.

Example

def write_card(self, tag, value):
    device = deviceDep.get(tag)
    device.connect()

    desfire = DESFire(device, Logger)

    try:
        desfire.select_application(XXX_APP_ID)

        file_ids = desfire.get_file_ids()
        if XXX_FILE_STORED_VALUE in file_ids:
            old_value = desfire.get_value(XXX_FILE_STORED_VALUE)
            desfire.delete_file(XXX_FILE_STORED_VALUE)
        else:
            old_value = None

        desfire.create_value_file(file_id=XXX_FILE_STORED_VALUE, communication_settings=0x00, access_permissions=0xeeee, min_value=0, max_value=1000000, current_value=value, limited_credit_enabled=0)

        return old_value
    finally:
        device.close()
credit_value(file_id, added_value)[source]

Increase stored value.

Parameters:
  • file_id – (byte)
  • added_value – (int, 32 bit) Value to be added to the current value
Raise:

desfire.protocol.DESFireCommunicationError on any error

debit_value(file_id, value_decrease)[source]

Decrease stored value.

Parameters:
  • file_id – (byte)
  • value_decrease – (int, 32 bit) How much we reduce from existing value

Example

class BurnerScreen(Screen):
    '''Continuously keep decreasing credits on the card.'''

    def on_enter(self):
        Logger.debug("Entering burner screen")
        nfc_controller.callback = self.detect_new_tag
        self.timer = None
        self.device = None

    def detect_new_tag(self, tag):

        Logger.debug("Detected tag %s", tag)
        self.tag = tag
        self.device = deviceDep.get(tag)
        self.device.connect()
        self.start_burner_cycle()

    def start_burner_cycle(self):
        Logger.debug("Starting next burner cycle")
        self.timer = Timer(0.3, self.burn)
        self.timer.start()

    def burn(self):

        Logger.debug("Running burner cycle")

        try:
            if not self.device.isConnected():
                # We have lost the tag
                return

            desfire = DESFire(self.device, Logger)
            desfire.select_application(XXX_APP_ID)

            desfire.debit_value(XXX_FILE_STORED_VALUE, 1)
            new_value = desfire.get_value(XXX_FILE_STORED_VALUE)
            desfire.commit()

            # Show how much value we have after burn
            self.value.text = "%d" % new_value

            # Do next tick
            self.start_burner_cycle()

        except Exception as e:
            # Most likely exception from device tag communications, tag moved away during communications
            tb = traceback.format_exc()
            Logger.exception(str(e))
            Logger.exception(tb)
        finally:
            # Tell pyjnius we are done with this thread
            jnius.detach()


    def cancel(self):
        self.close()

    def close(self):

        if self.device:
            self.device.close()

        # jnius threads are not cancellable
        # if self.timer:
        #    self.timer.cancel()

        nfc_controller.callback = None
        self.manager.switch_to(NFCScreen())
delete_file(file_id)[source]

Delete a file.

Parameters:file_id – byte
get_applications()[source]

Get all applications listed in Desfire root.

TODO: Check byte order here

Returns:List of 24-bit intesx
Raise:desfire.protocol.DESFireCommunicationError on any error
get_file_ids()[source]
get_file_settings(file_id)[source]

Get DESFire file settings.

Parameters:file_id – File id as a byte
Returns:File description dict.
get_value(file_id)[source]

Get stored value.

Parameters:file_id – Stored value file id
Returns:Integer.
Raise:desfire.protocol.DESFireCommunicationError on any error
parse_application_list(resp)[source]

Handle response for command 0x6a list applications.

DESFire application ids are 24-bit integers.

Parameters:resp – DESFire response as byte array
Returns:List of parsed application ids
read_data_file(file_id)[source]

Read standard data file.

If the data file is unwritten (no single write since card format) it should return 0x00 as data after format.

Example:

def write_test(desfire):
    '''Write a long data file to see card write functions work.

    Assume the card is formatted with 7000 bytes test file. If we do not fill in file bytes during write, then the next read will reflect back whatever garbage we left there unwritten.
    '''
    logger.debug("Writing and reading back a test file")
    desfire.select_application(XXX_APP_ID)
    file_settings = desfire.get_file_settings(XXX_BACKCHANNEL_FILE)
    logger.debug("File settings info %s", file_settings)

    data = [0xff]
    data += [0xaa] * 4000
    data += [0xbb]
    desfire.write_data_file(XXX_BACKCHANNEL_FILE, data)

    # Standard files do not have any kind of commit of write corruption or commit support,
    # so no need to commit here

    # Now read it back
    read_back_data = desfire.read_data_file(XXX_BACKCHANNEL_FILE)
    assert len(read_back_data) == 7000

    assert data == read_back_data[0:4002]
Parameters:file_id – File id to read, 8-bit int
Returns:List of bytes
read_linear_record_file(file_id, offset_in_records, length_in_records)[source]

Read all records of a linear record file.

Parameters:
  • file_id – File id, 8-bit int
  • offset_in_records – First record to read, 16-bit int
  • length_in_records – Number of records to read, 16-bit int
Returns:

File data as bytes

select_application(app_id)[source]

Choose application on a card on which all the following file commands will apply.

TODO: Check byte order here

Parameters:app_id – 24-bit int
Raise:desfire.protocol.DESFireCommunicationError on any error
classmethod wrap_command(command, parameters=None)[source]

Wrap a command to native DES framing.

Parameters:
  • command – Command byte
  • parameters – Command parameters as list of bytes

https://github.com/greenbird/workshops/blob/master/mobile/Android/Near%20Field%20Communications/HelloWorldNFC%20Desfire%20Base/src/com/desfire/nfc/DesfireReader.java#L129

write_data_file(file_id, data)[source]

Write the data to a standard data file.

Parameters:
  • file_id – File number to write, 8-bit int
  • data – Data as bytes or array of bytes
write_linear_record_file_record(file_id, offset_in_records, length_in_records, data)[source]

Write n records in a linear record file.

Parameters:
  • file_id – File id, 8-bit int
  • offset_in_records – First record to read, 16-bit int
  • length_in_records – Number of records to read, 16-bit int
  • data – bytes or list of bytes
Returns:

File data as bytes

exception desfire.protocol.DESFireCommunicationError(msg, status_code)[source]

Bases: exceptions.Exception

Outgoing DESFire command received a non-OK reply.

The exception message is human readable translation of the error code if available. The status_code carries the original status word error byte.

desfire.protocol.ERRORS = {12: 'No changes', 174: 'Authentication error', 189: 'File not found', 240: 'File not found', 28: 'Limited credit', 157: 'Permission denied', 126: 'Length error when sending the command'}

Error code translation mappings https://github.com/jekkos/android-hce-desfire/blob/master/hceappletdesfire/src/main/java/net/jpeelaer/hce/desfire/DesfireStatusWord.java

desfire.protocol.FILE_COMMUNICATION = {0: 'Plain communication', 1: 'Plain communication secured by DES/3DES MACing', 3: 'Fully DES/3DES enciphered communication'}

Communication requirements bits set a on DESFire file

desfire.protocol.FILE_TYPES = {0: 'Standard Data Files', 1: 'Backup Data Files', 2: 'Value Files with Backup', 3: 'Linear Record Files with Backup', 4: 'Cyclic Record Files with Backup'}

File kind bits set on a DESFire file

desfire.util module

Misc. utility functions.

desfire.util.byte_array_to_byte_string(bytes)[source]
desfire.util.byte_array_to_human_readable_hex(bytes)[source]
desfire.util.byte_string_to_byte_array(s)[source]
desfire.util.dword_to_byte_array(value)[source]
desfire.util.hex_array_to_byte_string(hex_array)[source]
desfire.util.word_to_byte_array(value)[source]

Module contents