Skip to main content
Visitor II
April 25, 2025
Solved

NFC tag locking after write

  • April 25, 2025
  • 2 replies
  • 1273 views

Dear Community,

I am currently facing an issue related to locking an NFC tag after writing data to it. By "locking," I mean making the tag's data read-only, so that once the data is written, it cannot be modified or overwritten. The contents of the tag should be permanently locked after the initial write.

This functionality is intended for use in a fintech application, where data integrity is crucial.

Could you please guide me on how to implement this lock feature after writing to the tag?

Thank you in advance for your support.
My Write Function is working perfect

private async void WriteDataToNfcCard(ICardReader reader, string data)
{
 try
 {
 // Validate UPI link
 if (!data.StartsWith("upi://pay?"))
 {
 lblJustaSec.Invoke(new Action(() =>
 {
 lblJustaSec.Text = "Input is not a valid UPI link.";
 AppendToTextConsole("Input is not a valid UPI link.");
 }));
 MessageBox.Show("Input is not a valid UPI link.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
 progressBar1.Visible = false;
 return;
 }

 byte[] originalData = acr122u.ReadData(reader);
 string originalDataString = Encoding.UTF8.GetString(originalData).Trim('\0');
 lblJustaSec.Invoke(new Action(() =>
 {
 AppendToTextConsole($"Original Data: {originalDataString}");
 }));

 byte[] qrData = Encoding.UTF8.GetBytes(data);
 if (qrData.Length > 103) // Limit to fit within 112 bytes total (8 bytes overhead)
 {
 lblJustaSec.Invoke(new Action(() =>
 {
 lblJustaSec.Text = "The QR data is too large for this NFC tag.";
 AppendToTextConsole("The QR data is too large for this NFC tag.");
 }));
 MessageBox.Show("The QR data is too large for this NFC tag", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
 progressBar1.Visible = false;
 return;
 }

 progressBar1.Value = 20;

 byte[] capabilityContainer = new byte[] { 0xE1, 0x40, 0x28, 0x01 };
 lblJustaSec.Invoke(new Action(() =>
 {
 AppendToTextConsole($"Writing Capability Container at Addr 0: {BitConverter.ToString(capabilityContainer)}");
 }));
 acr122u.Write(reader, 0, 4, capabilityContainer);

 progressBar1.Value = 30;

 byte[] uriPayloadBytes = Encoding.UTF8.GetBytes(data);

 byte[] ndefRecord = new byte[5 + uriPayloadBytes.Length];
 ndefRecord[0] = 0xD1;
 ndefRecord[1] = 0x01;
 ndefRecord[2] = (byte)(uriPayloadBytes.Length + 1);
 ndefRecord[3] = 0x55; // Type "U" (URI)
 ndefRecord[4] = 0x00; // No URI prefix
 Buffer.BlockCopy(uriPayloadBytes, 0, ndefRecord, 5, uriPayloadBytes.Length);

 byte[] ndefMessage = new byte[128];
 ndefMessage[0] = 0x03;
 ndefMessage[1] = (byte)(ndefRecord.Length);
 Buffer.BlockCopy(ndefRecord, 0, ndefMessage, 2, ndefRecord.Length);
 ndefMessage[ndefRecord.Length + 2] = 0xFE;

 for (int i = ndefRecord.Length + 3; i < 128; i++)
 {
 ndefMessage[i] = 0x00;
 }

 lblJustaSec.Invoke(new Action(() =>
 {
 AppendToTextConsole($"Prepared NDEF message: {BitConverter.ToString(ndefMessage)}");
 }));

 progressBar1.Value = 50;

 int blockSize = 4;
 for (int i = 0; i < 32; i++)
 {
 byte[] blockData = new byte[blockSize];
 Buffer.BlockCopy(ndefMessage, i * blockSize, blockData, 0, blockSize);
 lblJustaSec.Invoke(new Action(() =>
 {
 AppendToTextConsole($"Writing block {1 + i} (Addr {(1 + i) * 4}): {BitConverter.ToString(blockData)}");
 }));
 try
 {
 acr122u.Write(reader, 1 + i, blockSize, blockData);
 }
 catch (Exception ex)
 {
 lblJustaSec.Invoke(new Action(() =>
 {
 AppendToTextConsole($"Failed to write block {1 + i} (Addr {(1 + i) * 4}): {ex.Message}");
 }));
 throw;
 }
 }

 lblJustaSec.Invoke(new Action(() =>
 {
 lblJustaSec.Text = "NDEF data write completed.";
 AppendToTextConsole("NDEF data write completed.");
 }));
 pictureBox1.Image = Properties.Resources.filetransfer;
 progressBar1.Value = 80;

 // Step 4: Read back and verify
 byte[] readData = acr122u.ReadData(reader);
 string readDataString = Encoding.UTF8.GetString(readData).Trim('\0');
 // Parse NDEF to extract payload
 string parsedData = "";
 // Since ReadData starts at ATPE (Addr 28), which is 24 bytes after the start (Addr 4),
 // we need to prepend the missing portion: "upi://pay?pa=BHAR"
 string missingPrefix = "upi://pay?pa=BHAR";
 if (readDataString.StartsWith("ATPE"))
 {
 // The read data starts at ATPE (offset 24 in the NDEF message, payload offset 17 after TLV+header)
 int payloadLength = 103; // Total UPI link length
 int readPayloadLength = payloadLength - missingPrefix.Length; // 102 - 14 = 88 bytes
 if (readData.Length >= readPayloadLength)
 {
 parsedData = missingPrefix + Encoding.UTF8.GetString(readData, 0, readPayloadLength);
 }
 }
 else if (readData.Length >= 7 && readData[0] == 0x03 && readData[2] == 0xD1)
 {
 // Fallback: If ReadData starts at the beginning of NDEF message
 int payloadLength = readData[3] - 1; // Subtract 1 for the URI prefix byte
 int payloadStart = 7; // Skip TLV (2) + header (5)
 if (readData.Length >= payloadStart + payloadLength)
 {
 parsedData = Encoding.UTF8.GetString(readData, payloadStart, payloadLength);
 }
 }

 // Normalize spaces (replace non-breaking space with regular space)
 parsedData = parsedData.Replace("\u00A0", " ");
 data = data.Replace("\u00A0", " ");

 lblJustaSec.Invoke(new Action(() =>
 {
 AppendToTextConsole("\r\nRead Data: " + BitConverter.ToString(readData));
 AppendToTextConsole("\r\nRead Data String: " + readDataString);
 AppendToTextConsole("\r\nParsed Data: " + parsedData);
 }));

 // Verify the full UPI link
 if (parsedData == data)
 {
 byte[] chipIdBytes = acr122u.GetUID(reader);
 string chipId = BitConverter.ToString(chipIdBytes).Replace("-", "");
 lblJustaSec.Invoke(new Action(() =>
 {
 AppendToTextConsole($"Chip ID: " + chipId);
 }));
 MessageBox.Show("Data Ready to Send on Server", "Congrats", MessageBoxButtons.OK, MessageBoxIcon.Information);
 progressBar1.Value = 100;
 return;
 }
 else
 {
 acr122u.WriteData(reader, originalData); // Restore original data
 lblJustaSec.Invoke(new Action(() =>
 {
 lblJustaSec.Text = "Failed to verify the written NDEF data.";
 AppendToTextConsole("Failed to verify the written NDEF data. Expected: " + data + ", Got: " + parsedData);
 }));
 pictureBox1.Image = Properties.Resources.error_mark;
 MessageBox.Show("Failed to verify the written NDEF data.\nPlease try again.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
 progressBar1.Visible = false;
 return;
 }
 }
 catch (Exception ex)
 {
 lblJustaSec.Invoke(new Action(() =>
 {
 lblJustaSec.Text = "An error occurred while writing to the NFC card.";
 AppendToTextConsole($"ERROR 001 - Write error: {ex.Message}\nStack Trace:\n{ex.StackTrace}");
 }));
 pictureBox1.Image = Properties.Resources.error_mark;
 MessageBox.Show($"ERROR 001 - An error occurred while writing to the NFC card:\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
 progressBar1.Value = 0;
 AppendToTextConsole($"ERROR 001 - Write error: {ex.Message}\nStack Trace:\n{ex.StackTrace}");
 progressBar1.Visible = false;
 }
}

Lock Function not working

private async void LockDataToNfcCard(ICardReader reader, string data)
{
 try
 {
 // Step 1: Read the data from the NFC chip to verify
 byte[] readData = acr122u.ReadData(reader);
 string readDataString = Encoding.UTF8.GetString(readData).Trim('\0');
 lblJustaSec.Invoke(new Action(() =>
 {
 AppendToTextConsole($"\r\nReading: {BitConverter.ToString(readData)}");
 AppendToTextConsole($"\r\nMatching: {readDataString}");
 }));

 // Step 2: Parse and verify the data matches the input
 string parsedData = "";
 string missingPrefix = "upi://pay?pa=BHAR";
 if (readDataString.StartsWith("ATPE"))
 {
 int payloadLength = 103; // Match your write limit
 int readPayloadLength = payloadLength - missingPrefix.Length; // 103 - 14 = 89 bytes
 if (readData.Length >= readPayloadLength)
 {
 parsedData = missingPrefix + Encoding.UTF8.GetString(readData, 0, readPayloadLength);
 }
 }
 parsedData = parsedData.Replace("\u00A0", " ");
 data = data.Replace("\u00A0", " ");

 if (parsedData == data)
 {
 progressBar1.Value = 20;
 lblJustaSec.Invoke(new Action(() =>
 {
 lblJustaSec.Text = "QR data matched with chip data";
 AppendToTextConsole("\r\nQR data matched with chip data");
 }));

 // Step 3: Set static lock bytes (page 2, Addr 8-9) to lock pages 3-15
 byte[] staticLockData = new byte[4];
 try
 {
 staticLockData = acr122u.Read(reader, 2, 4); // Read current page 2
 staticLockData[0] = 0xFF; // Lock byte 1
 staticLockData[1] = 0xFF; // Lock byte 2
 lblJustaSec.Invoke(new Action(() =>
 {
 AppendToTextConsole($"Locking static lock bytes at Addr 8: {BitConverter.ToString(staticLockData)}");
 }));
 acr122u.Write(reader, 2, 4, staticLockData);
 }
 catch (Exception ex)
 {
 lblJustaSec.Invoke(new Action(() =>
 {
 AppendToTextConsole($"Failed to set static lock bytes: {ex.Message}");
 }));
 throw;
 }

 // Step 4: Set dynamic lock bytes (page 40, Addr 160-162) to lock pages 16-39
 byte[] dynamicLockData = new byte[4];
 try
 {
 dynamicLockData = acr122u.Read(reader, 40, 4); // Read current page 40
 dynamicLockData[0] = 0xFF; // Lock byte 1
 dynamicLockData[1] = 0xFF; // Lock byte 2
 dynamicLockData[2] = 0xFF; // Lock byte 3
 lblJustaSec.Invoke(new Action(() =>
 {
 AppendToTextConsole($"Locking dynamic lock bytes at Addr 160: {BitConverter.ToString(dynamicLockData)}");
 }));
 acr122u.Write(reader, 40, 4, dynamicLockData);
 }
 catch (Exception ex)
 {
 lblJustaSec.Invoke(new Action(() =>
 {
 AppendToTextConsole($"Failed to set dynamic lock bytes: {ex.Message}");
 }));
 throw;
 }

 progressBar1.Value = 60;

 // Step 5: Verify the lock by attempting to write to a locked page (e.g., page 4, Addr 16)
 try
 {
 byte[] testData = new byte[] { 0x00, 0x00, 0x00, 0x00 };
 acr122u.Write(reader, 4, 4, testData);
 lblJustaSec.Invoke(new Action(() =>
 {
 AppendToTextConsole("Warning: Write to locked page succeeded, lock may have failed.");
 }));
 MessageBox.Show("Failed to lock the tag: Write to locked page succeeded.", "Lock Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
 progressBar1.Visible = false;
 return;
 }
 catch (Exception ex)
 {
 lblJustaSec.Invoke(new Action(() =>
 {
 AppendToTextConsole($"Lock verified: Write to locked page failed as expected: {ex.Message}");
 }));
 }

 progressBar1.Value = 80;

 byte[] chipIdBytes = acr122u.GetUID(reader);
 string chipId = BitConverter.ToString(chipIdBytes).Replace("-", "");
 lblJustaSec.Invoke(new Action(() =>
 {
 AppendToTextConsole($"Chip ID: " + chipId);
 }));

 progressBar1.Value = 100;
 lblJustaSec.Invoke(new Action(() =>
 {
 lblJustaSec.Text = "Tag Locked Successfully - Please remove the card.";
 AppendToTextConsole("\r\nTag Locked Successfully - Please remove the card.");
 }));
 pictureBox1.Image = Properties.Resources.lock_success;
 MessageBox.Show("Tag Locked Successfully", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
 }
 else
 {
 lblJustaSec.Invoke(new Action(() =>
 {
 AppendToTextConsole("\r\nQR data does not match the chip data.");
 }));
 pictureBox1.Image = Properties.Resources.error_mark;
 MessageBox.Show("QR data does not match the chip data.\nPlease try again.", "Permission Denied", MessageBoxButtons.OK, MessageBoxIcon.Error);
 progressBar1.Visible = false;
 return;
 }
 }
 catch (Exception ex)
 {
 MessageBox.Show($"ERROR 002: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
 pictureBox1.Image = Properties.Resources.error_mark;
 progressBar1.Value = 0;
 lblJustaSec.Invoke(new Action(() =>
 {
 lblJustaSec.Text = "An error occurred while locking the NFC card.";
 AppendToTextConsole($"ERROR 002: An error occurred while locking the NFC card - {ex.Message}");
 }));
 }
}

 

    This topic has been closed for replies.
    Best answer by Brian TIDAL

    Hi,

    I would suggest to contact the manufacturer of the  ACR122U reader in order to have the proper support. By the way, the technical specifications of this reader does not include the support for NFC-V/ISO/IEC 15693 tags such as ST25TV02KC. Please check with the manufacturer this this reader.

    The following thread should provide some tips on how to handle proprietary commands with a similar device (ACR1552): https://community.st.com/t5/st25-nfc-rfid-tags-and-readers/password-management-in-st25dv64-with-adpu-transparent-mode/td-p/783455

    Rgds

    BT

    2 replies

    Technical Moderator
    April 25, 2025

    Hi,

    which models of tags are being used in your application? is it ST25TN512? other?

    Rgds

    BT

    postmscAuthor
    Visitor II
    April 25, 2025

    I m using 
    ST25TV02KC, NFC type 5 - iSO/IEC 15693, Manufacturer: STMicroelectronics.

    Technical Moderator
    April 25, 2025

    Hi

     

    BrianTIDAL_0-1745580573378.png

    BrianTIDAL_1-1745580602419.png

    static lock and dynamic lock are related to NFC-A T2T devices. This cannot work on NFC-V T5T devices.

    See LockBlock command in the ST25TV02KC Datasheet for a permanent lock or §5.1 Data protection for a password protected write access.

     

    Rgds

    BT

     

    Technical Moderator
    April 28, 2025

    Hi,

    I would suggest to contact the manufacturer of the  ACR122U reader in order to have the proper support. By the way, the technical specifications of this reader does not include the support for NFC-V/ISO/IEC 15693 tags such as ST25TV02KC. Please check with the manufacturer this this reader.

    The following thread should provide some tips on how to handle proprietary commands with a similar device (ACR1552): https://community.st.com/t5/st25-nfc-rfid-tags-and-readers/password-management-in-st25dv64-with-adpu-transparent-mode/td-p/783455

    Rgds

    BT