2025-04-24 11:00 PM - last edited on 2025-04-25 5:58 AM by mƎALLEm
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}");
}));
}
}
2025-04-25 12:37 AM
Hi,
which models of tags are being used in your application? is it ST25TN512? other?
Rgds
BT
2025-04-25 3:42 AM
I m using
ST25TV02KC, NFC type 5 - iSO/IEC 15693, Manufacturer: STMicroelectronics.
2025-04-25 4:42 AM
Hi
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