How to correctly enable password protection on NTAG213 using Android NFC (PWD_AUTH & ACCESS bytes)

Postmortem: Misconfiguration of NTAG213 Password Protection in Android NFC

Summary

A developer attempted to enable password protection on NTAG213 NFC tags using Android’s NfcA technology. After configuring the PWD, PACK, and ACCESS bytes, tags either remained unlocked or became inaccessible. The issue stemmed from incomplete configuration sequences and misunderstanding of NTAG213 authentication requirements.

Root Cause

  • Invalid ACCESS Byte Configuration: The ACCESS byte settings failed to properly activate write protection via password authentication
  • Missing Write-After-Configuration: Failure to write the ACCESS byte after PWD/PACK configuration locked the tag prematurely
  • Authentication Timing: Lack of PWD_AUTH execution before protected write operations
  • Protocol Selection: Using NfcA instead of NfcV after enabling password protection

Why This Happens in Real Systems

  • Complex NTAG213 documentation with vendor-specific implementation details
  • Authentication timing requirements not clearly evident in SDK documentation
  • Testing limitations due to Android NFC stack abstraction layer
  • Over-reliance on generic NFC examples not specific to NTAG213 access control

Real-World Impact

  • Security Compromise: Unprotected tags allow unauthorized data modification
  • Tag Bricking: Mismanaged ACCESS bytes permanently disable tags
  • Loss of Operational Continuity: Critical NFC workflows fail when tags are inaccessible
  • Increased Support Costs: Physical replacement required for bricked tags

Example or Code

java
// Configuration sequence for NTAG213 password protection
// Pages: PWD = 0xE5 (page 0x2B), PACK = 0x80 (page 0x2C), ACCESS = 0x05 (page 0x2D)

byte[] pwdPage = new byte[] {(byte)0xAA, (byte)0xBB, (byte)0xCC, (byte)0xDD}; // PWD bytes
byte[] packPage = new byte[] {(byte)0x00, (byte)0x00}; // PACK (use lower bytes)
byte[] accessPage = new byte[] {(byte)0x05, (byte)0x00, (byte)0x00, (byte)0x00}; // ACCESS byte = 05h

// Write sequence
NfcA nfc = NfcA.get(tag); // Use NfcA for configuration
nfc.connect();

// Write PWD (page 43/0x2B) and PACK (page 44/0x2C)
nfc.transceive(new byte[] {(byte)0xA2, (byte)0x2B, pwdPage[0], pwdPage[1], pwdPage[2], pwdPage[3]});
nfc.transceive(new byte[] {(byte)0xA2, (byte)0x2C, packPage[0], packPage[1], (byte)0x00, (byte)0x00});

// Write ACCESS (page 45/0x2D) LAST
nfc.transceive(new byte[] {(byte)0xA2, (byte)0x2D, accessPage[0], accessPage[1], accessPage[2], accessPage[3]});

nfc.close();

// Later when accessing protected tag:
NfcV nfcv = NfcV.get(tag); // Switch to NfcV for auth operations
nfcv.connect();
byte[] authCmd = new byte[] {
(byte)0x1B, // PWD_AUTH command
pwdPage[0], pwdPage[1], pwdPage[2], pwdPage[3]
};
byte[] packResponse = nfcv.transceive(authCmd); // Verify PACK response

// Now write protected data pages…

How Senior Engineers Fix It

  1. Strict Configuration Sequence: Always write ACCESS byte last after PWD/PACK configuration
  2. Protocol Switching: Transition from NfcA for configuration to NfcV for authenticated operations
  3. Pre-Write Authentication: Execute PWD_AUTH before every protected write command
  4. Validation Flow: Use PACK responses to confirm successful authentication
  5. Revocation Handling: Implement reset protocols for compromised tags

Why Juniors Miss It

  • Documentation Ambiguity: Vendor specs spread requirements across multiple documents
  • Android-Specific Gaps: NFC stack abstracts low-level protocol differences
  • Testing Friction: Physical tag requirements hamper rapid iteration
  • Security Complexity: Confusion between authentication for read vs. write operations
  • Legacy Code Patterns: Copying unprotected tag implementation boilerplate
  • Protocol Nuance: Unaware that NTAG213 switches from ISO14443A to virtual NFC-V after password protection
  1. Verify ACCESS Byte Logic: Confirm AUTH0 bit enables PWD requirement (ACCESS = 0x05 enables write protection)