The Door
The Door |
---|
What: |
How to authenticate members at the door. |
Participants: |
{{#arraymap:User:Bracken, User:Benjie|,|x|Has participant::x}} |
Plan is to use NFC with MIFARE Classic 1K cards because they are cheap and we can get it working quickly. We will use the broken crypto on the card but mitigate this by using an encrypted rolling count to detect cloning. We will leave options open to move to a MIFARE DESFire EV1 card down the road, these cards cost more, the MFRC522 can talk to them, but we need to write our own library.
Hardware
- Raspberry Pi running some linux (currently raspbian).
- MFRC522 reader IC
MIFARE Classic 1K Layout
The default sector access configuration on my cards is:
Key A | Access bits | Key B | |||
---|---|---|---|---|---|
FF FF FF FF FF FF* | FF | 07 | 80 | 69** | FF FF FF FF FF FF |
1111 1111 | 0000 0111 | 1000 0000 | 0110 1001 | ||
C23 C22 C21 C20 C13 C12 C11 C10 | C13 C12 C11 C10 C33 C32 C31 C30 | C33 C32 C31 C30 C23 C22 C21 C20 | User data |
*Keys are blanked to 0 when read unless access bits allow reading. **Byte is user data only.
Which means that Key A can:
- Write Key A.
- Read/Write Access bits.
- Read/Write Key B.
- Read/Write/Incriment/Decrement/Transfer/Restore Sector 1/2/3.
Key B can:
- read transport configuration???
Read/Write/Incriment/Decrement/Transfer/Restore Sector 1/2/3.Unless Key B is readable in sector trailer (it is), in which case it is just more user data.
Sector | Block | Byte | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ||
0 | 0 | NUID | Manufacturer Data | ||||||||||||||
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | Key A | Access bits | Key B | ||||||||||||||
1 | 0 | ||||||||||||||||
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | Key A | Access bits | Key B | ||||||||||||||
2 | 0 | ||||||||||||||||
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | Key A | Access bits | Key B | ||||||||||||||
3 | 0 | ||||||||||||||||
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | Key A | Access bits | Key B | ||||||||||||||
4 | 0 | ||||||||||||||||
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | Key A | Access bits | Key B | ||||||||||||||
5 | 0 | ||||||||||||||||
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | Key A | Access bits | Key B | ||||||||||||||
6 | 0 | ||||||||||||||||
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | Key A | Access bits | Key B | ||||||||||||||
7 | 0 | ||||||||||||||||
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | Key A | Access bits | Key B | ||||||||||||||
8 | 0 | ||||||||||||||||
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | Key A | Access bits | Key B | ||||||||||||||
9 | 0 | ||||||||||||||||
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | Key A | Access bits | Key B | ||||||||||||||
10 | 0 | ||||||||||||||||
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | Key A | Access bits | Key B | ||||||||||||||
11 | 0 | ||||||||||||||||
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | Key A | Access bits | Key B | ||||||||||||||
12 | 0 | ||||||||||||||||
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | Key A | Access bits | Key B | ||||||||||||||
13 | 0 | ||||||||||||||||
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | Key A | Access bits | Key B | ||||||||||||||
14 | 0 | ||||||||||||||||
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | Key A | Access bits | Key B | ||||||||||||||
15 | 0 | ||||||||||||||||
1 | |||||||||||||||||
2 | |||||||||||||||||
3 | Key A | Access bits | Key B |
4K cards have trailing sectors of varying block length from here but are identical up to sector 15.
Some small checksum formats here.
Excercises
Key B is rw, Key A is ro
Sector trailer:
Key A | Access bits | Key B | |||
---|---|---|---|---|---|
6B 65 79 20 61 00 | 78 | 77 | 88 | XX | 6B 65 79 20 62 00 |
0111 1000 | 0111 0111 | 1000 1000 | XXXX XXXX | ||
C23 C22 C21 C20 C13 C12 C11 C10 | C13 C12 C11 C10 C33 C32 C31 C30 | C33 C32 C31 C30 C23 C22 C21 C20 | User data |
Key B is rw data & access bytes, Key A is rw data
Sector trailer:
Key A | Access bits | Key B | |||
---|---|---|---|---|---|
6B 65 79 20 61 00 | 7F | 07 | 88 | XX | 6B 65 79 20 62 00 |
0111 1111 | 0000 0111 | 1000 1000 | XXXX XXXX | ||
C23 C22 C21 C20 C13 C12 C11 C10 | C13 C12 C11 C10 C33 C32 C31 C30 | C33 C32 C31 C30 C23 C22 C21 C20 | User data |
Authentication Methodology
Encrypting the Counter
The count is used to mitigate card cloning.
Sector 1 and sector 2 will hold a count each, the lower value or invalid sector will be incremented to the higher or valid sector plus 1 and this read back before authentication is accepted, then only if the higher or valid count is higher than that last count authenticated for that card, a lower count is guaranteed to be a clone card. Accepting higher numbers allows for loss of communication between the door and the members area and for successful writes with failed readback. The count is a 16-bit integer which wraps and higher/lower is considered to be half way round the loop.
The count shall be padded with a secret (different secrets for sector 1 and 2) and hashed with bcypt, the count (not the secret) and the hash will be stored in the sector.
Each count sector format will be:
Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Block | Content | |||||||||||||||
0 | uint16 clear count | uint8 algorithm | uint8 cost | 40-byte salt & digest | ||||||||||||
1 | salt & digest cont. | |||||||||||||||
2 | salt & digest cont. | reserved & zero | crc16 | |||||||||||||
3 | Key A | Access bytes 0x7F078869 |
Key B |
plaintext count
The count for this sector stored in clear stored as 16-bit unsigned big-endian integer.
algorithm
The hashing algorithm stored as an 8-bit unsigned enum.
Value | Algorithm |
---|---|
0 | bcrypt 2 |
1 | bcrypt 2a |
2 | bcrypt 2b |
3 | bcrypt 2y |
The door uses the default format for the version of the bcrypt library installed, currently '2a'.
cost
For bcrypt, the cost as 8-bit unsigned integer.
The door currently uses a cost of 8.
salt & digest
The 53-character base64 encoded salt and digest portion of the bcrypt string stored as 40-byte binary, big-endian, padded on the right with zeros.
reserved
Unused bytes, must be zero.
crc6
A crc16 (Xmodem) checksum of the raw sector from LSB up to this point stored as 16-bit unsigned big-endian integer.
Key A
As per MIFARE spec.
Access bytes
As per MIFARE spec
0x7F078869
Key A may read/write block 0-3.
Key B may read/write block 0-2.
Key B
As per MIFARE spec.
We will need to generate and keep the following for each tag:
Field | Format | At door? | On card? |
---|---|---|---|
count | unit16 initialised to 1 | yes | yes |
sector_a_sector | 1-byte int, sector on tag containing a | yes | NO |
sector_a_secret | 23-byte raw random data | yes | NO |
sector_a_key_a | 6-byte raw random data | yes** | yes* |
sector_a_key_b | 6-byte raw random data | yes | yes* |
sector_a_secret | 23-byte raw random data | yes | NO |
sector_b_sector | 1-byte int, sector on tag containing b | yes | NO |
sector_b_key_a | 6-byte raw random data | yes** | yes* |
sector_b_key_b | 6-byte raw random data | yes | yes* |
*Key A cannot read Key B and Key B cannot read Key A.
**Not needed at door, but not a lot more harmful than B.
We need to generate 70 bytes of cryptographically secure random data to initialise a tag. No random data is needed at authentication. This always takes less than a minute of mouse wiggling on my netbook.
The cost is a constant at the door and can be changed at any time.
Authentication Process
- Read NUID.
- Load NUID validity or log unknown tag.
- Load NUID user or log unallocated card found.
- Load known_count.
- Read sector (sector=1).
- Validate sector (sector=1).
- Read sector (sector=2).
- Validate sector (sector=2).
- If (sector_1 - sector_2) != absolute(1) log a spacing error and continue.
- If sector_1 == sector_2 log a spacing error and continue.
- If sector_1 > sector_2 or sector_2 is not valid:
- Assert sector_1 >= known_count or log duplicate card detected and reject
- Write Sector(sector=2 count=sector_1 + 1)
- Read Sector(sector=2)
- Validate Sector(sector=2)
- Assert sector_2 = sector_1 + 1 or log incrimentation failed and reject
- Else:
- Assert sector_2 >= known_count or log duplicate card detected and reject
- Write Sector(sector=1 count=sector_2 + 1)
- Read sector(sector=2)
- Validate Sector(sector=1)
- Assert sector_2 = sector_1 + 1 or log incrimentation failed and reject
- Open Door
Read Sector() as quickly as possible:
- Load sector Key B
- Read block 0.
- Read block 1.
- Read block 2.
- Load Sector secret.
Validate Sector():
- Assert crc16 matches sector or return corrupt sector
- Assert algorithm in list or return unknown algorithm
- Assert reserved bytes = 0 or return unexpected padding
- Re-assemble bcrypt string
- Assert count and secret matches hash or return counterfeit count
- Return authentic count
Write Sector():
- Load sector Key B
- Load Sector secret
- bcrypt count & secret
- pad
- crc16
- Write block0
- Write block1
- Write block2
The cost should be selected such that this process is ~1 second on the door hardware.