0

I am developing an NFC test app using Xamarin.Forms and the Plugin.NFC library: https://github.com/franckbour/Plugin.NFC/

However, this library does not include a feature for setting or removing passwords, so I need to add that functionality myself.

I am adding the card information here.

My card is ISO 14443-3A
NXP - NTAG213
NfcA, MifareUltralight, Ndef
and below there is exported memory

and here is the memory dump

    [ 04:C1:FE:B3 ] Addr. 00 : UID0 - UID2 / BCC0
    [ F2:04:74:80 ] Addr. 01 : UID3 - UDI6
    [ 02:48:00:00 ] Addr. 02 : BCC1 / INT. / LOCK0 - LOCK1
    [ E1:10:12:00 ] Addr. 03 : OTP0 - OTP3
    [ 01:03:A0:0C ] Addr. 04 : DATA
    [ 34:03:0A:D1 ] Addr. 05 : DATA
    [ 01:06:54:02 ] Addr. 06 : DATA
    [ 65:6E:61:61 ] Addr. 07 : DATA
    [ 61:FE:00:00 ] Addr. 08 : DATA
    [ 63:3F:54:61 ] Addr. 09 : DATA
    [ 67:49:64:3D ] Addr. 0A : DATA
    [ 30:34:43:31 ] Addr. 0B : DATA
    [ 46:45:46:32 ] Addr. 0C : DATA
    [ 30:34:37:34 ] Addr. 0D : DATA
    [ 38:30:26:50 ] Addr. 0E : DATA
    [ 61:79:6C:6F ] Addr. 0F : DATA
    [ 61:64:3D:4D ] Addr. 10 : DATA
    [ 79:5F:49:6D ] Addr. 11 : DATA
    [ 70:6F:72:74 ] Addr. 12 : DATA
    [ 61:6E:74:5F ] Addr. 13 : DATA
    [ 4B:65:79:FE ] Addr. 14 : DATA
    [ 73:20:65:76 ] Addr. 15 : DATA
    [ 65:72:79:74 ] Addr. 16 : DATA
    [ 68:69:6E:67 ] Addr. 17 : DATA
    [ 20:6F:6B:3F ] Addr. 18 : DATA
    [ 20:79:65:73 ] Addr. 19 : DATA
    [ 20:69:74:20 ] Addr. 1A : DATA
    [ 69:73:FE:00 ] Addr. 1B : DATA
    [ 70:73:75:6D ] Addr. 1C : DATA
    [ 20:68:61:73 ] Addr. 1D : DATA
    [ 20:62:65:65 ] Addr. 1E : DATA
    [ 6E:20:74:68 ] Addr. 1F : DATA
    [ 65:20:69:6E ] Addr. 20 : DATA
    [ 64:75:73:74 ] Addr. 21 : DATA
    [ 72:79:27:73 ] Addr. 22 : DATA
    [ 20:73:74:61 ] Addr. 23 : DATA
    [ 6E:64:61:72 ] Addr. 24 : DATA
    [ 64:20:64:75 ] Addr. 25 : DATA
    [ 6D:6D:79:FE ] Addr. 26 : DATA
    [ 00:00:00:00 ] Addr. 27 : DATA
    [ 00:00:00:BD ] Addr. 28 : LOCK2 - LOCK4
    [ 04:00:00:00 ] Addr. 29 : CFG 0 (MIRROR / AUTH0)
    [ 00:05:00:00 ] Addr. 2A : CFG 1 (ACCESS)
    [ 00:00:00:00 ] Addr. 2B : PWD0 - PWD3
    [ 00:00:00:00 ] Addr. 2C : PACK0 - PACK1

I can detect whether a card is password-protected or not using the following code in Android.

  public bool CheckConfiguration()
  {
    bool passExist;
    MifareUltralight mu = null;
    try
    {
      mu = MifareUltralight.Get( _currentTag );
      mu.Connect();
      var answer = mu.ReadPages( 41 );
      byte auth0 = answer[ 3 ];
      passExist = auth0 < (byte)0xEB;

      Debug.WriteLine( "Page 41 (addr29)" );
      Debug.WriteLine( string.Join( "", answer ) );

      mu.Close();
    }
    catch( Exception e )
    {
      mu?.Close();
      passExist = true;
    }
    return passExist;
  }

The authentication status is located at address 0x29. If the last byte is 0x00, it means the card is password-protected; if the last byte is 0xFF, it means the card is not password-protected.

I was able to send a basic command and receive a result successfully.


    private void TestReadBlock(MifareUltralight mfu)
    {
        try
        {
            if (!mfu.IsConnected)
                mfu.Connect();
    
            // Read block 4 as a test
            byte[] readCommand = { 0x30, 0x04 }; // Read block 4
            byte[] response = mfu.Transceive(readCommand);
    
            if (response != null)
            {
                Debug.WriteLine($"Read successful. Response: {BitConverter.ToString(response)}");
            }
            else
            {
                Debug.WriteLine("Read failed. Response is null.");
            }
        }
        catch (Java.IO.IOException ex)
        {
            Debug.WriteLine($"IOException during read: {ex.Message}");
        }
        finally
        {
            try
            {
                if (mfu.IsConnected)
                    mfu.Close();
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Failed to close connection: {ex.Message}");
            }
        }
    }

I can get a result.

However, when I attempt authentication with the 0x1B command, I always receive the error "Tag was lost."

For example, I get an exception when calling the Transceive method the first time.

    private bool RemovePasswordProtection(MifareUltralight mfu)
    {
        try
        {
            if (!mfu.IsConnected)
                mfu.Connect();
    
            byte[] pwdAuthCommand = { 0x1B, 0x00, 0x00, 0x00, 0x00 };
            byte[] response = mfu.Transceive(pwdAuthCommand);
    
            byte[] writeCommand = { 0xA2, 0x29, 0xFF, 0x00, 0x00, 0x00 };
            mfu.Transceive(writeCommand);
    
            return true;
        }
        catch (Exception ex)
        {
            Debug.WriteLine($"Error removing password protection: {ex.Message}");
            return false;
        }
        finally
        {
            try
            {
                if (mfu.IsConnected)
                    mfu.Close();
            }
            catch { }
        }
    }

I am not sure if the command is incorrect or if there is another underlying issue. One thing to note is that I call this authentication method after detecting a tag.

EDIT:

It seems that the password is not in the memory dump. When I set a password and then remove it, there is only on difference as follow

EDIT 2:

I add my password to the auth command however I get always the "Tag was lost" exception while executing mfu.Transceive( pwdAuthCommand );

 private async Task<bool> TryRawAuthentication( MifareUltralight mfu )
  {
    try
    {
        if( !mfu.IsConnected )
        mfu.Connect();

        byte[] pwdAuthCommand = {
              0x1B,   // PWD_AUTH command
              0x31,   // '1'
              0x32,   // '2'
              0x33,   // '3'
              0x34    // '4'
          };

        Debug.WriteLine( $"Sending PWD_AUTH command: {BitConverter.ToString( pwdAuthCommand )}" );
        byte[] response = mfu.Transceive( pwdAuthCommand );
        Debug.WriteLine( $"NfcA Auth Response: {BitConverter.ToString( response )}" );
        return true;
    }
    catch( Exception ex )
    {
      Debug.WriteLine( $"Raw authentication error: {ex.Message}" );
      return false;
    }
  }
2
  • 1
    My answer told you the PWD and PACK real values can never be read, they always return all zeros, so your edit about what changes when a password is set is as expected
    – Andrew
    Commented Dec 3 at 11:46
  • Yes I got it after I read the documentation. Thank you for the clarification @Andrew
    – boss
    Commented Dec 3 at 12:01

2 Answers 2

1

Most likely that { 0x00, 0x00, 0x00, 0x00 } is not the password.

As per the Datasheet Section 8.8.1

The PWD and PACK bytes can never be read out of the memory. Instead of transmitting the real value on any valid READ or FAST_READ command, only 00h bytes are replied.

You need to know the real password, you cannot read the password from the Dump.

If you try and authenticate with the wrong password the Tag will go in to HALT state and hence the Tag lost error.

So Transceive with the right values for the password you set.

byte[] pwdAuthCommand = { 0x1B, 0x01, 0x02, 0x03, 0x04 };

Once successfully authenticated you can then change the password/pack and password config.

Also always check the byte[] response from the Transceive as that can give you more info on what went wrong with a command.

Update:

I've had a look at the NFC Plugin and on Android it uses an older NFC API that because of user behaviour and how it works is more prone to generate a lot more "Tag Lost" error messages when doing any other operation than reading Ndef Messages. e.g. writing or password auth commands.

I don't know if there is a better Plugin that uses a better Android NFC API.

Thus it is possible that the "Tag Lost" message are really because the Tag has gone out of range before you try the password auth. If you are very careful as a user about the Tag placement it should not be a problem.

Otherwise the Standard response to a wrong password will be usually "Tag Lost".

I would double check the byte[] has the right content, I note that sometimes people cast the hex values to byte with (byte). I'm also note sure when you set it with NFC tools if it used the data entered as Hex values or as you have shown converted them to the ASCII hex values. May be use code to write a password to another Tag so you know exactly the hex values used.

From your Dump it does not look like you have set a limit on the number of password attempts, if you had then too many bad attempts would lock the card and can also produce "Tag Lost" on non read commands.

6
  • I dont want to read the password. I want to remove it. I set a password with the NFC Tools app. It is 1234. The question is that how can I remove the password and also set a password?
    – boss
    Commented Dec 3 at 11:24
  • By the way I compare the dumps, the password is not here. I set a password and then removed, there is only one change. I am adding it to the question
    – boss
    Commented Dec 3 at 11:38
  • Then pwdAuthCommand with the real password of "1234" first not "0000" as shown in the example code your gave, before you can write to the AUTH0 you need to authenticate. Also check your response to the pwdAuthCommand it might not be a ACK if auth limiting is used.
    – Andrew
    Commented Dec 3 at 11:41
  • thank you for your reply. I got your point and changed my code (edited my question) but I still get the same exception. And I cannot read the response from the Transceive method because the exception occurs there
    – boss
    Commented Dec 3 at 12:00
  • when I give this command 0x1B, 0x01, 0x02, 0x03, 0x04, I get the same exception. Tag was lost.
    – boss
    Commented Dec 3 at 12:09
1

A big thank you to @Andrew . His points really helped me solve this issue.

I couldn’t solve the problem using Plugin.NFC, but I managed to resolve it in a different way. Here’s the solution:

public class NfcCardOperations
{
  private const int PWD_PAGE_ADDRESS = 0x2B;
  private const int PACK_PAGE_ADDRESS = 0x2C;
  private const int AUTH0_PAGE_ADDRESS = 0x29;
  private const int ACCESS_PAGE_ADDRESS = 0x2A;

  public async Task SetPassword( Tag tag, string password, byte[] pack, bool protectRead = false )
  {
    if( string.IsNullOrWhiteSpace( password ) || password.Length != 8 )
    {
      throw new ArgumentException( "Password must be exactly 8 hexadecimal characters." );
    }

    if( pack == null || pack.Length != 2 )
    {
      throw new ArgumentException( "PACK must be exactly 2 bytes." );
    }

    try
    {
      using( var tech = NfcA.Get( tag ) )
      {
        await Task.Run( () =>
        {
          tech.Connect();

          if( tech.IsConnected )
          {
            byte[] pwdBytes = ConvertPasswordToBytes( password );

            WritePage( tech, PWD_PAGE_ADDRESS, pwdBytes );

            byte[] packData = new byte[] { pack[ 0 ], pack[ 1 ], 0x00, 0x00 };
            WritePage( tech, PACK_PAGE_ADDRESS, packData );

            byte protValue = (byte)(protectRead ? 0x80 : 0x00); // 0x80 for read/write protection, 0x00 for write-only protection
            byte[] accessData = new byte[] { protValue, 0x05, 0x00, 0x00 }; // AUTHLIM=5
            WritePage( tech, ACCESS_PAGE_ADDRESS, accessData );

            byte[] auth0Data = new byte[] { 0x04, 0x00, 0x00, 0x00 };
            WritePage( tech, AUTH0_PAGE_ADDRESS, auth0Data );
          }

          tech.Close();
        } );
      }
    }
    catch( Exception ex )
    {
      throw new Exception( "Error setting password: " + ex.Message );
    }
  }

  public async Task RemovePassword( Tag tag, string currentPassword )
  {
    try
    {
      using( var tech = NfcA.Get( tag ) )
      {
        await Task.Run( () =>
        {
          tech.Connect();

          if( tech.IsConnected )
          {
            byte[] pwdBytes = ConvertPasswordToBytes( currentPassword );
            byte[] pwdCommand = new byte[] { 0x1B, pwdBytes[ 0 ], pwdBytes[ 1 ], pwdBytes[ 2 ], pwdBytes[ 3 ] };
            byte[] pwdResponse = tech.Transceive( pwdCommand );


            byte[] accessData = new byte[] { 0x00, 0x00, 0x00, 0x00 };
            WritePage( tech, ACCESS_PAGE_ADDRESS, accessData );

            byte[] auth0Data = new byte[] { 0x04, 0x00, 0x00, 0xFF };
            WritePage( tech, AUTH0_PAGE_ADDRESS, auth0Data );

            byte[] clearBytes = new byte[] { 0x00, 0x00, 0x00, 0x00 };
            WritePage( tech, PWD_PAGE_ADDRESS, clearBytes );
            WritePage( tech, PACK_PAGE_ADDRESS, clearBytes );

            tech.Close();
          }
        } );
      }
    }
    catch( Exception ex )
    {
      throw new Exception( "Error removing password: " + ex.Message );
    }
  }


  private void WritePage( NfcA tech, int pageAddress, byte[] data )
  {
    if( data.Length != 4 )
    {
      throw new ArgumentException( "Data must be exactly 4 bytes." );
    }

    byte[] command = new byte[] { 0xA2, (byte)pageAddress, data[ 0 ], data[ 1 ], data[ 2 ], data[ 3 ] };
    byte[] response = tech.Transceive( command );

    if( response == null || response.Length == 0 )
    {
      throw new Exception( $"Failed to write to page {pageAddress:X2}" );
    }
  }

  private byte[] ConvertPasswordToBytes( string password )
  {
    byte[] pwdBytes = new byte[ 4 ];

    for( int i = 0 ; i < 4 ; i++ )
    {
      pwdBytes[ i ] = Convert.ToByte( password.Substring( i * 2, 2 ), 16 );
    }

    return pwdBytes;
  }

}

And here’s how I call my methods:

public async Task HandleTag()
{

  bool isSetPassword = false;

  const string DefaultPassword = "01020304"; // 8 hex characters
  byte[] defaultPack = new byte[] { 0x00, 0x00 };

  if( _currentTag == null )
  {
    Console.WriteLine( "Error: No NFC tag detected." );
    return;
  }

  try
  {

    if( isSetPassword )
    {
      Console.WriteLine( "Attempting to set password..." );
      await _nfcOps.SetPassword( _currentTag, DefaultPassword, defaultPack );
      Console.WriteLine( "Password successfully set." );
    }
    else
    {
      Console.WriteLine( "Attempting to remove password..." );
      await _nfcOps.RemovePassword( _currentTag, DefaultPassword );
      Console.WriteLine( "Password successfully removed." );
    }
  }
  catch( ArgumentException argEx )
  {
    Console.WriteLine( $"Argument Error: {argEx.Message}" );
  }
  catch( Exception ex )
  {
    Console.WriteLine( $"Error: {ex.Message}" );
  }
}

and finally my _currentTag comes from

 _currentTag = intent.GetParcelableExtra( NfcAdapter.ExtraTag ) as Tag;

I hope it helps

EDIT: If you set protectRead to true, you will not be able to read the data from the card unless you remove the password. This feature effectively locks the ability to read the data.

Not the answer you're looking for? Browse other questions tagged or ask your own question.