docs: roadmap index + custom-14a-command plans + backfill plan docs
- Add docs/plans/README.md as the roadmap index cataloguing all design/plan docs. - Add the custom ISO14443-A command handling design + plan (L3 NTAG I2C SECTOR_SELECT / cross-sector reads + native auth; L4 static APDUs + WTX relay), produced from a multi-agent design workflow. - Backfill previously-uncommitted plan docs (trace-formatter, ndef-trace-decode, live-sniff, firmware-upstream-rebase, 14a-live-trace) and NTAG5_SECURITY.md. - Sync CLAUDE.md package structure with the committed transponder models. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
780
docs/NTAG5_SECURITY.md
Normal file
780
docs/NTAG5_SECURITY.md
Normal file
@@ -0,0 +1,780 @@
|
||||
# NTAG5 Link (NTP5332) Security Configuration Reference
|
||||
|
||||
This document captures key technical details for implementing ISO 29167-10 AES-128 authentication on NTAG5 Link chips.
|
||||
|
||||
## Configuration Memory Map
|
||||
|
||||
### Device Security Configuration (DEV_SEC_CONFIG)
|
||||
|
||||
**Address:** Block 0x3F (NFC) / 0x103F (I2C)
|
||||
|
||||
| Byte | Name | Description |
|
||||
| ---- | --------------- | --------------------------- |
|
||||
| 0 | DEV_SEC_CONFIG | Security mode and lock bits |
|
||||
| 1 | SRAM_CONF_PROT | SRAM and config protection |
|
||||
| 2 | PP_AREA_1 (LSB) | Protection pointer area 1 |
|
||||
| 3 | PP_AREA_1 (MSB) | Protection pointer area 1 |
|
||||
|
||||
**DEV_SEC_CONFIG Bit Definition:**
|
||||
|
||||
| Bits | Name | Value | Description |
|
||||
| ---- | ------------- | ----- | ------------------------ |
|
||||
| 7-5 | Security Lock | 010b | Locked (cannot modify) |
|
||||
| | | 101b | Writable (default) |
|
||||
| 4 | RFU | 00b | Reserved |
|
||||
| 3-0 | NFC_SEC_MODE | 0010b | AES mode (NTP5332 only) |
|
||||
| | | 0101b | Plain password (default) |
|
||||
|
||||
**Example Values:**
|
||||
- `0xA5` = Writable + Plain password mode (factory default)
|
||||
- `0xA2` = Writable + AES mode
|
||||
|
||||
### NFC Global Crypto Header (NFC_GCH)
|
||||
|
||||
**Address:** Block 0x0C (NFC) / 0x100C (I2C), **Byte 1** (other bytes are RFU)
|
||||
|
||||
**What it controls:** NFC_GCH defines the status/access enforcement for passwords (in password mode), keys, protection pointer & conditions, key headers, key privileges, crypto configuration header, and EAS/AFI protection.
|
||||
|
||||
**IMPORTANT:** NFC_GCH is **one-way programmable** (lower → higher values only) and **irreversible**.
|
||||
|
||||
**Allowed Values (AES Mode):** All other values are invalid.
|
||||
|
||||
| Value | Status | Description |
|
||||
| ----- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 0x81 | Deactivated (default) | Protection pointer/conditions not activated; user memory R/W possible without prior mutual auth; PP/PPC can be modified via PROTECT PAGE; keys & privileges are readable/writable via READ/WRITE CONFIG according to each key's KH status; EAS/AFI protection not activated |
|
||||
| 0x87 | Deactivated & privileges locked | Same as 0x81, but Key Privileges are locked (cannot be modified). EAS/AFI still not protected |
|
||||
| 0xC1 | Access right activated | Protection pointer/conditions enabled; user-memory R/W protection enforced per configuration; keys/privileges still readable/writable according to KH status; EAS/AFI protection enabled |
|
||||
| 0xC7 | Access right activated & privileges locked | Same as 0xC1, but Key Privileges are locked |
|
||||
| 0xE7 | Activated (final) | Protection enabled; all Key Headers/Privileges/Keys are locked (cannot be modified); EAS/AFI protection enabled |
|
||||
|
||||
**Allowed Values (Plain Password Mode):**
|
||||
|
||||
| Value | Status | Description |
|
||||
| ----- | ------------------ | ------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 0x81 | Writable (default) | Passwords readable/writable via READ/WRITE CONFIG |
|
||||
| 0xE7 | Locked | Passwords not readable/writable via READ/WRITE CONFIG (note: separate LOCK PASSWORD needed to lock passwords permanently) |
|
||||
|
||||
### NFC Crypto Configuration Header (NFC_CCH)
|
||||
|
||||
**Address:** Block 0x0D (NFC) / 0x100D (I2C), **Byte 1** (other bytes are RFU)
|
||||
|
||||
**What it controls:** NFC_CCH controls whether the NFC Authentication Limit can be changed freely or only after authentication. This is the "global knob" for Authentication Limit behavior (not general access-right enforcement).
|
||||
|
||||
**Allowed Values:** All other values are invalid.
|
||||
|
||||
| Value | Status | Description |
|
||||
| ----- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 0x81 | Unlocked (default) | Authentication limit can be modified freely |
|
||||
| 0xE7 | Locked | Authentication limit can only be modified after mutual auth with a key that has the Crypto Config privilege (bit 5 of NFC_KPx) |
|
||||
|
||||
### NFC Key Headers (NFC_KHx)
|
||||
|
||||
**Addresses:** Key headers live in blocks 0x10/0x12/0x14/0x16, specifically in **Byte 1** of each block (other bytes are RFU).
|
||||
|
||||
| Key | Block (NFC) | Block (I2C) | Byte Position |
|
||||
| ------- | ----------- | ----------- | ------------- |
|
||||
| NFC_KH0 | 0x10 | 0x1010 | Byte 1 |
|
||||
| NFC_KH1 | 0x12 | 0x1012 | Byte 1 |
|
||||
| NFC_KH2 | 0x14 | 0x1014 | Byte 1 |
|
||||
| NFC_KH3 | 0x16 | 0x1016 | Byte 1 |
|
||||
|
||||
**Key Header Values:**
|
||||
|
||||
| Value | Status | Description |
|
||||
| ----- | -------------------- | ------------------------------------------------------------- |
|
||||
| 0x81 | Not active (default) | Key can be read/written but CANNOT be used for authentication |
|
||||
| 0xE7 | Active and locked | Key is active and locked - can be used for authentication |
|
||||
| 0xFF | Disabled | Key slot is disabled (NXP recommends disabling unused keys) |
|
||||
|
||||
**IMPORTANT:**
|
||||
- Keys with header 0x81 cannot be used for authentication
|
||||
- The header must be set to 0xE7 to activate the key
|
||||
- Once 0xE7 is set, the key value cannot be modified
|
||||
- Key headers are **one-way** (lower→higher values only) and **irreversible**
|
||||
|
||||
### NFC Key Privileges (NFC_KPx)
|
||||
|
||||
**Addresses:** Key privileges live in blocks 0x11/0x13/0x15/0x17, specifically in **Byte 0** of each block (other bytes are RFU).
|
||||
|
||||
| Key | Block (NFC) | Block (I2C) | Byte Position |
|
||||
| ------- | ----------- | ----------- | ------------- |
|
||||
| NFC_KP0 | 0x11 | 0x1011 | Byte 0 |
|
||||
| NFC_KP1 | 0x13 | 0x1013 | Byte 0 |
|
||||
| NFC_KP2 | 0x15 | 0x1015 | Byte 0 |
|
||||
| NFC_KP3 | 0x17 | 0x1017 | Byte 0 |
|
||||
|
||||
**NFC_KPx Bit Meanings (AES mode):**
|
||||
|
||||
Each NFC_KPx is a 1-byte bitmask. Bit = 1 grants that capability after successful mutual authentication using KeyID = x.
|
||||
|
||||
| Bit | Name | Privilege Granted |
|
||||
| --- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 7 | Restricted AREA_1 Write | Write access to restricted user memory AREA_1 |
|
||||
| 6 | Restricted AREA_1 Read | Read access to restricted user memory AREA_1 |
|
||||
| 5 | Crypto Config | Preset of Authentication Limit |
|
||||
| 4 | EAS/AFI | Access to write-alike EAS/AFI commands: PROTECT EAS/AFI, SET EAS, RESET EAS, LOCK EAS, WRITE EAS ID, WRITE AFI, LOCK AFI |
|
||||
| 3 | Destroy | Access to DESTROY functionality |
|
||||
| 2 | Privacy | Enable/disable PRIVACY mode |
|
||||
| 1 | Write | Write access to the protected user memory area |
|
||||
| 0 | Read | Read access to the protected user memory area |
|
||||
|
||||
**Common Privilege Values:**
|
||||
- `0x03` = Read + Write access to protected memory
|
||||
- `0x07` = Read + Write + Privacy control
|
||||
- `0xFF` = All privileges enabled
|
||||
|
||||
### AES Key Storage
|
||||
|
||||
**Key Addresses (per NTP53x2 datasheet page 16):**
|
||||
| Key | Blocks (NFC) | Blocks (I2C) | Size |
|
||||
| ---- | ------------ | ------------- | ------------------- |
|
||||
| KEY0 | 0x20-0x23 | 0x1020-0x1023 | 16 bytes (4 blocks) |
|
||||
| KEY1 | 0x24-0x27 | 0x1024-0x1027 | 16 bytes |
|
||||
| KEY2 | 0x28-0x2B | 0x1028-0x102B | 16 bytes |
|
||||
| KEY3 | 0x2C-0x2F | 0x102C-0x102F | 16 bytes |
|
||||
|
||||
**NOTE:** Addresses 0x1E-0x1F are RFU (reserved), NOT key storage!
|
||||
|
||||
**Default Key Value:** Unknown - likely not all zeros. Factory default may vary.
|
||||
|
||||
**Key Write Requirements:**
|
||||
- Must enable AES mode (DEV_SEC_CONFIG = 0xA2) before writing keys
|
||||
- Keys can be written and read back while header is 0x81 (not active)
|
||||
- Once a key header is set to 0xE7, the key storage becomes unreadable
|
||||
- Always verify key values by reading back before activating headers
|
||||
|
||||
## Safe Programming Order
|
||||
|
||||
This order prevents locking yourself out when provisioning AES mode on an NTP5332.
|
||||
|
||||
### Step 1: Keep NFC_GCH at 0x81 (Deactivated)
|
||||
|
||||
While NFC_GCH = 0x81:
|
||||
- Protection settings aren't enforced yet
|
||||
- Protection pointer/conditions can be modified via PROTECT PAGE without prior mutual auth
|
||||
- Keys and privileges are freely readable/writable
|
||||
|
||||
**WARNING:** NFC_GCH only programs one-way (lower→higher) and is irreversible. Don't jump to C1/C7/E7 until you've tested.
|
||||
|
||||
### Step 2: Set Up Memory Protection Layout
|
||||
|
||||
Configure these while GCH is still 0x81:
|
||||
- Protection Pointer Address
|
||||
- Protection Pointer Condition
|
||||
- Any EAS/AFI protection conditions you plan to use
|
||||
|
||||
### Step 3: Write Key Privileges and Keys
|
||||
|
||||
While each NFC_KHx is still 0x81 (Not active):
|
||||
- Write NFC_KPx (Key Privileges) to define what each key can authorize
|
||||
- Write the 128-bit key values to storage blocks (0x20-0x2F)
|
||||
- Key and privileges are readable/writable via READ/WRITE CONFIG
|
||||
- Key is not active for authentication yet
|
||||
|
||||
**Key Storage Addresses (per datasheet page 16):**
|
||||
- KEY0: blocks 0x20-0x23
|
||||
- KEY1: blocks 0x24-0x27
|
||||
- KEY2: blocks 0x28-0x2B
|
||||
- KEY3: blocks 0x2C-0x2F
|
||||
|
||||
### Step 4: Activate/Lock Only the Keys You Need
|
||||
|
||||
For each key you intend to use:
|
||||
- Set NFC_KHx → 0xE7 (Active and locked)
|
||||
- Key becomes usable for authentication
|
||||
- Key material can no longer be read/written via READ/WRITE CONFIG
|
||||
|
||||
For unused keys (NXP recommendation):
|
||||
- Set NFC_KHx → 0xFF (Disabled)
|
||||
- Prevents unused key slots from being exploited
|
||||
|
||||
**WARNING:** Key headers are one-way (lower→higher) and irreversible.
|
||||
|
||||
### Step 5: Enable Enforcement
|
||||
|
||||
Set NFC_GCH → 0xC1 (Access right activated):
|
||||
- Read/write protection becomes enforced according to your settings
|
||||
- EAS/AFI protection becomes enforced
|
||||
- Keys can still be modified if their headers are still 0x81
|
||||
|
||||
### Step 6: Test Everything
|
||||
|
||||
Verify your protection configuration:
|
||||
- Try protected reads/writes WITHOUT mutual auth (should be denied)
|
||||
- Mutual-auth with each KeyID and confirm only the privileges in that key's KPx work
|
||||
- Test all access patterns you need
|
||||
|
||||
### Step 7: (Optional) Freeze Configuration
|
||||
|
||||
Only after thorough testing:
|
||||
- NFC_GCH → 0xC7: Locks privileges, keeps enforcement, keys still modifiable per their KH status
|
||||
- NFC_GCH → 0xE7: "Finalize" state - enforcement on, all key headers/privileges/keys locked forever
|
||||
|
||||
### Step 8: (Optional) Lock Authentication Limit
|
||||
|
||||
Only when you're sure about auth-limit behavior:
|
||||
- Set NFC_CCH → 0xE7 to lock the authentication-limit setting
|
||||
- After this, auth limit can only be modified with a key that has Crypto Config privilege (bit 5)
|
||||
|
||||
### Footgun to Avoid
|
||||
|
||||
**Authentication Limit:** If you enable the Authentication Limit feature (block 0x0E), hitting the terminal failure count causes **permanent, irreversible lockout** of that key slot. Set a reasonable limit or leave at 0 (unlimited) during development.
|
||||
|
||||
## Legacy Key Provisioning (Simple Version)
|
||||
|
||||
For simple use cases (single key, no protection pointer):
|
||||
|
||||
1. **Ensure AES Mode is Enabled**
|
||||
- Read DEV_SEC_CONFIG at 0x3F
|
||||
- If bits 3-0 != 0010b, write new value with AES mode enabled
|
||||
- Power cycle the chip (remove and replace on reader)
|
||||
|
||||
2. **Write Key Value**
|
||||
- Write 16 bytes to KEY0 blocks 0x20-0x23
|
||||
- Key can only be written when NFC_KH0 is 0x81
|
||||
|
||||
3. **Activate Key Header**
|
||||
- Write 0xE7 to block 0x10 byte 1
|
||||
- This is IRREVERSIBLE - key is locked after this
|
||||
|
||||
4. **(Optional) Activate NFC_GCH**
|
||||
- Write 0xC1 or 0xE7 to enable protection pointer enforcement
|
||||
- 0xE7 locks all crypto configuration permanently
|
||||
|
||||
## Authentication Protocol
|
||||
|
||||
### COMPLETE MAM AUTHENTICATION (VERIFIED WORKING 2026-02-13)
|
||||
|
||||
**NTAG5 Link uses UNDOCUMENTED AuthMethod values for MAM:**
|
||||
| Value | Method | Result |
|
||||
| ----- | --------------- | -------------------------------------------------- |
|
||||
| 0x00 | TAM1 (Tag Auth) | SUCCESS - 18 byte response |
|
||||
| 0x02 | **MAM1** | SUCCESS - 24 byte response |
|
||||
| 0x06 | **MAM2** | SUCCESS - empty response (authentication complete) |
|
||||
| 0x80 | MAM1 (per ISO) | Error 0x0F (NOT SUPPORTED) |
|
||||
| 0x90 | MAM2 (per ISO) | Error 0x0F (NOT SUPPORTED) |
|
||||
|
||||
**CRITICAL BYTE ORDERING RULES:**
|
||||
All data to/from the chip must be byte-reversed:
|
||||
- IChallenge sent to chip: **REVERSED**
|
||||
- TChallenge_high from response: **REVERSED**
|
||||
- IResponse sent to chip: **REVERSED**
|
||||
|
||||
### MAM1 (AuthMethod = 0x02)
|
||||
|
||||
**Command Format:**
|
||||
```
|
||||
FLAGS(0x22) | CMD(0x35) | UID(8) | CSI(0x00) | AuthMethod(0x02) | KeyID | IChallenge[::-1]
|
||||
```
|
||||
- IChallenge: 10 bytes, must be **REVERSED** before sending
|
||||
|
||||
**Response Structure (24 bytes):**
|
||||
| Offset | Length | Content |
|
||||
| ------ | ------ | ---------------------------------------------- |
|
||||
| 0 | 1 | Flags: 0x04 |
|
||||
| 1 | 1 | Header: 0xA7 |
|
||||
| 2-7 | 6 | TChallenge[79:32] (plaintext, needs reversal!) |
|
||||
| 8-23 | 16 | Encrypted block |
|
||||
|
||||
**Decryption Procedure:**
|
||||
1. Extract TChallenge_high_raw = bytes 2-7 (6 bytes)
|
||||
2. Extract encrypted block = bytes 8-23 (16 bytes)
|
||||
3. **REVERSE** encrypted block, then AES-ECB decrypt
|
||||
4. Parse decrypted: `C_MAM1(2) || TChallenge[31:0](4) || IChallenge(10)`
|
||||
5. **REVERSE** TChallenge_high_raw to get actual TChallenge_high
|
||||
6. TChallenge[79:0] = TChallenge_high + TChallenge_low
|
||||
|
||||
**Verified Constants:**
|
||||
- C_MAM1 = 0xDA83
|
||||
- IChallenge in decrypted = original IChallenge (chip de-reverses what it receives)
|
||||
|
||||
### MAM2 (AuthMethod = 0x06)
|
||||
|
||||
**Command Format (NO KeyID!):**
|
||||
```
|
||||
FLAGS(0x22) | CMD(0x35) | UID(8) | CSI(0x00) | AuthMethod(0x06) | IResponse[::-1]
|
||||
```
|
||||
- IResponse: 16 bytes, must be **REVERSED** before sending
|
||||
|
||||
**IResponse Computation:**
|
||||
```python
|
||||
# Build plaintext (16 bytes)
|
||||
C_MAM2_PURPOSE = bytes([0xDA, 0x80]) # C_MAM2[11:0] || Purpose[3:0]
|
||||
IChallenge_31_0 = original_ichallenge[6:10] # Last 4 bytes of ORIGINAL
|
||||
TChallenge_79_0 = tc_high_reversed + tc_low # From MAM1 response
|
||||
|
||||
plaintext = C_MAM2_PURPOSE + IChallenge_31_0 + TChallenge_79_0
|
||||
|
||||
# Compute IResponse
|
||||
iresponse_to_send = AES_DEC(key, plaintext)[::-1] # Decrypt then REVERSE
|
||||
```
|
||||
|
||||
**Expected Response:** Empty (just flags/header, no data) = SUCCESS
|
||||
|
||||
### Complete MAM Example (Python)
|
||||
|
||||
```python
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
def perform_mam(hcard, proto, uid_lsb, key, key_id, ichallenge):
|
||||
cipher = AES.new(key, AES.MODE_ECB)
|
||||
|
||||
# MAM1: Send IChallenge REVERSED
|
||||
mam1_cmd = bytes([0x22, 0x35]) + uid_lsb + bytes([0x00, 0x02, key_id]) + ichallenge[::-1]
|
||||
mam1_resp = send_command(hcard, proto, mam1_cmd)
|
||||
|
||||
# Parse MAM1 response
|
||||
tc_high_raw = mam1_resp[2:8] # Needs reversal!
|
||||
encrypted = mam1_resp[8:24]
|
||||
|
||||
decrypted = cipher.decrypt(encrypted[::-1])
|
||||
assert decrypted[0:2] == bytes([0xDA, 0x83]) # C_MAM1
|
||||
|
||||
tc_high = tc_high_raw[::-1] # REVERSE!
|
||||
tc_low = decrypted[2:6]
|
||||
tc_full = tc_high + tc_low
|
||||
|
||||
# MAM2: Build IResponse
|
||||
C_MAM2_PURPOSE = bytes([0xDA, 0x80])
|
||||
ich_31_0 = ichallenge[6:10] # Original, not reversed
|
||||
plaintext = C_MAM2_PURPOSE + ich_31_0 + tc_full
|
||||
iresponse = cipher.decrypt(plaintext)[::-1] # Decrypt then REVERSE
|
||||
|
||||
# MAM2: No KeyID in command!
|
||||
mam2_cmd = bytes([0x22, 0x35]) + uid_lsb + bytes([0x00, 0x06]) + iresponse
|
||||
mam2_resp = send_command(hcard, proto, mam2_cmd)
|
||||
|
||||
return mam2_resp is empty or success
|
||||
```
|
||||
|
||||
**Verified Working (2026-02-13):** All 4 key slots (KEY0-KEY3) authenticate successfully using this protocol.
|
||||
|
||||
**Standard MAM1/MAM2 (0x80/0x90) fail** with Error 0x0F despite the chip's GET NXP SYSTEM INFORMATION reporting MUTUAL AUTH = 1. Use AuthMethod 0x02/0x06 instead.
|
||||
|
||||
---
|
||||
|
||||
### ISO 29167-10 Standard MAM (AuthMethod 0x80/0x90) - NOT SUPPORTED
|
||||
|
||||
**NOTE:** The standard ISO 29167-10 AuthMethod values (0x80 for MAM1, 0x90 for MAM2) are **NOT supported** by NTAG5 Link hardware, despite documentation suggesting otherwise.
|
||||
|
||||
Use the undocumented AuthMethod values instead:
|
||||
- **AuthMethod 0x02** for MAM1
|
||||
- **AuthMethod 0x06** for MAM2 (no KeyID in command)
|
||||
|
||||
See "Complete MAM Authentication" section above for the working protocol.
|
||||
|
||||
## TAM1 Authentication (Tag Authentication Method 1)
|
||||
|
||||
TAM1 is a one-way authentication where the reader verifies the tag's identity. It uses the CHALLENGE (0x39) and READBUFFER (0x3A) commands instead of the full mutual authentication.
|
||||
|
||||
### TAM1 Protocol Flow
|
||||
|
||||
1. **Reader sends CHALLENGE** - Contains KeyID and random IChallenge
|
||||
2. **Tag computes AES encryption** - No response sent
|
||||
3. **Reader sends READBUFFER** - Requests the crypto result
|
||||
4. **Tag returns TResponse** - 16-byte encrypted block
|
||||
5. **Reader decrypts and verifies** - Checks C_TAM1 constant and IChallenge
|
||||
|
||||
### TAM1 Message Format
|
||||
|
||||
**CHALLENGE Command (0x39):**
|
||||
```
|
||||
Flags | 0x39 | CSI | AuthMethod/RFU | KeyID | IChallenge (10 bytes)
|
||||
```
|
||||
- Flags: 0x02 (high data rate, NOT addressed)
|
||||
- CSI: 0x00 (AES-128 Crypto Suite Identifier)
|
||||
- AuthMethod/RFU: 0x00 (TAM1 = 00b, CustomData = 0, RFU = 00000b)
|
||||
- KeyID: 0x00-0x03 (KEY0-KEY3)
|
||||
- IChallenge: 10 bytes random challenge
|
||||
|
||||
**READBUFFER Command (0x3A):**
|
||||
```
|
||||
Flags | 0x3A
|
||||
```
|
||||
- Flags: 0x02 (high data rate, NOT addressed)
|
||||
|
||||
**TResponse Format (16 bytes):**
|
||||
```
|
||||
AES-ECB-ENC(Key, C_TAM1 || TRnd || IChallenge)
|
||||
```
|
||||
- C_TAM1: 0x96C5 (2 bytes) - constant per ISO 29167-10
|
||||
- TRnd: 4 bytes random from tag
|
||||
- IChallenge: 10 bytes echoed from CHALLENGE command
|
||||
|
||||
### TAM1 Verification
|
||||
|
||||
To verify TAM1:
|
||||
1. **Reverse the TResponse byte order** (ACR1552 returns data in LSB-first order)
|
||||
2. Decrypt TResponse with the expected key using AES-128-ECB
|
||||
3. Check bytes 0-1 equal 0x96C5 (C_TAM1 constant)
|
||||
4. **Reverse the echoed IChallenge** (bytes 6-15) before comparing
|
||||
5. Check reversed bytes 6-15 equal the IChallenge you sent
|
||||
6. If both match, the tag is authentic
|
||||
|
||||
**CRITICAL: ACR1552 Byte Ordering**
|
||||
|
||||
The ACR1552 returns TResponse in reversed byte order due to ISO 15693's LSB-first transmission.
|
||||
Both the entire TResponse and the echoed IChallenge within the decrypted data must be reversed.
|
||||
|
||||
```python
|
||||
# Example verification (Python)
|
||||
tresponse_raw = resp[1:17] # Skip flags byte
|
||||
tresponse = tresponse_raw[::-1] # Reverse entire TResponse
|
||||
|
||||
cipher = AES.new(key, AES.MODE_ECB)
|
||||
decrypted = cipher.decrypt(tresponse)
|
||||
|
||||
c_tam1 = decrypted[0:2] # Should be 96C5
|
||||
trnd = decrypted[2:6] # 4 bytes random from tag
|
||||
echoed_raw = decrypted[6:16] # Echoed IChallenge (reversed)
|
||||
echoed = echoed_raw[::-1] # Reverse to get original order
|
||||
|
||||
if c_tam1 == bytes([0x96, 0xC5]) and echoed == ichallenge:
|
||||
# Authentication successful
|
||||
```
|
||||
|
||||
### ACR1552 Complete APDU Format for TAM1
|
||||
|
||||
The ACR1552 uses pseudo-APDUs with TLV wrapping for transparent mode communication.
|
||||
|
||||
**PCSC APDU Structure:**
|
||||
```
|
||||
FF C2 00 01 Lc [TLV Data] 00
|
||||
```
|
||||
- CLA: 0xFF (pseudo-APDU)
|
||||
- INS: 0xC2 (transparent exchange)
|
||||
- P1: 0x00
|
||||
- P2: 0x01 (exchange function)
|
||||
- Lc: Length of TLV data
|
||||
- Le: 0x00
|
||||
|
||||
**TLV Data Structure:**
|
||||
```
|
||||
5F46 04 [timeout_4bytes] Timeout in microseconds (big-endian)
|
||||
FF6E 03 03 01 0F FWTI (Frame Waiting Time Integer)
|
||||
95 xx [iso15693_command] ISO 15693 command payload
|
||||
```
|
||||
|
||||
**Response TLV Structure:**
|
||||
```
|
||||
C0 03 [status_3bytes] Error/status TLV (SW1 SW2 at bytes 1-2)
|
||||
92 01 xx Framing TLV
|
||||
96 02 xx xx Status TLV
|
||||
97 xx [iso15693_response] Response data TLV (includes flags byte)
|
||||
```
|
||||
- SW = 0x9000: Success
|
||||
- SW = 0x6401: Execution error (no response from ICC)
|
||||
|
||||
### Complete APDU Examples (Working TAM1 Flow)
|
||||
|
||||
**Example 1: GET SYSTEM INFO (required before CHALLENGE)**
|
||||
```
|
||||
TX: ffc20001115f4604000f4240ff6e0303010f9502022b00
|
||||
├─ FF C2 00 01 11 PCSC header (Lc=17)
|
||||
├─ 5F46 04 000F4240 Timeout = 1,000,000 µs (1 second)
|
||||
├─ FF6E 03 030100F FWTI
|
||||
├─ 95 02 022B ISO15693: Flags=02, Cmd=2B (GET SYSTEM INFO)
|
||||
└─ 00 Le
|
||||
|
||||
RX: c00300900092010096020000970f000f02e88c59580104e00000ff0301
|
||||
├─ C0 03 009000 Status OK (SW=9000)
|
||||
├─ 92 01 00 Framing OK
|
||||
├─ 96 02 0000 Status OK
|
||||
└─ 97 0F 00... Response: Flags=00 + System Info (14 bytes)
|
||||
```
|
||||
|
||||
**Example 2: CHALLENGE (KeyID=3, IChallenge=33ccc451ad4d9a6e9cd6)**
|
||||
```
|
||||
TX: ffc200011e5f4604000f4240ff6e0303010f950f023900000333ccc451ad4d9a6e9cd600
|
||||
├─ FF C2 00 01 1E PCSC header (Lc=30)
|
||||
├─ 5F46 04 000F4240 Timeout = 1 second
|
||||
├─ FF6E 03 03010F FWTI
|
||||
├─ 95 0F 02 39 00 00 03 33ccc451ad4d9a6e9cd6
|
||||
│ ├─ 02 Flags (high data rate)
|
||||
│ ├─ 39 CHALLENGE command
|
||||
│ ├─ 00 CSI (AES-128)
|
||||
│ ├─ 00 AuthMethod=TAM1
|
||||
│ ├─ 03 KeyID=3
|
||||
│ └─ 33ccc451ad4d9a6e9cd6 IChallenge (10 bytes)
|
||||
└─ 00 Le
|
||||
|
||||
RX: c003036401
|
||||
└─ C0 03 036401 Status: SW=6401 (no response from ICC)
|
||||
This is EXPECTED - CHALLENGE has no response
|
||||
```
|
||||
|
||||
**Example 3: READBUFFER**
|
||||
```
|
||||
TX: ffc20001115f4604000f4240ff6e0303010f9502023a00
|
||||
├─ FF C2 00 01 11 PCSC header (Lc=17)
|
||||
├─ 5F46 04 000F4240 Timeout = 1 second
|
||||
├─ FF6E 03 03010F FWTI
|
||||
├─ 95 02 02 3A ISO15693: Flags=02, Cmd=3A (READBUFFER)
|
||||
└─ 00 Le
|
||||
|
||||
RX: c00300900092010096020000971102238333363a3142b26328d1bf618362e4
|
||||
├─ C0 03 009000 Status OK
|
||||
├─ 92 01 00 Framing OK
|
||||
├─ 96 02 0000 Status OK
|
||||
└─ 97 11 02 238333363a3142b26328d1bf618362e4
|
||||
├─ 02 Flags (success, no error)
|
||||
└─ 238333...e4 TResponse (16 bytes)
|
||||
```
|
||||
|
||||
### Critical Timing Requirement
|
||||
|
||||
**IMPORTANT:** The chip requires time to compute the AES encryption after receiving CHALLENGE.
|
||||
|
||||
| Scenario | READBUFFER Result |
|
||||
| ----------------------------------------------------- | ------------------------- |
|
||||
| READBUFFER sent immediately after CHALLENGE | Often fails (no response) |
|
||||
| GET SYSTEM INFO sent between CHALLENGE and READBUFFER | Works (implicit delay) |
|
||||
| 1 second delay between CHALLENGE and READBUFFER | Works reliably |
|
||||
|
||||
**Recommended approach:** Either:
|
||||
1. Send any command (e.g., GET SYSTEM INFO) after CHALLENGE before READBUFFER, OR
|
||||
2. Add a minimum 50-100ms delay after CHALLENGE before sending READBUFFER
|
||||
|
||||
The crypto calculation typically takes ~4-8ms, but reader/driver overhead can cause timing issues if READBUFFER is sent too quickly.
|
||||
|
||||
### READBUFFER Failure Example (Insufficient Delay)
|
||||
|
||||
When CHALLENGE is sent immediately after connection with no prior commands and READBUFFER follows too quickly:
|
||||
|
||||
```
|
||||
TX: ffc200011e5f4604000f4240ff6e0303010f950f0239000003[IChallenge]00
|
||||
RX: c003036401 (SW=6401, expected - no response for CHALLENGE)
|
||||
|
||||
TX: ffc20001115f4604000f4240ff6e0303010f9502023a00
|
||||
RX: c003036401 (SW=6401, FAILURE - chip not ready)
|
||||
```
|
||||
|
||||
The second `c003036401` response indicates "no response from ICC" - the chip hasn't finished computing the crypto result yet. Adding a delay or sending an intermediate command resolves this.
|
||||
|
||||
### TAM1 vs State Machine
|
||||
|
||||
Per the datasheet, CHALLENGE can only be executed in READY state with non-addressed mode. However, testing shows:
|
||||
|
||||
- CHALLENGE works even after GET SYSTEM INFO (which puts chip in SELECTED state)
|
||||
- The critical factor is the **timing delay**, not the chip state
|
||||
- Sending GET SYSTEM INFO first provides the necessary delay for crypto completion
|
||||
|
||||
## Reader-Specific Notes
|
||||
|
||||
### ACS ACR1552
|
||||
|
||||
- Uses transparent mode with TLV-wrapped commands
|
||||
- Supports ISO15693 natively
|
||||
- Switch protocol command: `8F 02 02 03` (ISO15693 Layer 3)
|
||||
- Timeout TLV: `5F46` with 4-byte timeout in microseconds
|
||||
- Commands work correctly for basic operations
|
||||
- CHALLENGE returns SW=6401 (no response from ICC) - this is expected
|
||||
- READBUFFER returns SW=9000 with 17-byte response (flags + 16-byte TResponse)
|
||||
- Authentication returns error 0x0F when key is not properly configured
|
||||
|
||||
**CRITICAL: Byte Order Reversal Required**
|
||||
- TResponse data is returned in LSB-first (reversed) byte order
|
||||
- Must reverse entire TResponse before AES decryption
|
||||
- Echoed IChallenge in decrypted data is also reversed
|
||||
- See TAM1 Verification section for correct processing order
|
||||
- AUTHENTICATE (0x35) command returns "no ICC response" - use CHALLENGE/READBUFFER instead
|
||||
|
||||
### HID Omnikey 5022 CL
|
||||
|
||||
- Uses transceive APDU: `FF 68 0E 03`
|
||||
- 10-byte header: TxRxFlags | ValidBits | Timeout(4) | RFU(4)
|
||||
- TxRxFlags: 0x05 for ISO15693 (CRC TX + CRC RX enabled)
|
||||
- Response format: STS(1) | RxB(1) | Card_Response(n) | SW(2)
|
||||
- Direct transceive works WITHOUT entering transparent session first
|
||||
- ISO15693 transparent session may not work reliably - use direct transceive
|
||||
|
||||
**AUTHENTICATE (0x35) for TAM1 - CONFIRMED WORKING EXAMPLE:**
|
||||
|
||||
TAM1 command format (AuthMethod = 0x00):
|
||||
```
|
||||
Flags(22) | Cmd(35) | UID(8) | CSI(00) | AuthMethod(00) | KeyID | IChallenge(10)
|
||||
```
|
||||
|
||||
**Verified Example (2026-02-13):**
|
||||
|
||||
Input parameters:
|
||||
- Chip UID: E0040158F08BE802 (MSB) / 02E88BF0580104E0 (LSB)
|
||||
- KEY0: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
- IChallenge: 112233445566778899AA
|
||||
|
||||
Command sent to reader:
|
||||
```
|
||||
FF680E03210500004C4B4000000000223502E88BF0580104E0000000112233445566778899AA00
|
||||
```
|
||||
|
||||
| Bytes | Value | Meaning |
|
||||
| -------------------- | ---------- | -------------------- |
|
||||
| FF680E03 | Header | Omnikey transceive |
|
||||
| 21 | Lc=33 | Data length |
|
||||
| 05 | TxRxFlags | ISO15693 CRC |
|
||||
| 00 | ValidBits | Whole bytes |
|
||||
| 004C4B40 | Timeout | 5 seconds |
|
||||
| 00000000 | RFU | Reserved |
|
||||
| 22 | Flags | Addressed, high rate |
|
||||
| 35 | Cmd | AUTHENTICATE |
|
||||
| 02E88BF0580104E0 | UID | LSB order |
|
||||
| 00 | CSI | AES-128 |
|
||||
| 00 | AuthMethod | TAM1 |
|
||||
| 00 | KeyID | KEY0 |
|
||||
| 112233445566778899AA | IChallenge | 10 bytes |
|
||||
| 00 | Le | Response length |
|
||||
|
||||
Response received:
|
||||
```
|
||||
000006A7F2E114AFC63A0F6A7170A4F1692DD29F9000
|
||||
```
|
||||
|
||||
| Bytes | Value | Meaning |
|
||||
| -------------------------------- | --------- | ------------------ |
|
||||
| 0000 | STS+RxB | Transceive OK |
|
||||
| 06 | Flags | Success |
|
||||
| A7 | Header | Barker code (skip) |
|
||||
| F2E114AFC63A0F6A7170A4F1692DD29F | TResponse | 16 bytes encrypted |
|
||||
| 9000 | SW | Success |
|
||||
|
||||
**Decryption process:**
|
||||
|
||||
1. Extract TResponse: `F2E114AFC63A0F6A7170A4F1692DD29F`
|
||||
2. REVERSE bytes: `9FD22D69F1A470716A0F3AC6AF14E1F2`
|
||||
3. AES-ECB decrypt with KEY0: `96C51B6F50BBAA998877665544332211`
|
||||
4. Parse result:
|
||||
- C_TAM1: `96C5` (correct constant)
|
||||
- TRnd: `1B6F50BB` (tag random)
|
||||
- Echoed: `AA998877665544332211` (IChallenge reversed)
|
||||
|
||||
**VERIFICATION: SUCCESS** - C_TAM1=96C5, IChallenge matches (reversed)
|
||||
|
||||
```python
|
||||
# Omnikey 5022 TAM1 verification (AuthMethod=0x00)
|
||||
card_resp = response[2:-2] # Strip STS, RxB, and SW
|
||||
data_after_flags = card_resp[1:] # Skip flags byte (0x06)
|
||||
tresponse = data_after_flags[1:17] # Skip header byte (0xA7), take 16 bytes
|
||||
tresponse = tresponse[::-1] # Reverse byte order
|
||||
|
||||
cipher = AES.new(key, AES.MODE_ECB)
|
||||
decrypted = cipher.decrypt(tresponse)
|
||||
|
||||
c_tam1 = decrypted[0:2] # Should be 96C5
|
||||
trnd = decrypted[2:6] # 4-byte tag random
|
||||
echoed = decrypted[6:16] # REVERSED - compare against ichallenge[::-1]
|
||||
|
||||
if c_tam1 == bytes([0x96, 0xC5]) and echoed == ichallenge[::-1]:
|
||||
# TAM1 authentication successful
|
||||
```
|
||||
|
||||
## Error Codes
|
||||
|
||||
### ISO15693 Error Codes (from flag byte with error bit set)
|
||||
|
||||
| Code | Description |
|
||||
| ---- | ---------------------- |
|
||||
| 0x01 | Command not supported |
|
||||
| 0x02 | Command not recognized |
|
||||
| 0x03 | Option not supported |
|
||||
| 0x0F | Unknown error |
|
||||
| 0x10 | Block not available |
|
||||
| 0x11 | Block already locked |
|
||||
| 0x12 | Block locked |
|
||||
| 0x13 | Programming failed |
|
||||
| 0x14 | Locking failed |
|
||||
|
||||
**Error 0x0F during authentication typically means:**
|
||||
- Key header not activated (still 0x81 instead of 0xE7)
|
||||
- Wrong key slot used
|
||||
- Chip not in AES mode
|
||||
- Malformed MAM1 message
|
||||
|
||||
## READ CONFIG Command Requirements
|
||||
|
||||
**IMPORTANT:** READ CONFIG (0xC0) must use **unaddressed mode** (flags=0x02).
|
||||
|
||||
**Correct Format:**
|
||||
```
|
||||
Flags | Cmd | MfgCode | BlockAddr | NumBlocks
|
||||
0x02 | 0xC0 | 0x04 | addr | 0x00
|
||||
```
|
||||
|
||||
Example: `bytes([0x02, 0xC0, 0x04, 0x3F, 0x00])` to read DEV_SEC_CONFIG
|
||||
|
||||
**Wrong Format (does not work):**
|
||||
```
|
||||
Flags | Cmd | MfgCode | UID (8 bytes) | BlockAddr
|
||||
0x22 | 0xC0 | 0x04 | uid_lsb | addr
|
||||
```
|
||||
|
||||
Using addressed mode (flags=0x22 with UID) returns no response from the chip.
|
||||
|
||||
## Common Issues
|
||||
|
||||
1. **"Unknown error" (0x0F) on AUTHENTICATE**
|
||||
- Check DEV_SEC_CONFIG is in AES mode (bits 3-0 = 0010b)
|
||||
- Check Key Header is 0xE7 (active and locked)
|
||||
- Power cycle chip after mode changes
|
||||
|
||||
2. **Key Header reads as 0x00**
|
||||
- Incorrect address used - Key headers are at 0x10, 0x12, 0x14, 0x16
|
||||
- Block 0x0E is NFC_AUTH_LIMIT, not KEY0 header
|
||||
|
||||
3. **Cannot write key after activation**
|
||||
- Once key header is 0xE7, key is permanently locked
|
||||
- Must set key value BEFORE activating header
|
||||
|
||||
4. **Mode change doesn't take effect**
|
||||
- Configuration changes require power cycle (POR)
|
||||
- Remove card from reader field and place again
|
||||
|
||||
5. **Keys must be written in AES mode**
|
||||
- Enable AES mode (DEV_SEC_CONFIG = 0xA2) before writing key values
|
||||
- Power cycle after enabling AES mode, then write keys
|
||||
- Keys written while in password mode may not work correctly for authentication
|
||||
|
||||
6. **TAM1/MAM1 authentication fails despite correct key**
|
||||
- Key values verified by read-back but authentication still fails
|
||||
- This is a known issue under investigation with NXP (open support ticket)
|
||||
- Ensure chip is in AES mode before writing keys
|
||||
|
||||
## Troubleshooting Checklist
|
||||
|
||||
Before attempting authentication:
|
||||
|
||||
1. **Verify AES Mode:**
|
||||
```
|
||||
Read config block 0x3F
|
||||
DEV_SEC_CONFIG byte 0, bits 3-0 should be 0010b
|
||||
Example: 0xA2 = AES mode enabled, writable
|
||||
```
|
||||
|
||||
2. **Verify Key Header is Active:**
|
||||
```
|
||||
Read config block 0x10 (KEY0), 0x12 (KEY1), 0x14 (KEY2), or 0x16 (KEY3)
|
||||
Byte 1 should be 0xE7 for the key you want to use
|
||||
0x81 = not active (cannot authenticate)
|
||||
```
|
||||
|
||||
3. **Verify Key Value is Known:**
|
||||
```
|
||||
If key header was 0x81 before you configured it:
|
||||
- Was AES mode enabled BEFORE writing key values?
|
||||
- Did read-back verification match what you wrote?
|
||||
- After activating header to 0xE7, key blocks should return errors (protected)
|
||||
```
|
||||
|
||||
4. **Check Auth Failure Counter:**
|
||||
```
|
||||
Read config block 0x0E
|
||||
NFC_AUTH_LIMIT (bits 9-0) sets max failures before lockout
|
||||
If counter reaches limit, key slot is permanently blocked
|
||||
```
|
||||
|
||||
5. **Test with Correct Command Format:**
|
||||
```
|
||||
AUTHENTICATE: 0x22 0x35 <UID_LSB_8bytes> 0x00 0x80 <key_slot> <challenge_10bytes>
|
||||
- FLAGS = 0x22 (high data rate, addressed mode) - REQUIRED
|
||||
- CSI = 0x00 (AES-128)
|
||||
- MAM1_Header = 0x80
|
||||
```
|
||||
Reference in New Issue
Block a user