Search code examples
javaandroidauthenticationnfcmifare

NTAG212 Mifare Ultralight with Authentication


I am new to NFC Android and I have been stuck for days trying to get the Page 4 to page 7 of NTAG212 Mifare Ultralight with Authentication, I already have the PWD and PACK to do the PWD_AUTH based on the NTAG212 Docs.

I do this approach...

//assume password as array of bytes
//assume pack as array of bytes
try{
nfc.connect();
byte[] cmd1 = nfc.transceive(new byte[]{ (byte) 0x30, (byte) 0x00 }); //read the page 0     to make the NFC active
nfc.transceive(new byte[]{
   (byte) 0x1B, //command for PWD_AUTH
   pass[0],
   pass[1],
   pass[2],
   pass[3]
});
byte[] cmd4 = nfc.transceive(new byte[]{ (byte) 0x30, (byte) 0x04 }); //read the page 4
}catch(TagLostException e){
  e.printStackTrace();
}catch(IOException e){
  e.printStachTrace();
}finally{
    try{
        nfc.close();
    }catch(Exception e){
      //display failed to close
    }
}

I always receive a android.nfc.TagLostException: Tag was lost. Error after sending the PWD_AUTH command to the NFC. Can someone tell me what I am doing wrong? Is my approach correct? Please help.

NOTE: I have read the docs of NTAG212 many times, searched google, stackoverflow and all possible resources.

TIA,
Kenster


Solution

  • The PWD_AUTH command that you send to the tag does not make much sense.

    The idea of the PWD_AUTH command is that you send your password (a 4-byte value) and that the tag responds with its password acknowledge (PACK) value (a 2-byte value) if you authenticated with the correct password. You can then verify the PACK value against the expected password acknowledge to "authenticate" the tag.

    So the correct command would be:

    byte[] response = nfc.transceive(new byte[] {
        (byte) 0x1B, // PWD_AUTH
        pass[0], pass[1], pass[2], pass[3]
    });
    if ((response != null) && (response.length >= 2)) {
       byte[] pack = Arrays.copyOf(response, 2);
       // TODO: verify PACK to confirm that tag is authentic (not really,
       // but that whole PWD_AUTH/PACK authentication mechanism was not
       // really meant to bring much security, I hope; same with the
       // NTAG signature btw.)
    }
    

    What you need in order to enable password protection (on NTAG212):

    1. Set PWD (page 39) to your desired password (default value is 0xFFFFFFFF).

      byte[] response = nfc.transceive(new byte[] {
          (byte) 0xA2, // WRITE
          (byte) 39,   // page address
          pass[0], pass[1], pass[2], pass[3]
      });
      
    2. Set PACK (page 40, bytes 0-1) to your desired password acknowledge (default value is 0x0000).

      byte[] response = nfc.transceive(new byte[] {
          (byte) 0xA2, // WRITE
          (byte) 40,   // page address
          pack[0], pack[1],   // bytes 0-1 are PACK value
          (byte) 0, (byte) 0  // other bytes are RFU and must be written as 0
      });
      
    3. Set AUTHLIM (page 38, byte 0, bits 2-0) to the maximum number of failed password verification attempts (setting this value to 0 will permit an unlimited number of PWD_AUTH attempts).

    4. Set PROT (page 38, byte 0, bit 7) to your desired value (0 = PWD_AUTH in needed only for write access, 1 = PWD_AUTH is necessary for read and write access).

      byte[] response = nfc.transceive(new byte[] {
          (byte) 0x30, // READ
          (byte) 38    // page address
      });
      if ((response != null) && (response.length >= 16)) {  // read always returns 4 pages
          boolean prot = false;  // false = PWD_AUTH for write only, true = PWD_AUTH for read and write
          int authlim = 0; // value between 0 and 7
          response = nfc.transceive(new byte[] {
              (byte) 0xA2, // WRITE
              (byte) 38,   // page address
              (byte) ((response[0] & 0x078) | (prot ? 0x080 : 0x000) | (authlim & 0x007)),
              response[1], response[2], response[3]  // keep old value for bytes 1-3, you could also simply set them to 0 as they are currently RFU and must always be written as 0 (response[1], response[2], response[3] will contain 0 too as they contain the read RFU value)
          });
      }
      
    5. Set AUTH0 (page 37, byte 3) to the first page that should require password authentication.

      byte[] response = nfc.transceive(new byte[] {
          (byte) 0x30, // READ
          (byte) 37    // page address
      });
      if ((response != null) && (response.length >= 16)) {  // read always returns 4 pages
          boolean prot = false;  // false = PWD_AUTH for write only, true = PWD_AUTH for read and write
          int auth0 = 0; // first page to be protected, set to a value between 0 and 37 for NTAG212
          response = nfc.transceive(new byte[] {
              (byte) 0xA2, // WRITE
              (byte) 37,   // page address
              response[0], // keep old value for byte 0
              response[1], // keep old value for byte 1
              response[2], // keep old value for byte 2
              (byte) (auth0 & 0x0ff)
          });
      }
      

    If you use MifareUltralight tag technology, instead of using the transceive method directly, you could also use the readPages and writePage methods:

    • The READ command

      byte[] response = nfc.transceive(new byte[] {
          (byte) 0x30,                  // READ
          (byte) (pageAddress & 0x0ff)  // page address
      });
      

      is equvalent to

      byte[] response = nfc.readPages(pageAddress);
      
    • The WRITE command

      byte[] data = { (byte)..., (byte)..., (byte)..., (byte)... };
      byte[] response = nfc.transceive(new byte[] {
          (byte) 0xA2,                  // WRITE
          (byte) (pageAddress & 0x0ff), // page address
          data[0], data[1], data[2], data[3]
      });
      

      is equvalent to

      nfc.writePage(pageAddress, data);