# 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 0x00 0x80 - FLAGS = 0x22 (high data rate, addressed mode) - REQUIRED - CSI = 0x00 (AES-128) - MAM1_Header = 0x80 ```