The key piece of data used to represent a cardholder in a Gallagher system is a "cardholder credential". This consists of a tuple of items:
- A 4-bit region code (RC). Usually displayed as a letter from
A
-P
. - A 16-bit facility code (FC). Usually displayed alongside the region code (e.g.
A12345
). - A 24-bit card number (CN). These nominally start from 1 and are usually seen in the field to range up to ~50,000.
- A 4-bit issue level (IL). These are intended to start at 1 and be incremented each time the credential needs to be re-issued (e.g. due to a card being lost or stolen).
The region and facility codes pair represents a unique site installation, while the card number and issue level pair represents a unique physical card.
There are also some unknown data items referred to in firmware which have only ever been seen set to 0 in the field, here labeled UB, UC, UD, UE and UX (for historical research reasons). It appears to be safe to always set these to 0.
This tuple of data is not encoded directly onto cards but is instead obfuscated into an 8 byte format.
First, the items (but not UX) are arranged into 8 bytes as follows:
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | |
---|---|---|---|---|---|---|---|---|
Byte 0 | CN23 | CN22 | CN21 | CN20 | CN19 | CN18 | CN17 | CN16 |
Byte 1 | FC11 | FC10 | FC9 | FC8 | FC7 | FC6 | FC5 | FC4 |
Byte 2 | CN10 | CN9 | CN8 | CN7 | CN6 | CN5 | CN4 | CN3 |
Byte 3 | CN2 | CN1 | CN0 | RC3 | RC2 | RC1 | RC0 | UB3 |
Byte 4 | UB2 | UB1 | UB0 | CN15 | CN14 | CN13 | CN12 | CN11 |
Byte 5 | UE3 | UE2 | UE1 | UE0 | FC15 | FC14 | FC13 | FC12 |
Byte 6 | UC3 | UC2 | UC1 | UC0 | UD3 | UD2 | UD1 | UD0 |
Byte 7 | FC3 | FC2 | FC1 | FC0 | IL3 | IL2 | IL1 | IL0 |
(Here, XY represents the Yth bit of X, where the 0th is the least significant bit.)
Then, the 8 resultant bytes are mapped through a substitution table (aka an S-box) to produce the final 8 byte output:
00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 0A | 0B | 0C | 0D | 0E | 0F | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
00 | A3 | B0 | 80 | C6 | B2 | F4 | 5C | 6C | 81 | F1 | BB | EB | 55 | 67 | 3C | 05 |
10 | 1A | 0E | 61 | F6 | 22 | CE | AA | 8F | BD | 3B | 1F | 5E | 44 | 04 | 51 | 2E |
20 | 4D | 9A | 84 | EA | F8 | 66 | 74 | 29 | 7F | 70 | D8 | 31 | 7A | 6D | A4 | 00 |
30 | 82 | B9 | 5F | B4 | 16 | AB | FF | C2 | 39 | DC | 19 | 65 | 57 | 7C | 20 | FA |
40 | 5A | 49 | 13 | D0 | FB | A8 | 91 | 73 | B1 | 33 | 18 | BE | 21 | 72 | 48 | B6 |
50 | DB | A0 | 5D | CC | E6 | 17 | 27 | E5 | D4 | 53 | 42 | F3 | DD | 7B | 24 | AC |
60 | 2B | 58 | 1E | A7 | E7 | 86 | 40 | D3 | 98 | 97 | 71 | CB | 3A | 0F | 01 | 9B |
70 | 6E | 1B | FC | 34 | A6 | DA | 07 | 0C | AE | 37 | CA | 54 | FD | 26 | FE | 0A |
80 | 45 | A2 | 2A | C4 | 12 | 0D | F5 | 4F | 69 | E0 | 8A | 77 | 60 | 3F | 99 | 95 |
90 | D2 | 38 | 36 | 62 | B7 | 32 | 7E | 79 | C0 | 46 | 93 | 2F | A5 | BA | 5B | AF |
A0 | 52 | 1D | C3 | 75 | CF | D6 | 4C | 83 | E8 | 3D | 30 | 4E | BC | 08 | 2D | 09 |
B0 | 06 | D9 | 25 | 9E | 89 | F2 | 96 | 88 | C1 | 8C | 94 | 0B | 28 | F0 | 47 | 63 |
C0 | D5 | B3 | 68 | 56 | 9C | F9 | 6F | 41 | 50 | 85 | 8B | 9D | 59 | BF | 9F | E2 |
D0 | 8E | 6A | 11 | 23 | A1 | CD | B5 | 7D | C7 | A9 | C8 | EF | DF | 02 | B8 | 03 |
E0 | 6B | 35 | 3E | 2C | 76 | C9 | DE | 1C | 4B | D1 | ED | 14 | C5 | AD | E9 | 64 |
F0 | 4A | EC | 8D | F7 | 10 | 43 | 78 | 15 | 87 | E4 | D7 | 92 | E1 | EE | E3 | 90 |
(The raw 256 byte subtitution table is available here).
These 8 bytes are then stored on the card using the required card-specific format.
Take the cardholder credential (RC 0 (A), FC 9876, CN 1234, IL 1).
First, arrange the data into 8 bytes as follows:
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | |
---|---|---|---|---|---|---|---|---|
Byte 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Byte 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 |
Byte 2 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 |
Byte 3 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
Byte 4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Byte 5 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
Byte 6 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Byte 7 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
This gives the 8 byte value 0x00699A4000020041
. Next, map each byte through the substitution table:
00
-> A3
69
-> 97
9A
-> 93
40
-> 5A
00
-> A3
02
-> 80
00
-> A3
41
-> 49
This gives the final result, 0xA397935AA380AA49
.
Take 0xA38A8A4BA3A3A32C
as input. First, map each byte through the inverted subsitution table:
A3
-> 00
8A
-> 8A
8A
-> 8A
4B
-> E8
A3
-> 00
A3
-> 00
A3
-> 00
2C
-> E3
This gives 0x008A8AE8000000E3
. Next, arrange the data and read out the constituent parts:
Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | |
---|---|---|---|---|---|---|---|---|
Byte 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Byte 1 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
Byte 2 | 1 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
Byte 3 | 1 | 1 | 1 | 0 | 1 | 0 | 0 | 0 |
Byte 4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Byte 5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Byte 6 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Byte 7 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 1 |
This gives a resulting credential of (RC 4 (D), FC 2222, CN 1111, IN 3) (and all unknown fields set to 0).