cancel
Showing results for 
Search instead for 
Did you mean: 

NFC tag locking after write

postmsc
Visitor

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}");
        }));
    }
}

 

3 REPLIES 3
Brian TIDAL
ST Employee

Hi,

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

Rgds

BT

In order to give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.

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

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

 

In order to give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.