Zaragoza Avanza transit pass card encoder & decoder
  • TypeScript 95.7%
  • JavaScript 4.3%
Find a file
2026-02-28 17:57:09 +01:00
.forgejo/workflows Add build workflows 2026-02-28 17:57:05 +01:00
.github Add build workflows 2026-02-28 17:57:05 +01:00
.vscode Add build workflows 2026-02-28 17:57:05 +01:00
docs Add spreadsheet.avif 2026-02-28 17:57:05 +01:00
src Add more notes 2026-02-28 17:57:09 +01:00
test Dates are cracked! 2026-02-28 17:57:08 +01:00
.gitignore Dates are cracked! 2026-02-28 17:57:08 +01:00
.prettierrc Add build workflows 2026-02-28 17:57:05 +01:00
bun.lock Add build workflows 2026-02-28 17:57:05 +01:00
eslint.config.js Add build workflows 2026-02-28 17:57:05 +01:00
jsr.json Add build workflows 2026-02-28 17:57:05 +01:00
LEGAL.md Add LEGAL.md and disclaimer 2026-02-28 17:57:07 +01:00
LICENSE Add balance operations 2026-02-28 17:57:05 +01:00
package.json Add build workflows 2026-02-28 17:57:05 +01:00
README.md Add more notes 2026-02-28 17:57:09 +01:00
tsconfig.build.json Add build workflows 2026-02-28 17:57:05 +01:00
tsconfig.json Add build workflows 2026-02-28 17:57:05 +01:00

Zaragoza Avanza Bus Transit Pass pwn

Important

⚠️ Aviso legal: Este repositorio es un proyecto de investigación de seguridad independiente.
No está destinado a cometer fraude ni a facilitar el uso indebido del transporte público.
No se proporcionará asistencia para ningún uso ilícito.
Consulta LEGAL.md para el descargo de responsabilidad completo.

Below are some of my notes on the Zaragoza bus transit pass, which is a MIFARE Classic 1K card. The card has 16 sectors, each with 4 blocks of 16 bytes each. Each sector has two keys (Key A and Key B) that control access to the blocks within that sector.

Presented to you by zaragoza nerds 🤓

Click here to open the reverse engineering spreadsheet with all the collected data and highlights:

Spreadsheet

See also:

Keys

Sectors Key A Key B
0-8 04000C0F0903 0B02070A0409
9-15 A0A1A2A3A4A5 B0B1B2B3B4B5

Sectors

Sector Description
0 Manufacturer block, IDs
1 Appears to be latest transaction
2 Balance (blocks 8 and 9)
3 Empty on top up cards
4 Empty on top up cards
5 Empty
6 Empty
7 Appears to be transaction logs
8 Appears to be transaction logs
9 Unused
10 Unused
11 Unused
12 Unused
13 Unused
14 Unused
15 Unused

Blocks

Sector Block Description Template Access Conditions
0 0 [00-03] RFID's UID
[04] BCC (checksum byte)
[05] SAK (always 88 for MIFARE Classic 1K)
[15] last two digits of year the card was manufactured in (i.e. 20 for 2020, 25 for 2025)
..,,..,,..880400C8,,0020000000.. Read-only
1 [00-14] Card type (02699F for balance cards, 0A9775 for personal expiring cards)
[15] XOR of all previous bytes
..,,..000000000000000000000000,, Only Key B can write
2 [00-01] ASCII prefix (4245 for BE, 4250 for BP)
[02-04] Zaragoza card ID number (together forming prefix + number)
[05-14] empty, zeroed
[15] XOR of all previous bytes
42,,..,,..00000000000000000000.. Read-only
3 0th sector's trailer block 04000C0F0903..,,..,,0B02070A0409 Trailer
1 4 Empty on top-up cards
[00-14] unknown
[15] XOR of all previous bytes
00000000000000000000000000000000 Only Key B can write
5 Empty if new card
[00-02] 020002, 0A0100 or 0A0200
[03-04] unknown card-specific constant
[05-06] unknown
[07] integer
[08] either 01 or 02
09 unknown
[10-11] date
12 BCD-encoded hour (0-23)
13 BCD-encoded minute
14 BCD-encoded second
[15] sequence counter
020002..,,..,,..,,..,,..,,..,,.. No restrictions
6 Appears to always be empty 00000000000000000000000000000000 No restrictions
7 1st sector's trailer block 04000C0F0903..,,..,,0B02070A0409 Trailer
2 8 Balance ..,,0000..,,FFFF..,,000002FD02FD Value block1
9 Always has the same value as block 8 ..,,0000..,,FFFF..,,000002FD02FD Value block1
10 Empty if new card, correlates to block 5 (see Blocks 5 and 10)
[00-01] unknown
[02-05] same as [10-13] in block 5
[06-08] always 010200
[09-10] same as [07-08] in block 5
[11-14] always 00006300
[15] XOR of all previous bytes
Always 000000000000000A000000000000000A on unlimited personal cards
..,,..,,..,,010200..,,00006300.. No restrictions
11 2nd sector's trailer block 04000C0F0903..,,..,,0B02070A0409 Trailer
3 12 Subscription metadata 00000000000000000000000000000000 Only Key B can write
13 Subscription on personal unlimited cards 00000000000000000000000000000000 Only Key B can write
14 See block 13 00000000000000000000000000000000 Only Key B can write
15 3rd sector's trailer block 04000C0F0903..,,..,,0B02070A0409 Trailer
4 16 See block 12 00000000000000000000000000000000 Only Key B can write
17 See block 13 00000000000000000000000000000000 Only Key B can write
18 See block 13 00000000000000000000000000000000 Only Key B can write
19 4th sector's trailer block 04000C0F0903..,,..,,0B02070A0409 Trailer
5 20 Empty 00000000000000000000000000000000 Only Key B can write
21 Empty 00000000000000000000000000000000 Only Key B can write
22 Empty 00000000000000000000000000000000 Only Key B can write
23 5th sector's trailer blocks 04000C0F0903..,,..,,0B02070A0409 Trailer
6 24 Empty 00000000000000000000000000000000 Only Key B can write
25 Empty 00000000000000000000000000000000 Only Key B can write
26 Empty 00000000000000000000000000000000 Only Key B can write
27 6th sector's trailer block 04000C0F0903..,,..,,0B02070A0409 Trailer
7 28 Transaction logs; value of block 5 right before overwriting it; each subsequent write adds an entry to blocks 29, then 30, then 32, then 33 020002..,,..,,..,,..,,..,,..,,.. No restrictions
29 See block 28 020002..,,..,,..,,..,,..,,..,,.. No restrictions
30 See block 28 020002..,,..,,..,,..,,..,,..,,.. No restrictions
31 7th sector's trailer block 04000C0F0903..,,..,,0B02070A0409 Trailer
8 32 See block 28 020002..,,..,,..,,..,,..,,..,,.. No restrictions
33 See block 28 020002..,,..,,..,,..,,..,,..,,.. No restrictions
34 Expiration date, encoding unknown; always 00000000FFFFFFFF0000000000FF00FF on top up cards 00000000FFFFFFFF0000000000FF00FF Value block1
35 8th sector's trailer block 04000C0F0903..,,..,,0B02070A0409 Trailer

Value blocks 8, 9 and 34 store a 32-bit integer three times for redundancy: the value, its bitwise complement, then the value again.

The balance is stored in blocks 8 and 9 for redundancy.

Balance

€1.00 = 1000 units. For example, a balance of €5.00 would be stored as 8813000077ecffff8813000002fd02fd in blocks 8 and 9:

  1. Convert 5.00 to units: 5000
  2. Convert 5000 to hexadecimal: 1388
  3. Convert to little-endian: 8813
  4. Calculate the complement: 77ec (flip every bit)
  5. Write the value, complement, and value again, then append the static address 02fd02fd and write to blocks 8 and 9
  • Bytes 00-03: 88130000 -> little-endian -> 5000
  • Bytes 04-07: 77ecffff -> complement of 5000
  • Bytes 08-11: 88130000 -> value repeated
  • Bytes 12-13: 02fd -> address bytes (block pointer for transfer operations)
  • Bytes 14-15: 02fd -> address bytes repeated

Balance on personal unlimited cards is always 00000000FFFFFFFF0000000002FD02FD (zero).

See src/balance.ts for encoder/decoder implementation in JavaScript/TypeScript.

Transaction logs

Transaction logs are stored in sectors 1 (block 5), 7 (blocks 28-30) and 8 (blocks 32-33). Each log entry is 16 bytes, so each block can store one log entry.

  • [00-02]: Always 020002 for top up cards and 0A0100 or 0A0200 for personal cards
  • [03-04]: Card-specific constant, same across BE322742 and BE322743 dumps, could be related to fare id or season
  • [05-06]: Unknown, highest recorded value for first byte is 129, several appearances of (128, 1)
  • [07]: Most likely bus line number, but unknown how to decode lines > 255 and trams
  • [08]: Line direction (always either 01 or 02)
  • [10-11]: Transaction date
  • [15]: Unknown, some sort of sequence counter but doesn't correlate to dates

See src/transaction.ts for decoder implementation in JavaScript/TypeScript.

Subscription

  • [00-01] Subscription start date
  • [02-03] Subscription end date
  • [04-05] Unknown, appears to be 0000
  • [06-09] Unknown
  • [10-11] Unknown date, most likely the date of the last usage
  • [12-14] Unknown
  • [15] XOR of all previous bytes

Subscription metadata

  • [00-01] Unknown
  • [02-03] Some date
  • [04-07] Unknown, appears to always be 00210000
  • [08-09] Validity days (e.g. 016D for 365 days)
  • [10-14] Unknown, appears to always be 0021000000
  • [15] XOR of all previous bytes

Dates

Mad respect to li0ard for figuring out the date encoding!

Dates are composed of two bytes (16 bits), which when converted to binary:

  • the first 7 bits represent the year (based 0 at 2000)
  • 4 bits for month (1-12)
  • 5 bits for day

Examples:

34 4E -> 0011010001001110 -> 0011010 0010 01110

  • Year: 0011010 -> 26 + 2000 -> 2026
  • Month: 0010 -> 2 (February)
  • Day: 01110 -> 14

See src/date.ts for encoder/decoder implementation in JavaScript/TypeScript.

Blocks 5 and 10

Block 10 stores complimentary data to block 5, but unknown how it correlates.

	00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15

             cType ?? ?? linDr ?? yr d? HH MM SS sq
5:  02 00 02 F8 01 06 40 D2 02 25 2A DC 09 1B 2F 01
10: 00 00 2A DC 09 1B 01 02 00 D2 02 00 00 63 00 54
    ?? ?? yr d? HH MM          linDr

             cType ?? ?? linDr ?? yr d? HH MM SS sq
5:  02 00 02 F8 01 03 20 D2 01 FE 2A D8 13 2D 12 01
10: 00 00 2A D8 13 2D 01 02 00 D2 01 00 00 63 00 7F
    ?? ?? yr d? HH MM          linDr
 
             cType ?? ?? linDr ?? yr d? HH MM SS sq
5:  02 00 02 F8 01 05 DC D2 02 6B 2A D9 0D 33 3B 02
10: D2 01 2A D9 0D 33 01 02 00 D2 02 00 00 63 00 AE
    ?? ?? yr d? HH MM          linDr

             cType ?? ?? linDr ?? yr d? HH MM SS sq
5:  02 00 02 F8 01 02 BC D2 01 99 2A D9 10 26 0D 03
10: D2 02 2A D9 10 26 01 02 00 D2 01 00 00 63 00 A6
    ?? ?? yr d? HH MM          linDr

             cType ?? ?? linDr ?? yr d? HH MM SS sq
5:  02 00 02 26 01 80 CE 16 01 0D 34 54 10 2D 24 03
10: 23 02 34 54 10 2D 01 02 00 16 01 00 00 63 00 0B
    ?? ?? yr d? HH MM          linDr

cType = some constant, could be related to season or card type
linDr = line number and direction (either `01` or `02`)
yr = related to year, see ^2 in footnotes
d? = probably date in some form
HH, MM, SS = hour, minute, second
sq = sequence counter

Acknowledgements

Huge thanks to li0ard for help with decoding RFIDs!

License

MIT

Donate

hloth.dev/donate


  1. Value blocks have the following restrictions: Key A can read, decrement, restore and transfer, Key B can also write and increment. ↩︎