39  NFC Access Control Systems

Key Concepts
  • NFC Access Control: Using NFC credentials (smart card, smartphone) to authenticate and authorise physical access to secured areas
  • Credential: The digital identity stored on an NFC card or device that uniquely identifies the holder to an access control system
  • Reader: The fixed NFC device at a door or gate that reads credentials and communicates with the access control system
  • Access Control System (ACS): The back-end system that stores user permissions and makes pass/fail decisions based on credential data
  • Card Cloning: Copying an NFC card’s data to a blank card; a significant security threat to access control systems using non-authenticated tags
  • MIFARE Classic Vulnerability: The MIFARE Classic tag uses a proprietary Crypto-1 cipher that has been broken; cards using it can be cloned inexpensively
  • Anti-Passback: An access control policy preventing a credential from being used to enter a door twice without first exiting; prevents credential sharing

39.1 In 60 Seconds

Build a complete ESP32-based NFC door lock system using PN532 over I2C. The system reads 7-byte tag UIDs, checks them against an authorized database, controls a servo motor for lock/unlock, provides LED and buzzer feedback, and maintains a circular buffer access log for security auditing. Includes complete wiring diagrams, Arduino code, and troubleshooting guides.

Sammy the Sensor was thrilled: “We are building a real security system!” Max the Microcontroller outlined the plan: “We connect an NFC reader to an ESP32 and add a motor to control the lock. When someone taps their badge, the reader checks if they are on the approved list. If yes, the motor turns and the green light goes on. If not, the red light flashes and the buzzer sounds an alarm!” Bella the Battery noted, “Every tap gets recorded in a log, so the building manager can see who came in and when. It is like a guest book that fills itself automatically.” Lila the LED added, “And if you lose your badge, the manager can just remove it from the approved list. No need to change the locks like you would with a physical key!”

39.2 Learning Objectives

By the end of this chapter, you will be able to:

  • Construct NFC Access Control: Assemble and program a complete door lock system using ESP32 and PN532 modules with I2C communication
  • Configure I2C Communication: Set up and verify reliable NFC tag reading by mapping GPIO pins, validating pull-up resistors, and testing signal integrity
  • Evaluate Authorization Logic: Compare UID-only matching against challenge-response authentication and justify when each approach is appropriate
  • Calculate System Latency: Determine end-to-end access response time by analyzing detection, authorization, and actuation timing for ADA compliance
  • Demonstrate Access Logging: Implement and verify circular buffer storage that records UID, status, and timestamp for security auditing
  • Diagnose NFC Issues: Systematically troubleshoot communication failures, range limitations, and electromagnetic interference using structured debugging procedures

This chapter guides you through building a complete NFC-based door lock system. When someone taps an authorized NFC tag (like an employee badge), the system:

  1. Reads the tag’s unique ID
  2. Checks if the ID is in the authorized list
  3. Unlocks the door (servo motor)
  4. Provides visual feedback (green LED = granted, red LED = denied)
  5. Logs the access attempt for security auditing

Think of it like the key card systems in hotels or offices, but one you build yourself!

Prerequisites:

Continue Learning:

39.3 Prerequisites

Required Knowledge:

  • Understanding of NFC operating modes (Reader/Writer, Peer-to-Peer, Card Emulation)
  • Basic Arduino/ESP32 programming experience
  • Familiarity with I2C communication protocol

Required Hardware:

  • ESP32 Development Board
  • PN532 NFC Reader Module (I2C)
  • Servo motor (SG90) for lock mechanism
  • NFC tags (Type 2 recommended)
  • LED (Green for granted, Red for denied)
  • Buzzer (optional)
  • Jumper wires and breadboard

Estimated Time: 45 minutes

39.4 System Architecture

The NFC access control system follows a straightforward flow: tag detection, authorization check, and hardware response.

NFC access control system architecture showing the flow from NFC tag detection through UID reading, authorization check against stored credentials database, to hardware response including servo lock actuation, LED status indicators, and buzzer feedback

NFC access control system architecture
Figure 39.1: NFC access control system architecture showing tag reading, authorization checking, and hardware feedback mechanisms including servo lock, LEDs, and buzzer.

This decision tree helps select the appropriate NFC operating mode based on application requirements.

Decision tree for selecting NFC operating mode based on application requirements including read/write for tags, peer-to-peer for data exchange, and card emulation for payments

39.5 Hardware Wiring

Connect the ESP32 to the PN532 NFC module and output devices as shown:

Wiring diagram showing ESP32 microcontroller pin connections to PN532 NFC reader module via I2C (SDA to GPIO 21, SCL to GPIO 22), servo motor signal to GPIO 13, green LED to GPIO 25, red LED to GPIO 26, and buzzer to GPIO 27, with power connections for 3.3V and 5V rails
Figure 39.2: ESP32 NFC access control system wiring showing I2C communication with PN532 reader, servo motor control, and LED/buzzer feedback indicators.

Wiring Summary Table:

Component Pin ESP32 GPIO Notes
PN532 VCC VCC 3.3V Power supply
PN532 GND GND GND Common ground
PN532 SDA SDA GPIO 21 I2C data
PN532 SCL SCL GPIO 22 I2C clock
Servo Signal Orange GPIO 13 PWM control
Servo VCC Red 5V Higher voltage for motor
Servo GND Brown GND Common ground
Green LED Anode GPIO 25 Via 220Ω resistor
Red LED Anode GPIO 26 Via 220Ω resistor
Buzzer + GPIO 27 Active buzzer
Try It: GPIO Pin Assignment Explorer

39.6 Complete Implementation

The following code implements a fully functional NFC access control system:

#include <Wire.h>
#include <PN532_I2C.h>
#include <PN532.h>
#include <NfcAdapter.h>
#include <ESP32Servo.h>

// Pin definitions
#define SERVO_PIN 13
#define GREEN_LED 25
#define RED_LED 26
#define BUZZER 27

// NFC setup
PN532_I2C pn532i2c(Wire);
NfcAdapter nfc = NfcAdapter(pn532i2c);

// Servo for lock
Servo lockServo;

// Authorized UIDs (add your NFC tag UIDs here)
String authorizedUIDs[] = {
  "04 A3 B2 C1 D4 5E 80",  // Alice's badge
  "08 F7 E2 9A 3B 1C 4D",  // Bob's badge
  "12 34 56 78 9A BC DE"   // Admin card
};
const int numAuthorizedUIDs = 3;

// Lock states
const int LOCKED_ANGLE = 0;
const int UNLOCKED_ANGLE = 90;
bool isLocked = true;

// Access log (circular buffer)
struct AccessLog {
  String uid;
  String status;
  unsigned long timestamp;
};
AccessLog accessHistory[10];
int accessLogIndex = 0;

void setup() {
  Serial.begin(115200);
  Serial.println("\n=== ESP32 NFC Access Control System ===");

  // Initialize pins
  pinMode(GREEN_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);
  pinMode(BUZZER, OUTPUT);

  // Initial state: deny
  digitalWrite(GREEN_LED, LOW);
  digitalWrite(RED_LED, HIGH);

  // Initialize servo
  lockServo.attach(SERVO_PIN);
  lockServo.write(LOCKED_ANGLE);

  // Initialize NFC
  nfc.begin();
  Serial.println("✓ NFC Reader initialized");
  Serial.println("📱 Ready to scan NFC tags...\n");

  // Startup beep
  beep(100);
}

void loop() {
  // Check for NFC tag
  if (nfc.tagPresent(1000)) {  // 1 second timeout
    NfcTag tag = nfc.read();

    String uid = tag.getUidString();
    Serial.println("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    Serial.println("📱 NFC Tag Detected");
    Serial.print("   UID: ");
    Serial.println(uid);

    // Check authorization
    if (isAuthorized(uid)) {
      grantAccess(uid);
    } else {
      denyAccess(uid);
    }

    Serial.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");

    delay(2000);  // Prevent multiple reads
  }

  delay(100);
}

bool isAuthorized(String uid) {
  /**
   * Check if UID is in authorized list
   */
  for (int i = 0; i < numAuthorizedUIDs; i++) {
    if (uid == authorizedUIDs[i]) {
      return true;
    }
  }
  return false;
}

void grantAccess(String uid) {
  /**
   * Grant access: unlock door, green LED, success beep
   */
  Serial.println("✅ ACCESS GRANTED");

  // Visual feedback
  digitalWrite(RED_LED, LOW);
  digitalWrite(GREEN_LED, HIGH);

  // Unlock door
  lockServo.write(UNLOCKED_ANGLE);
  isLocked = false;

  // Audio feedback
  beep(50);
  delay(100);
  beep(50);

  Serial.println("🔓 Door unlocked");
  Serial.println("   Relocking in 5 seconds...");

  // Log access
  logAccess(uid, "GRANTED");

  // Keep door unlocked for 5 seconds
  delay(5000);

  // Re-lock door
  lockServo.write(LOCKED_ANGLE);
  isLocked = true;
  digitalWrite(GREEN_LED, LOW);
  digitalWrite(RED_LED, HIGH);

  Serial.println("🔒 Door locked");
}

void denyAccess(String uid) {
  /**
   * Deny access: red LED, error beep
   */
  Serial.println("❌ ACCESS DENIED");
  Serial.println("   Unauthorized NFC tag");

  // Visual feedback (red LED already on)
  // Blink red LED
  for (int i = 0; i < 3; i++) {
    digitalWrite(RED_LED, LOW);
    delay(100);
    digitalWrite(RED_LED, HIGH);
    delay(100);
  }

  // Audio feedback (long error beep)
  beep(500);

  // Log denied attempt
  logAccess(uid, "DENIED");
}

void beep(int duration_ms) {
  /**
   * Sound buzzer for specified duration
   */
  digitalWrite(BUZZER, HIGH);
  delay(duration_ms);
  digitalWrite(BUZZER, LOW);
}

void logAccess(String uid, String status) {
  /**
   * Record access attempt in circular buffer
   */
  accessHistory[accessLogIndex].uid = uid;
  accessHistory[accessLogIndex].status = status;
  accessHistory[accessLogIndex].timestamp = millis();

  accessLogIndex = (accessLogIndex + 1) % 10;  // Circular buffer

  // Print log entry
  Serial.print("📝 Logged: ");
  Serial.print(status);
  Serial.print(" - ");
  Serial.println(uid);
}

// Optional: Add command to print access history via Serial
void printAccessHistory() {
  Serial.println("\n=== Access History (Last 10 attempts) ===");
  for (int i = 0; i < 10; i++) {
    if (accessHistory[i].uid != "") {
      Serial.print(i + 1);
      Serial.print(". ");
      Serial.print(accessHistory[i].status);
      Serial.print(" - ");
      Serial.print(accessHistory[i].uid);
      Serial.print(" @ ");
      Serial.print(accessHistory[i].timestamp / 1000);
      Serial.println("s");
    }
  }
  Serial.println("=====================================\n");
}

39.7 Expected Serial Output

When the system is running, you’ll see output like this:

=== ESP32 NFC Access Control System ===
✓ NFC Reader initialized
📱 Ready to scan NFC tags...

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📱 NFC Tag Detected
   UID: 04 A3 B2 C1 D4 5E 80
✅ ACCESS GRANTED
🔓 Door unlocked
   Relocking in 5 seconds...
📝 Logged: GRANTED - 04 A3 B2 C1 D4 5E 80
🔒 Door locked
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📱 NFC Tag Detected
   UID: FF AA BB CC DD EE 11
❌ ACCESS DENIED
   Unauthorized NFC tag
📝 Logged: DENIED - FF AA BB CC DD EE 11
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Try It: NFC Access Control Simulator

39.8 Adding New Authorized Tags

To add new authorized tags to the system:

  1. Scan the new tag to get its UID from Serial Monitor
  2. Add the UID to the authorizedUIDs array:
String authorizedUIDs[] = {
  "04 A3 B2 C1 D4 5E 80",
  "08 F7 E2 9A 3B 1C 4D",
  "YOUR NEW UID HERE"     // Add new tag
};
  1. Update the count: Change numAuthorizedUIDs to match
  2. Re-upload the code to ESP32
Pro Tip: Dynamic Tag Management

For production systems, consider storing authorized UIDs in EEPROM or SPIFFS (ESP32’s flash filesystem) so you can add/remove tags without re-uploading code. You could also implement a web interface for remote management.

39.9 Knowledge Check

Question 1: In the ESP32 NFC access control system, why does the servo motor use a separate 5V power supply instead of the ESP32’s 3.3V pin?

Question 2: The system uses UID matching for authorization. What is the main security limitation of this approach in a production environment?

39.10 Troubleshooting

Common issues and solutions:

Problem Cause Solution
NFC not initializing I2C wiring issue Check SDA/SCL connections, verify 3.3V power
Tag not detected Range too far Hold tag within 2-3cm of reader
Wrong UID format Tag type mismatch Use Type 2 tags (NTAG213/215/216)
Servo jittering Power issue Use separate 5V supply for servo
Intermittent reads Interference Keep away from metal surfaces

39.11 Security Considerations

This basic implementation demonstrates NFC access control concepts. For production use, consider:

  • Encrypted UIDs: Use tag types with encryption (NTAG 424 DNA)
  • Challenge-Response: Implement mutual authentication between reader and tag
  • Audit Logging: Store logs to external database with timestamps
  • Anti-Passback: Prevent same tag from entering twice without exit
  • Tamper Detection: Add sensors to detect physical attacks on reader

39.12 Real-World NFC Access Control Deployments

39.12.1 Hilton Hotels: 6,200 Properties, Digital Key

Hilton’s Digital Key rollout deployed NFC-based room access across 6,200 properties worldwide, allowing guests to unlock rooms by tapping their smartphone against the door reader. The system uses HCE (Host Card Emulation) mode rather than the UID-matching approach shown in this lab – a critical distinction for production security.

Why UID matching fails at scale:

In this lab, the system matches the tag’s static UID against an authorized list. Hilton’s security team evaluated this approach during their 2014 pilot at 10 properties and rejected it for three reasons:

Vulnerability Impact Hilton’s mitigation
UID cloning MIFARE Classic UIDs cloneable with $30 Proxmark device Moved to MIFARE DESFire EV2 with AES-128 challenge-response
Static credentials Stolen UID works forever until manually revoked Time-limited session keys with 24-hour expiration tied to reservation dates
No mutual authentication Rogue reader could harvest UIDs from guests’ phones DESFire mutual authentication – both reader and card prove identity

Deployment numbers:

  • 6,200 properties, approximately 950,000 door locks upgraded
  • 140 million+ room entries via NFC/BLE Digital Key as of 2023
  • Lock battery life: 24 months on 4 AA lithium batteries (approximately 80 NFC authentications per day per lock)
  • Authentication latency: 340 ms average (vs. 50 ms for the lab’s simple UID check), dominated by the 3-pass DESFire mutual authentication handshake
  • False rejection rate: 0.3% (mostly caused by phone NFC antenna misalignment with lock reader)

Battery capacity determines lock lifespan: \(N_{\text{auths}} = \frac{C_{\text{battery}} \times V}{E_{\text{auth}}}\) authentications. Worked example: 4× AA lithium = 3000 mAh at 1.5V each = 18 Wh total. DESFire auth consumes ~15 mA for 340 ms = 15 mA × 0.34 s = 5.1 mAs = 1.42 mWh at 3V. \(N_{\text{auths}} = \frac{18000}{1.42} = 12,676\) authentications. At 80 auths/day: \(\frac{12,676}{80} = 158\) days, but real locks achieve 24 months due to deep sleep (0.01 mA standby) between auths.

Cost comparison:

Approach Per-door hardware cost Per-door annual operating cost Security level
Physical key $15 lock + $2/key $180 (rekeying, lost keys) Low
Magnetic stripe card $250 lock + $0.50/card $85 (card replacement, encoder) Medium
UID-only NFC (this lab) $45 reader + $0.30/tag $12 (tag replacement) Low – cloneable
DESFire challenge-response $180 lock + $1.50/card $35 (card + key management) High
HCE smartphone (Hilton) $180 lock + $0/phone $8 (server infrastructure) High

Hilton’s ROI: eliminated $142M/year in physical key card costs across the portfolio, with a 3.1-year payback on the $520M lock upgrade investment.

Try It: NFC Deployment Cost Calculator

39.12.2 Lesson for Lab Builders: Upgrading from UID to Challenge-Response

The ESP32 lab in this chapter uses UID matching, which is appropriate for learning and prototyping. For any production deployment beyond a hobby project, replace UID matching with MIFARE DESFire EV2 or NTAG 424 DNA tags. The PN532 reader used in the lab supports DESFire commands via the Adafruit_PN532 library’s InDataExchange function, allowing you to send ISO 7816-4 APDUs for 3DES or AES authentication without changing hardware.

An office building deploys the ESP32 NFC access control system for 20 doors. The security manager needs to document the complete access latency from badge tap to door unlock for compliance with ADA accessibility requirements (maximum 5 seconds for powered doors).

System components and timing:

1. NFC tag detection (PN532 reader):

  • Polling interval: The code calls nfc.tagPresent(1000) with 1-second timeout
  • Tag detection when present: 50-150 ms (typical NFC Type 2 tag)
  • Average: 100 ms

2. UID reading (NFC communication):

  • ISO 14443A REQA/ATQA exchange: 1-2 ms
  • SELECT cascade (UID retrieval): 2-4 ms per cascade level
  • 7-byte UID (2 cascade levels): ~6 ms total
  • Total: 8 ms

3. Authorization check (microcontroller):

bool isAuthorized(String uid) {
  for (int i = 0; i < numAuthorizedUIDs; i++) {
    if (uid == authorizedUIDs[i]) {
      return true;
    }
  }
  return false;
}
  • String comparison: ~20 μs per UID (ESP32 @ 240 MHz)
  • With 100 authorized UIDs: 100 × 20 μs = 2 ms
  • Array iteration overhead: <1 ms
  • Total: 3 ms

4. Servo motor actuation:

  • Code: lockServo.write(UNLOCKED_ANGLE);
  • ESP32 PWM signal generation: <1 ms
  • SG90 servo physical movement (0° to 90°):
    • Speed: 0.1 sec/60° (datasheet specification)
    • For 90° rotation: 90/60 × 0.1 = 0.15 seconds = 150 ms

5. Feedback (LED/Buzzer):

  • These occur in parallel with servo, so don’t add to critical path
  • digitalWrite(GREEN_LED, HIGH): <0.1 ms
  • beep(50): Runs concurrently during door opening

6. Serial logging:

  • Serial.println() calls: 5-10 ms each (9600 baud = slow)
  • But occurs AFTER decision made, doesn’t block door opening
  • Not in critical path

Total critical path latency:

Step Latency Notes
Tag detection 100 ms Polling loop waiting for tag presence
UID reading 8 ms NFC communication
Authorization check 3 ms Array linear search
Servo actuation 150 ms Physical motor movement
Total 261 ms 0.261 seconds

Compliance verification:

ADA requirement: <5 seconds for powered door operation Measured system: 0.261 seconds Margin: 5 / 0.261 = 19× faster than requirement

Worst-case scenario analysis:

Scenario: Denied access with buzzer warning

Step Latency
Tag detection 100 ms
UID reading 8 ms
Authorization check (not found) 5 ms (checks all 100 UIDs)
Red LED blink loop (3 blinks) 3 × (100 on + 100 off) = 600 ms
Error beep 500 ms
Total 1,213 ms (1.2 seconds)

Still well within 5-second requirement.

Optimization opportunities:

1. Faster authorization lookup (hash map):

// Replace linear array search with hash map
std::unordered_set<String> authorizedUIDs = {
  "04 A3 B2 C1 D4 5E 80",
  "08 F7 E2 9A 3B 1C 4D",
  // ... rest
};

bool isAuthorized(String uid) {
  return authorizedUIDs.count(uid) > 0;  // O(1) lookup
}
  • Hash lookup: 0.1 ms (constant time regardless of database size)
  • Improvement: 3 ms → 0.1 ms (30× faster for 100 UIDs)
  • Matters more at scale (1,000+ users: 50 ms → 0.1 ms = 500× faster)

2. Faster servo (optional):

  • SG90 servo: 0.1 sec/60° = 150 ms for 90°
  • Digital servo (e.g., MG90S): 0.08 sec/60° = 120 ms
  • Improvement: 150 ms → 120 ms (20% faster)

3. Reduce polling timeout:

// Change from 1000 ms to 100 ms
if (nfc.tagPresent(100)) {  // Was: nfc.tagPresent(1000)
  • Tag detection: 100 ms → 20-50 ms average
  • Improvement: 100 ms → 35 ms (3× faster response)
  • Trade-off: Higher CPU usage (polls 10× more frequently)

Optimized total latency:

Component Original Optimized
Tag detection 100 ms 35 ms
UID reading 8 ms 8 ms
Authorization 3 ms 0.1 ms
Servo 150 ms 120 ms
Total 261 ms 163 ms

Improvement: 37% faster (261 ms → 163 ms)

Conclusion: The basic implementation achieves 261 ms access latency, already 19× faster than ADA requirements. Optimizations can reduce this to 163 ms but provide diminishing returns for this application—human perception doesn’t distinguish between 0.26 sec and 0.16 sec. Optimization effort better spent on security hardening (challenge-response authentication) than speed.

Try It: Authentication Latency Calculator
Tag Type Security Level Cost Read Range Recommended Use Avoid When
NTAG213/215/216 ⭐ Low (UID cloneable) $0.20-0.50 2-4 cm Prototyping, low-security (gym lockers, parking) High-value assets, payment
MIFARE Classic 1K ⭐ Low (broken crypto) $0.30-0.60 2-4 cm Legacy only (hotel keycards pre-2010) NEW deployments (avoid entirely!)
MIFARE DESFire EV2 ⭐⭐⭐⭐ High (AES-128) $1.50-3.00 2-4 cm Office buildings, universities, hospitals Budget-constrained, overkill for low-security
NTAG 424 DNA ⭐⭐⭐⭐⭐ Very High (AES-128 + secure unique NFC) $2.50-4.00 2-4 cm Recommended for all NEW deployments Low-value applications where cost matters
MIFARE Plus ⭐⭐⭐ Medium-High (AES-128 optional) $0.80-1.50 2-4 cm Upgrading legacy MIFARE Classic systems Greenfield (use DESFire or NTAG 424)
HID iCLASS SE ⭐⭐⭐⭐⭐ Very High (proprietary crypto) $4.00-8.00 2-4 cm Government, defense, critical infrastructure Budget-limited, vendor lock-in concerns

Security feature comparison:

Feature NTAG213 MIFARE Classic MIFARE DESFire EV2 NTAG 424 DNA HID iCLASS SE
UID clonable? ✅ Yes (fatal flaw) ✅ Yes ❌ No (secure diversified UID) ❌ No (SDMUID) ❌ No (encrypted)
Mutual authentication ❌ No ⚠️ Yes (broken CRYPTO1) ✅ Yes (AES-128) ✅ Yes (AES-128) ✅ Yes (proprietary)
Encrypted data ❌ No (plain text) ⚠️ Yes (broken CRYPTO1) ✅ Yes (AES-128) ✅ Yes (AES-128 + CMAC) ✅ Yes
Rolling codes ❌ No ❌ No ⚠️ Optional (app-layer) ✅ Yes (built-in SUN) ✅ Yes
Relay attack protection ❌ No ❌ No ⚠️ Partial (timing checks) ⚠️ Partial ✅ Yes (distance bounding)
Write protection ⚠️ One-time lock ⚠️ Key-protected (broken) ✅ AES-protected ✅ AES-protected ✅ Hardware-locked

Attack resistance comparison:

Attack Vector NTAG213 MIFARE Classic DESFire EV2 NTAG 424 DNA
UID cloning ❌ 5 minutes ($30 Proxmark) ❌ 5 minutes ✅ Immune (diversified UID) ✅ Immune
Data readout ❌ Instant (no encryption) ❌ 30 min (broken CRYPTO1) ✅ Immune (AES-128) ✅ Immune
Replay attack ❌ Works forever (static UID) ❌ Works forever ⚠️ Depends on app design ✅ Immune (SUN rolling code)
Man-in-middle ❌ Trivial ❌ Trivial ⚠️ Requires breaking AES ⚠️ Requires breaking AES

Decision tree:

Step 1: What’s the value of the protected asset/area?

  • Low value (<$1,000): NTAG213 acceptable (gym locker, parking)
  • Medium value ($1,000-50,000): MIFARE DESFire EV2
  • High value (>$50,000, sensitive data): NTAG 424 DNA or HID iCLASS SE

Step 2: Do you have existing MIFARE Classic infrastructure?

  • YES → Upgrade path: MIFARE Plus (backward compatible) or DESFire EV2 (full migration)
  • NO → Start fresh with NTAG 424 DNA (best security/cost ratio)

Step 3: Is vendor lock-in acceptable?

  • NO → Avoid HID iCLASS (proprietary), use NTAG 424 DNA (NXP open standard)
  • YES → HID iCLASS SE (highest security, best enterprise support)

Step 4: What’s the credential budget per employee?

  • <$1: NTAG213 (understand security limitations!)
  • $1-3: MIFARE DESFire EV2Sweet spot for most applications
  • $3-5: NTAG 424 DNABest long-term security
  • $5: HID iCLASS SE (defense, government)

Cost-benefit analysis (100 employees, 5-year TCO):

Tag Type Per-Tag Cost Readers/Controllers Lost Card Replacement (20/year) Security Incidents 5-Year Total
NTAG213 $0.30 × 100 = $30 $200 $0.30 × 100 = $30 3 breaches @ $5,000 = $15,000 $15,260
DESFire EV2 $2.00 × 100 = $200 $400 $2.00 × 100 = $200 0 breaches = $0 $800
NTAG 424 DNA $3.00 × 100 = $300 $400 $3.00 × 100 = $300 0 breaches = $0 $1,000

Paradox: Cheaper tags (NTAG213) cost MORE long-term due to security incidents (cloning → unauthorized access → theft/data breach).

Recommendation for NEW deployments in 2024:

Best choice: NTAG 424 DNA - Highest security (AES-128 + SUN rolling codes) - Industry standard (NFC Forum Type 4, no vendor lock-in) - Future-proof (resistant to known attacks) - Reasonable cost ($3-4 per badge)

Budget alternative: MIFARE DESFire EV2 - Good security (AES-128 mutual auth) - Slightly cheaper ($1.50-2.00) - Lacks built-in rolling codes (must implement in application)

NEVER use in NEW deployments:

  • ❌ NTAG213/215/216 for anything beyond prototyping (UID cloneable in 5 minutes)
  • ❌ MIFARE Classic (completely broken crypto since 2008, deprecated)
Common Mistake: Using UID-Only Authentication in Production Access Control

What they did wrong: An apartment building deployed 250 NTAG213 tags for resident access, checking only the 7-byte UID against an authorized database (exactly like the ESP32 lab code in this chapter). Within 3 months, 8 cloned badges were discovered being used by unauthorized individuals.

Why UID-only authentication fails in production:

The ESP32 lab code uses this approach:

bool isAuthorized(String uid) {
  for (int i = 0; i < numAuthorizedUIDs; i++) {
    if (uid == authorizedUIDs[i]) {  // Static UID comparison
      return true;
    }
  }
  return false;
}

Problem: NTAG213/215/216 UIDs are NOT cryptographically protected. Anyone with a $30 Proxmark3 or $10 PN532 reader can: 1. Read the UID in <100 ms 2. Clone it to a “magic” NTAG213 (UID-writeable variant) in 30 seconds 3. Use cloned badge indefinitely (static UID never changes)

The attack in practice:

Day 1: Attacker stands near building entrance with PN532 reader hidden in bag - Logs UID of 5 residents tapping badges: 04 3A B2 C1 D4 5E 80, 08 F7 E2 9A..., etc.

Day 2: Attacker buys 5 “magic” NTAG213 tags ($2 each) from AliExpress

Day 3: Attacker writes stolen UIDs to magic tags using free software (NFC Tools, Mifare Classic Tool)

Original badge UID: 04 3A B2 C1 D4 5E 80
Clone badge UID: 04 3A B2 C1 D4 5E 80  ← Identical!

Day 4: Attacker uses cloned badge to enter building, steal packages from mailroom

Why traditional security measures fail:

Defense Effectiveness Against UID Cloning
“Check UID length” ❌ Useless (cloned UID has same length)
“Store UID in database” ❌ Useless (clone has identical UID)
“Use HTTPS to cloud” ❌ Useless (UID still static)
“Encrypt database” ❌ Useless (doesn’t protect UID itself)
“Add timestamp check” ❌ Useless (clone works anytime)

The ONLY effective fix: Challenge-response authentication

MIFARE DESFire EV2 / NTAG 424 DNA approach:

Instead of checking UID, the system performs a cryptographic challenge:

Reader → Tag: "Prove you hold secret key K"
         (sends random challenge: 0x3A7F9C2D)

Tag → Reader: HMAC-SHA256(challenge, K) = 0x8F3D2A...
         (computes response using key stored in tag)

Reader: Verify response matches expected value
         (only genuine tag with correct key produces correct response)

Why this prevents cloning:

  1. Secret key K never transmitted: Attacker can’t intercept it
  2. Challenge changes every auth: Replaying previous response fails (challenge is different)
  3. Key extraction requires chip-off attack: Physical destruction of tag + electron microscope probing (cost: $5,000+, success rate <5%)

Upgrading the ESP32 lab for production:

Replace NTAG213 with NTAG 424 DNA tags:

#include <NTAG424DNA.h>

NTAG424DNA tag(pn532);

// Stored in tag's secure memory (inaccessible via NFC)
byte tagKey[16] = {0x00, 0x01, 0x02, ... }; // AES-128 key

bool isAuthorizedSecure() {
  byte challenge[16];
  randomSeed(analogRead(0));
  for (int i = 0; i < 16; i++) {
    challenge[i] = random(256);  // Generate random challenge
  }

  byte response[16];
  if (!tag.authenticate(challenge, response)) {
    return false;  // Tag didn't respond or response invalid
  }

  // Verify CMAC response (tag computed CMAC using its internal key)
  byte expectedResponse[16];
  aes128_cmac(tagKey, challenge, expectedResponse);

  return memcmp(response, expectedResponse, 16) == 0;
}

Cost comparison:

Approach Tag Cost Security Cloning Resistance
UID-only (NTAG213) $0.30 ⭐ Low ❌ Cloneable in 5 min
Challenge-response (NTAG 424) $3.00 ⭐⭐⭐⭐⭐ High ✅ Immune (key extraction requires $5k equipment)

Per-door TCO (5 years, 100 residents):

Cost Component UID-Only Challenge-Response
Initial badges $30 (NTAG213) $300 (NTAG 424)
Security incidents (cloning) 8 breaches × $5,000 = $40,000 $0 (immune to cloning)
Lost badge replacement $0.30 × 100 = $30 $3.00 × 100 = $300
5-year total $40,060 $600

Conclusion:

UID-only authentication is acceptable ONLY for: - ✅ Prototyping / learning (this ESP32 lab’s purpose) - ✅ Low-value applications (gym lockers, parking gates where loss <$100) - ✅ Non-security use cases (NFC business cards, smart posters)

UID-only is NEVER acceptable for: - ❌ Building access (apartment, office, university) - ❌ Payment systems - ❌ Vehicle access - ❌ Any application where unauthorized access causes >$500 damage

Lesson: This lab teaches NFC fundamentals using UID matching for simplicity. Production systems MUST use challenge-response authentication with cryptographic tags (MIFARE DESFire EV2 or NTAG 424 DNA). The $2.70 price difference per tag ($0.30 → $3.00) is negligible compared to the cost of a single security breach.

Common Pitfalls

MIFARE Classic uses the broken Crypto-1 cipher. Cards can be cloned in seconds using a $30 device. Fix: migrate to MIFARE DESFire EV2/EV3 (AES-128 based) or NTAG 424 DNA for any access control application.

NFC UIDs are not secret and can be read by any NFC reader. Systems that grant access based on UID alone are trivially bypassed with a cloned tag. Fix: require cryptographic authentication (mutual authentication with a secret key) in addition to or instead of UID matching.

A purely NFC-based access control system becomes inaccessible during NFC reader failure or power outage. Fix: include a backup access method (PIN pad, mechanical key override) for every secured door.

39.13 Summary

This chapter covered building an ESP32-based NFC access control system:

  • Hardware Integration: Connecting PN532 NFC reader, servo motor, and feedback indicators
  • I2C Communication: Configuring ESP32 for reliable tag reading
  • Authorization Logic: Matching tag UIDs against an authorized list
  • Access Logging: Recording all access attempts in a circular buffer
  • User Feedback: Providing visual (LED) and audio (buzzer) confirmation

The system demonstrates the Reader/Writer operating mode of NFC, where the ESP32 acts as an NFC reader and passive tags provide identification.

39.14 Concept Relationships

Builds On:

Enables:

Related Concepts:

  • UID-only authentication is sufficient for prototyping but vulnerable in production
  • MIFARE DESFire provides AES-128 mutual authentication for secure access control
  • I2C communication between ESP32 and PN532 enables SPI-free integration

39.15 See Also

Hardware Platforms:

Security Upgrades:

Tutorials:

39.16 Try It Yourself

Beginner Challenge: Build the basic ESP32 + PN532 circuit on a breadboard. Upload the code and verify you can read your phone’s NFC UID when it’s tapped near the reader. Add your phone’s UID to the authorized list and test access granted/denied feedback.

Intermediate Challenge: Add a relay module to GPIO 14 to control an actual door lock (12V solenoid). Ensure the relay is activated only when access is granted, with a 5-second unlock window. Add a manual override button (GPIO 32) that grants temporary access for 30 seconds.

Advanced Challenge: Upgrade from UID matching to NTAG 424 DNA authentication. Use the PN532’s InDataExchange function to send ISO 7816-4 APDUs for challenge-response. Compare the authentication latency (UID: <100ms vs NTAG 424: ~300ms).

Production Enhancement: Replace the circular buffer access log with an SD card logger (SPI interface). Write each access event as a CSV line with timestamp (use an RTC module for accurate time). Implement a web server (ESP32 AsyncWebServer) to display the last 100 access attempts via HTTP.

39.17 What’s Next

Chapter Focus Link
NFC Smart Home Automation Build a Raspberry Pi-based smart home controller that triggers scenes when users tap NFC tags placed around the house Read
NFC Security and Comparisons Payment security, technology selection frameworks, and production-grade challenge-response authentication Read
NFC Hands-On and Applications Practical NFC implementations including tag programming, NDEF writing, and real-world deployment exercises Read
NFC Modes and Protocols Deep dive into P2P, Read/Write, and Card Emulation modes with NDEF format and Bluetooth handover Read