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

  1. Read NUID.
  2. Load NUID validity or log unknown tag.
  3. Load NUID user or log unallocated card found.
  4. Load known_count.
  5. Read sector (sector=1).
  6. Validate sector (sector=1).
  7. Read sector (sector=2).
  8. Validate sector (sector=2).
  9. If (sector_1 - sector_2) != absolute(1) log a spacing error and continue.
  10. If sector_1 == sector_2 log a spacing error and continue.
  11. If sector_1 > sector_2 or sector_2 is not valid:
    1. Assert sector_1 >= known_count or log duplicate card detected and reject
    2. Write Sector(sector=2 count=sector_1 + 1)
    3. Read Sector(sector=2)
    4. Validate Sector(sector=2)
    5. Assert sector_2 = sector_1 + 1 or log incrimentation failed and reject
  12. Else:
    1. Assert sector_2 >= known_count or log duplicate card detected and reject
    2. Write Sector(sector=1 count=sector_2 + 1)
    3. Read sector(sector=2)
    4. Validate Sector(sector=1)
    5. Assert sector_2 = sector_1 + 1 or log incrimentation failed and reject
  13. Open Door

Read Sector() as quickly as possible:

  1. Load sector Key B
  2. Read block 0.
  3. Read block 1.
  4. Read block 2.
  5. Load Sector secret.

Validate Sector():

  1. Assert crc16 matches sector or return corrupt sector
  2. Assert algorithm in list or return unknown algorithm
  3. Assert reserved bytes = 0 or return unexpected padding
  4. Re-assemble bcrypt string
  5. Assert count and secret matches hash or return counterfeit count
  6. Return authentic count

Write Sector():

  1. Load sector Key B
  2. Load Sector secret
  3. bcrypt count & secret
  4. pad
  5. crc16
  6. Write block0
  7. Write block1
  8. Write block2

The cost should be selected such that this process is ~1 second on the door hardware.

This article is issued from Old-wiki. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.