Mifare Classic RFID tag dump: Python parsing library

You can get a dump for testing by the link: https://github.com/zhovner/mfdread/raw/master/dump.mfd

File extension

mfd

KS implementation details

License: BSD-2-Clause
Minimal Kaitai Struct required: 0.9

References

This page hosts a formal specification of Mifare Classic RFID tag dump using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for Python generated by Kaitai Struct depends on the Python runtime library. You have to install it before you can parse data.

The Python runtime library can be installed from PyPI:

python3 -m pip install kaitaistruct

Code

Parse a local file and get structure in memory:

data = MifareClassic.from_file("path/to/local/file.mfd")

Or parse structure from a bytes:

from kaitaistruct import KaitaiStream, BytesIO

raw = b"\x00\x01\x02..."
data = MifareClassic(KaitaiStream(BytesIO(raw)))

After that, one can get various attributes from the structure by invoking getter methods like:

data.sectors # => get sectors

Python source code to parse Mifare Classic RFID tag dump

mifare_classic.py

# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

from pkg_resources import parse_version
import kaitaistruct
from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO


if parse_version(kaitaistruct.__version__) < parse_version('0.9'):
    raise Exception("Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" % (kaitaistruct.__version__))

class MifareClassic(KaitaiStruct):
    """You can get a dump for testing by the link: https://github.com/zhovner/mfdread/raw/master/dump.mfd
    
    .. seealso::
       Source - https://github.com/nfc-tools/libnfc
       https://www.nxp.com/docs/en/data-sheet/MF1S70YYX_V1.pdf
    """
    def __init__(self, _io, _parent=None, _root=None):
        self._io = _io
        self._parent = _parent
        self._root = _root if _root else self
        self._read()

    def _read(self):
        self._raw_sectors = []
        self.sectors = []
        i = 0
        while not self._io.is_eof():
            self._raw_sectors.append(self._io.read_bytes((((4 if i >= 32 else 1) * 4) * 16)))
            _io__raw_sectors = KaitaiStream(BytesIO(self._raw_sectors[-1]))
            self.sectors.append(MifareClassic.Sector(i == 0, _io__raw_sectors, self, self._root))
            i += 1


    class Key(KaitaiStruct):
        def __init__(self, _io, _parent=None, _root=None):
            self._io = _io
            self._parent = _parent
            self._root = _root if _root else self
            self._read()

        def _read(self):
            self.key = self._io.read_bytes(6)


    class Sector(KaitaiStruct):
        def __init__(self, has_manufacturer, _io, _parent=None, _root=None):
            self._io = _io
            self._parent = _parent
            self._root = _root if _root else self
            self.has_manufacturer = has_manufacturer
            self._read()

        def _read(self):
            if self.has_manufacturer:
                self.manufacturer = MifareClassic.Manufacturer(self._io, self, self._root)

            self._raw_data_filler = self._io.read_bytes(((self._io.size() - self._io.pos()) - 16))
            _io__raw_data_filler = KaitaiStream(BytesIO(self._raw_data_filler))
            self.data_filler = MifareClassic.Sector.Filler(_io__raw_data_filler, self, self._root)
            self.trailer = MifareClassic.Trailer(self._io, self, self._root)

        class Values(KaitaiStruct):
            def __init__(self, _io, _parent=None, _root=None):
                self._io = _io
                self._parent = _parent
                self._root = _root if _root else self
                self._read()

            def _read(self):
                self.values = []
                i = 0
                while not self._io.is_eof():
                    self.values.append(MifareClassic.Sector.Values.ValueBlock(self._io, self, self._root))
                    i += 1


            class ValueBlock(KaitaiStruct):
                def __init__(self, _io, _parent=None, _root=None):
                    self._io = _io
                    self._parent = _parent
                    self._root = _root if _root else self
                    self._read()

                def _read(self):
                    self.valuez = [None] * (3)
                    for i in range(3):
                        self.valuez[i] = self._io.read_u4le()

                    self.addrz = [None] * (4)
                    for i in range(4):
                        self.addrz[i] = self._io.read_u1()


                @property
                def addr(self):
                    if hasattr(self, '_m_addr'):
                        return self._m_addr if hasattr(self, '_m_addr') else None

                    if self.valid:
                        self._m_addr = self.addrz[0]

                    return self._m_addr if hasattr(self, '_m_addr') else None

                @property
                def addr_valid(self):
                    if hasattr(self, '_m_addr_valid'):
                        return self._m_addr_valid if hasattr(self, '_m_addr_valid') else None

                    self._m_addr_valid =  ((self.addrz[0] == ~(self.addrz[1])) and (self.addrz[0] == self.addrz[2]) and (self.addrz[1] == self.addrz[3])) 
                    return self._m_addr_valid if hasattr(self, '_m_addr_valid') else None

                @property
                def valid(self):
                    if hasattr(self, '_m_valid'):
                        return self._m_valid if hasattr(self, '_m_valid') else None

                    self._m_valid =  ((self.value_valid) and (self.addr_valid)) 
                    return self._m_valid if hasattr(self, '_m_valid') else None

                @property
                def value_valid(self):
                    if hasattr(self, '_m_value_valid'):
                        return self._m_value_valid if hasattr(self, '_m_value_valid') else None

                    self._m_value_valid =  ((self.valuez[0] == ~(self.valuez[1])) and (self.valuez[0] == self.valuez[2])) 
                    return self._m_value_valid if hasattr(self, '_m_value_valid') else None

                @property
                def value(self):
                    if hasattr(self, '_m_value'):
                        return self._m_value if hasattr(self, '_m_value') else None

                    if self.valid:
                        self._m_value = self.valuez[0]

                    return self._m_value if hasattr(self, '_m_value') else None



        class Filler(KaitaiStruct):
            """only to create _io."""
            def __init__(self, _io, _parent=None, _root=None):
                self._io = _io
                self._parent = _parent
                self._root = _root if _root else self
                self._read()

            def _read(self):
                self.data = self._io.read_bytes(self._io.size())


        @property
        def block_size(self):
            if hasattr(self, '_m_block_size'):
                return self._m_block_size if hasattr(self, '_m_block_size') else None

            self._m_block_size = 16
            return self._m_block_size if hasattr(self, '_m_block_size') else None

        @property
        def data(self):
            if hasattr(self, '_m_data'):
                return self._m_data if hasattr(self, '_m_data') else None

            self._m_data = self.data_filler.data
            return self._m_data if hasattr(self, '_m_data') else None

        @property
        def blocks(self):
            if hasattr(self, '_m_blocks'):
                return self._m_blocks if hasattr(self, '_m_blocks') else None

            io = self.data_filler._io
            _pos = io.pos()
            io.seek(0)
            self._m_blocks = []
            i = 0
            while not io.is_eof():
                self._m_blocks.append(io.read_bytes(self.block_size))
                i += 1

            io.seek(_pos)
            return self._m_blocks if hasattr(self, '_m_blocks') else None

        @property
        def values(self):
            if hasattr(self, '_m_values'):
                return self._m_values if hasattr(self, '_m_values') else None

            io = self.data_filler._io
            _pos = io.pos()
            io.seek(0)
            self._m_values = MifareClassic.Sector.Values(io, self, self._root)
            io.seek(_pos)
            return self._m_values if hasattr(self, '_m_values') else None


    class Manufacturer(KaitaiStruct):
        def __init__(self, _io, _parent=None, _root=None):
            self._io = _io
            self._parent = _parent
            self._root = _root if _root else self
            self._read()

        def _read(self):
            self.nuid = self._io.read_u4le()
            self.bcc = self._io.read_u1()
            self.sak = self._io.read_u1()
            self.atqa = self._io.read_u2le()
            self.manufacturer = self._io.read_bytes(8)


    class Trailer(KaitaiStruct):
        def __init__(self, _io, _parent=None, _root=None):
            self._io = _io
            self._parent = _parent
            self._root = _root if _root else self
            self._read()

        def _read(self):
            self.key_a = MifareClassic.Key(self._io, self, self._root)
            self._raw_access_bits = self._io.read_bytes(3)
            _io__raw_access_bits = KaitaiStream(BytesIO(self._raw_access_bits))
            self.access_bits = MifareClassic.Trailer.AccessConditions(_io__raw_access_bits, self, self._root)
            self.user_byte = self._io.read_u1()
            self.key_b = MifareClassic.Key(self._io, self, self._root)

        class AccessConditions(KaitaiStruct):
            def __init__(self, _io, _parent=None, _root=None):
                self._io = _io
                self._parent = _parent
                self._root = _root if _root else self
                self._read()

            def _read(self):
                self.raw_chunks = [None] * (self._parent.ac_count_of_chunks)
                for i in range(self._parent.ac_count_of_chunks):
                    self.raw_chunks[i] = self._io.read_bits_int_be(4)


            class TrailerAc(KaitaiStruct):
                def __init__(self, ac, _io, _parent=None, _root=None):
                    self._io = _io
                    self._parent = _parent
                    self._root = _root if _root else self
                    self.ac = ac
                    self._read()

                def _read(self):
                    pass

                @property
                def can_read_key_b(self):
                    """key A is required."""
                    if hasattr(self, '_m_can_read_key_b'):
                        return self._m_can_read_key_b if hasattr(self, '_m_can_read_key_b') else None

                    self._m_can_read_key_b = self.ac.inv_shift_val <= 2
                    return self._m_can_read_key_b if hasattr(self, '_m_can_read_key_b') else None

                @property
                def can_write_keys(self):
                    if hasattr(self, '_m_can_write_keys'):
                        return self._m_can_write_keys if hasattr(self, '_m_can_write_keys') else None

                    self._m_can_write_keys =  ((((self.ac.inv_shift_val + 1) % 3) != 0) and (self.ac.inv_shift_val < 6)) 
                    return self._m_can_write_keys if hasattr(self, '_m_can_write_keys') else None

                @property
                def can_write_access_bits(self):
                    if hasattr(self, '_m_can_write_access_bits'):
                        return self._m_can_write_access_bits if hasattr(self, '_m_can_write_access_bits') else None

                    self._m_can_write_access_bits = self.ac.bits[2].b
                    return self._m_can_write_access_bits if hasattr(self, '_m_can_write_access_bits') else None

                @property
                def key_b_controls_write(self):
                    if hasattr(self, '_m_key_b_controls_write'):
                        return self._m_key_b_controls_write if hasattr(self, '_m_key_b_controls_write') else None

                    self._m_key_b_controls_write = not (self.can_read_key_b)
                    return self._m_key_b_controls_write if hasattr(self, '_m_key_b_controls_write') else None


            class ChunkBitRemap(KaitaiStruct):
                def __init__(self, bit_no, _io, _parent=None, _root=None):
                    self._io = _io
                    self._parent = _parent
                    self._root = _root if _root else self
                    self.bit_no = bit_no
                    self._read()

                def _read(self):
                    pass

                @property
                def shift_value(self):
                    if hasattr(self, '_m_shift_value'):
                        return self._m_shift_value if hasattr(self, '_m_shift_value') else None

                    self._m_shift_value = (-1 if self.bit_no == 1 else 1)
                    return self._m_shift_value if hasattr(self, '_m_shift_value') else None

                @property
                def chunk_no(self):
                    if hasattr(self, '_m_chunk_no'):
                        return self._m_chunk_no if hasattr(self, '_m_chunk_no') else None

                    self._m_chunk_no = (((self.inv_chunk_no + self.shift_value) + self._parent._parent.ac_count_of_chunks) % self._parent._parent.ac_count_of_chunks)
                    return self._m_chunk_no if hasattr(self, '_m_chunk_no') else None

                @property
                def inv_chunk_no(self):
                    if hasattr(self, '_m_inv_chunk_no'):
                        return self._m_inv_chunk_no if hasattr(self, '_m_inv_chunk_no') else None

                    self._m_inv_chunk_no = (self.bit_no + self.shift_value)
                    return self._m_inv_chunk_no if hasattr(self, '_m_inv_chunk_no') else None


            class DataAc(KaitaiStruct):
                def __init__(self, ac, _io, _parent=None, _root=None):
                    self._io = _io
                    self._parent = _parent
                    self._root = _root if _root else self
                    self.ac = ac
                    self._read()

                def _read(self):
                    pass

                @property
                def read_key_a_required(self):
                    if hasattr(self, '_m_read_key_a_required'):
                        return self._m_read_key_a_required if hasattr(self, '_m_read_key_a_required') else None

                    self._m_read_key_a_required = self.ac.val <= 4
                    return self._m_read_key_a_required if hasattr(self, '_m_read_key_a_required') else None

                @property
                def write_key_b_required(self):
                    if hasattr(self, '_m_write_key_b_required'):
                        return self._m_write_key_b_required if hasattr(self, '_m_write_key_b_required') else None

                    self._m_write_key_b_required =  (( ((not (self.read_key_a_required)) or (self.read_key_b_required)) ) and (not (self.ac.bits[0].b))) 
                    return self._m_write_key_b_required if hasattr(self, '_m_write_key_b_required') else None

                @property
                def write_key_a_required(self):
                    if hasattr(self, '_m_write_key_a_required'):
                        return self._m_write_key_a_required if hasattr(self, '_m_write_key_a_required') else None

                    self._m_write_key_a_required = self.ac.val == 0
                    return self._m_write_key_a_required if hasattr(self, '_m_write_key_a_required') else None

                @property
                def read_key_b_required(self):
                    if hasattr(self, '_m_read_key_b_required'):
                        return self._m_read_key_b_required if hasattr(self, '_m_read_key_b_required') else None

                    self._m_read_key_b_required = self.ac.val <= 6
                    return self._m_read_key_b_required if hasattr(self, '_m_read_key_b_required') else None

                @property
                def decrement_available(self):
                    if hasattr(self, '_m_decrement_available'):
                        return self._m_decrement_available if hasattr(self, '_m_decrement_available') else None

                    self._m_decrement_available =  (( ((self.ac.bits[1].b) or (not (self.ac.bits[0].b))) ) and (not (self.ac.bits[2].b))) 
                    return self._m_decrement_available if hasattr(self, '_m_decrement_available') else None

                @property
                def increment_available(self):
                    if hasattr(self, '_m_increment_available'):
                        return self._m_increment_available if hasattr(self, '_m_increment_available') else None

                    self._m_increment_available =  (( ((not (self.ac.bits[0].b)) and (not (self.read_key_a_required)) and (not (self.read_key_b_required))) ) or ( ((not (self.ac.bits[0].b)) and (self.read_key_a_required) and (self.read_key_b_required)) )) 
                    return self._m_increment_available if hasattr(self, '_m_increment_available') else None


            class Ac(KaitaiStruct):
                def __init__(self, index, _io, _parent=None, _root=None):
                    self._io = _io
                    self._parent = _parent
                    self._root = _root if _root else self
                    self.index = index
                    self._read()

                def _read(self):
                    pass

                class AcBit(KaitaiStruct):
                    def __init__(self, i, chunk, _io, _parent=None, _root=None):
                        self._io = _io
                        self._parent = _parent
                        self._root = _root if _root else self
                        self.i = i
                        self.chunk = chunk
                        self._read()

                    def _read(self):
                        pass

                    @property
                    def n(self):
                        if hasattr(self, '_m_n'):
                            return self._m_n if hasattr(self, '_m_n') else None

                        self._m_n = ((self.chunk >> self.i) & 1)
                        return self._m_n if hasattr(self, '_m_n') else None

                    @property
                    def b(self):
                        if hasattr(self, '_m_b'):
                            return self._m_b if hasattr(self, '_m_b') else None

                        self._m_b = self.n == 1
                        return self._m_b if hasattr(self, '_m_b') else None


                @property
                def bits(self):
                    if hasattr(self, '_m_bits'):
                        return self._m_bits if hasattr(self, '_m_bits') else None

                    _pos = self._io.pos()
                    self._io.seek(0)
                    self._m_bits = [None] * (self._parent._parent.ac_bits)
                    for i in range(self._parent._parent.ac_bits):
                        self._m_bits[i] = MifareClassic.Trailer.AccessConditions.Ac.AcBit(self.index, self._parent.chunks[i].chunk, self._io, self, self._root)

                    self._io.seek(_pos)
                    return self._m_bits if hasattr(self, '_m_bits') else None

                @property
                def val(self):
                    """c3 c2 c1."""
                    if hasattr(self, '_m_val'):
                        return self._m_val if hasattr(self, '_m_val') else None

                    self._m_val = (((self.bits[2].n << 2) | (self.bits[1].n << 1)) | self.bits[0].n)
                    return self._m_val if hasattr(self, '_m_val') else None

                @property
                def inv_shift_val(self):
                    if hasattr(self, '_m_inv_shift_val'):
                        return self._m_inv_shift_val if hasattr(self, '_m_inv_shift_val') else None

                    self._m_inv_shift_val = (((self.bits[0].n << 2) | (self.bits[1].n << 1)) | self.bits[2].n)
                    return self._m_inv_shift_val if hasattr(self, '_m_inv_shift_val') else None


            class ValidChunk(KaitaiStruct):
                def __init__(self, inv_chunk, chunk, _io, _parent=None, _root=None):
                    self._io = _io
                    self._parent = _parent
                    self._root = _root if _root else self
                    self.inv_chunk = inv_chunk
                    self.chunk = chunk
                    self._read()

                def _read(self):
                    pass

                @property
                def valid(self):
                    if hasattr(self, '_m_valid'):
                        return self._m_valid if hasattr(self, '_m_valid') else None

                    self._m_valid = (self.inv_chunk ^ self.chunk) == 15
                    return self._m_valid if hasattr(self, '_m_valid') else None


            @property
            def data_acs(self):
                if hasattr(self, '_m_data_acs'):
                    return self._m_data_acs if hasattr(self, '_m_data_acs') else None

                _pos = self._io.pos()
                self._io.seek(0)
                self._m_data_acs = [None] * ((self._parent.acs_in_sector - 1))
                for i in range((self._parent.acs_in_sector - 1)):
                    self._m_data_acs[i] = MifareClassic.Trailer.AccessConditions.DataAc(self.acs_raw[i], self._io, self, self._root)

                self._io.seek(_pos)
                return self._m_data_acs if hasattr(self, '_m_data_acs') else None

            @property
            def remaps(self):
                if hasattr(self, '_m_remaps'):
                    return self._m_remaps if hasattr(self, '_m_remaps') else None

                _pos = self._io.pos()
                self._io.seek(0)
                self._m_remaps = [None] * (self._parent.ac_bits)
                for i in range(self._parent.ac_bits):
                    self._m_remaps[i] = MifareClassic.Trailer.AccessConditions.ChunkBitRemap(i, self._io, self, self._root)

                self._io.seek(_pos)
                return self._m_remaps if hasattr(self, '_m_remaps') else None

            @property
            def acs_raw(self):
                if hasattr(self, '_m_acs_raw'):
                    return self._m_acs_raw if hasattr(self, '_m_acs_raw') else None

                _pos = self._io.pos()
                self._io.seek(0)
                self._m_acs_raw = [None] * (self._parent.acs_in_sector)
                for i in range(self._parent.acs_in_sector):
                    self._m_acs_raw[i] = MifareClassic.Trailer.AccessConditions.Ac(i, self._io, self, self._root)

                self._io.seek(_pos)
                return self._m_acs_raw if hasattr(self, '_m_acs_raw') else None

            @property
            def trailer_ac(self):
                if hasattr(self, '_m_trailer_ac'):
                    return self._m_trailer_ac if hasattr(self, '_m_trailer_ac') else None

                _pos = self._io.pos()
                self._io.seek(0)
                self._m_trailer_ac = MifareClassic.Trailer.AccessConditions.TrailerAc(self.acs_raw[(self._parent.acs_in_sector - 1)], self._io, self, self._root)
                self._io.seek(_pos)
                return self._m_trailer_ac if hasattr(self, '_m_trailer_ac') else None

            @property
            def chunks(self):
                if hasattr(self, '_m_chunks'):
                    return self._m_chunks if hasattr(self, '_m_chunks') else None

                _pos = self._io.pos()
                self._io.seek(0)
                self._m_chunks = [None] * (self._parent.ac_bits)
                for i in range(self._parent.ac_bits):
                    self._m_chunks[i] = MifareClassic.Trailer.AccessConditions.ValidChunk(self.raw_chunks[self.remaps[i].inv_chunk_no], self.raw_chunks[self.remaps[i].chunk_no], self._io, self, self._root)

                self._io.seek(_pos)
                return self._m_chunks if hasattr(self, '_m_chunks') else None


        @property
        def ac_bits(self):
            if hasattr(self, '_m_ac_bits'):
                return self._m_ac_bits if hasattr(self, '_m_ac_bits') else None

            self._m_ac_bits = 3
            return self._m_ac_bits if hasattr(self, '_m_ac_bits') else None

        @property
        def acs_in_sector(self):
            if hasattr(self, '_m_acs_in_sector'):
                return self._m_acs_in_sector if hasattr(self, '_m_acs_in_sector') else None

            self._m_acs_in_sector = 4
            return self._m_acs_in_sector if hasattr(self, '_m_acs_in_sector') else None

        @property
        def ac_count_of_chunks(self):
            if hasattr(self, '_m_ac_count_of_chunks'):
                return self._m_ac_count_of_chunks if hasattr(self, '_m_ac_count_of_chunks') else None

            self._m_ac_count_of_chunks = (self.ac_bits * 2)
            return self._m_ac_count_of_chunks if hasattr(self, '_m_ac_count_of_chunks') else None