Information has been obtained from the following link
https://gist.github.com/auscompgeek/65b ... ba19262420
Opal is the public transport smartcard ticketing system in Sydney, Australia.
Opal cards are MIFARE DESFire EV1 cards, with the application ID 0x314553. All files are restricted except for file 7, which is freely readable.
The official Android app can interpret this free read data. Much of the information here was derived by reverse engineering this app.
Opal Desfire Card - Transport NSW Australia
Format
The free read file is 16 octets long.
The data is essentially a 128-bit integer, stored in little-endian.
As the fields are not byte-aligned, reading the data as a bitstream is difficult (read: impossible) without reversing the order of the octets.
Offsets and lengths listed are measured in bits, and assume you have reversed the octets. Ranges listed do not include the end bit.
The free read file is 16 octets long.
The data is essentially a 128-bit integer, stored in little-endian.
As the fields are not byte-aligned, reading the data as a bitstream is difficult (read: impossible) without reversing the order of the octets.
Offsets and lengths listed are measured in bits, and assume you have reversed the octets. Ranges listed do not include the end bit.
Start | End | Length | Description |
0 | 16 | 16 | Checksum |
16 | 20 | 1 | Weekly paid journey count |
20 | 21 | 1 | Auto top-up enabled |
21 | 25 | 4 | Last tap usage type |
25 | 28 | 3 | Mode of transport |
28 | 39 | 11 | Last tap time: minutes since 00:00 |
39 | 54 | 15 | Last tap date: days since epoch |
54 | 75 | 21 | Balance in cents (two's complement) |
75 | 91 | 16 | Transaction sequence number |
91 | 92 | 1 | Card status, 1 if blocked |
92 | 96 | 4 | Serial number check digit |
96 | 128 | 32 | Serial number |
Usage types
Transfers between ferries can only occur at Circular Quay.
ID | Description |
0 | Card has not been used |
1 | Tap on: new journey |
2 | Tap on: transfer from same mode |
3 | Tap on: transfer from different mode |
4 | Tap on: Manly-CQ ferry: new journey |
5 | Tap on: Manly-CQ ferry: transfer from another ferry 1 |
6 | Tap on: Manly-CQ ferry: transfer from different mode |
7 | Tap off: distance based fare |
8 | Tap off: flat-rate fare |
9 | Tap off: automatically completed journey (failure to tap off) |
10 | Tap off: end of trip without start (failure to tap on) |
11 | Tap off: tap on reversal |
12 | Unsuccessful tap (low balance?) |
13 | (reserved) |
14 | (reserved) |
15 | (reserved) |
Transfers between ferries can only occur at Circular Quay.
Opal Decode for Python
https://gist.github.com/auscompgeek/c05 ... b84a2bd7d1
https://gist.github.com/auscompgeek/c05 ... b84a2bd7d1
Code: Select all
#!/usr/bin/env python3
import binascii
import enum
import typing
class Mode(enum.IntEnum):
RAIL = 0
FERRY = 1
BUS = 2
class Usage(enum.IntEnum):
UNUSED = 0
NEW_JOURNEY = 1
TRANSFER_SAME_MODE = 2
TRANSFER_INTERMODE = 3
NEW_JOURNEY_MCQ = 4
TRANSFER_SAME_MODE_MCQ = 5
TRANSFER_INTERMODE_MCQ = 6
DISTANCE_BASED_FARE = 7
FLAT_FARE = 8
AUTO_COMPLETE_JOURNEY = 9
END_NO_START = 10
TAP_REVERSAL = 11
UNSUCCESSFUL = 12
class FreeReadData(typing.NamedTuple):
serial: str
serial_check: int
blocked: bool
seq_num: int
balance: int
last_date: int
last_time: int
last_mode: Mode
last_usage: Usage
auto_topup_enabled: bool
journey_count: int
crc16: int
def twos_complement(input_value: int, num_bits: int) -> int:
"""Calculate a two's complement integer from the given input value's bits."""
mask = 1 << (num_bits - 1)
return (input_value & ~mask) - (input_value & mask)
def split_bits(data: int, num_bits: int) -> typing.Tuple[int, int]:
return data & ((1 << num_bits) - 1), data >> num_bits
def parse_data(b: bytes) -> FreeReadData:
assert len(b) == 16
data = int.from_bytes(b, "little")
serial = "%09d" % (data & 0xFFFFFFFF)
data >>= 32
serial_check = data & 0xF
data >>= 4
blocked = bool(data & 1)
data >>= 1
seq_num = data & 0xFFFF
data >>= 16
balance_unsigned, data = split_bits(data, 21)
last_date, data = split_bits(data, 15)
last_time, data = split_bits(data, 11)
last_mode = Mode(data & 0b111)
data >>= 3
last_usage = Usage(data & 0xF)
data >>= 4
auto_topup_enabled = bool(data & 1)
data >>= 1
journey_count = data & 0xF
data >>= 4
crc16 = data
balance = twos_complement(balance_unsigned, 21)
return FreeReadData(
serial,
serial_check,
blocked,
seq_num,
balance,
last_date,
last_time,
last_mode,
last_usage,
auto_topup_enabled,
journey_count,
crc16,
)
if __name__ == "__main__":
b = bytes.fromhex(input())
data = parse_data(b)
print(data)
# print(binascii.crc_hqx(b[:-2], 0))
Reading Card Data Using an Android Device
Because File 7 on the Opal Card is unrestricted, the file contents can be read using an Android Device that supports NFC,
using the excellent NFC Tag Info Software written by NXP.
This software is available free of charge on the Google Play Store. (See Link Below)
NFC Tag Info by NXP
The following is a screenshot of a scanned Opal Card
Because File 7 on the Opal Card is unrestricted, the file contents can be read using an Android Device that supports NFC,
using the excellent NFC Tag Info Software written by NXP.
This software is available free of charge on the Google Play Store. (See Link Below)
NFC Tag Info by NXP
The following is a screenshot of a scanned Opal Card