Search code examples
javarfidmifareapdupcsc

Authenticating Ultralight EV1 with PC/SC reader


I have a problem trying to authenticate an Ultralight EV1 card using a PC/SC reader (specifically an ACR1222L) in Java. I'm able to write and read on an unprotected tag using the corresponding APDUs for ISO 14443-3 tags. However, I can't find a way to run the PWD_AUTH command since it is not part of the 14443-3 standard (or any native command for that matter). Is it possible to run this command (or any native command for that matter)?

I have tried sending the following APDU {e0 00 00 24 07 1b ff ff ff ff 63 00} where 1b is the native command, ff ff ff ff is the password and 63 00 is the CRC_A of the command plus password. I have also tried without the CRC, switching the order of the parameters, etc., but so far I could not get it working.

I also tried wrapping the APDU (as described in https://stackoverflow.com/a/41729534/3613883). I got it working with a Desfire EV1 card but it doesn’t work with the ultralight EV1 (since it doesn’t support ISO7816-4 obviously).

So, is there a way to authenticate a Ultralight EV1 card using a PC/SC reader?


Solution

  • First of all, MIFARE Ultralight EV1 does not speak APDUs. Instead it uses commands based directly on the framing defined in ISO/IEC 14443-3. Since ISO/IEC 14443-3 only defines the framing and the anti-collision/enumeration commands, any protocol on top of that (e.g. the MIFARE Ultralight/NTAG command sets) is proprietary.

    The correct command for password authentication using the password FF FF FF FF would be:

    byte[] tagCommand = new byte[] { (byte)0x1B, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF };
    

    Note that the CRC will typically be handled by the contactless frontend chip so you don't need to apped it manually.

    With the ACR1222L, there are multiple different ways to exchange such proprietary commands:

    1. You can use PC_to_RDR_Escape (note that that's only available if you installed the original ACR driver package for the reader). Assuming that you are using the Java Smartcard IO API, you would do that using the method Card.transmitControlCommand():

      byte[] response = card.transmitControlCommand(SCARD_CTL_CODE(3500), command);
      

      The definition of the method SCARD_CTL_CODE can be found in this post.

      The command needs to be a byte array that contains an APDU header for the pseudo-APDU that passes raw commands to the contactless frontend chip and the actual command for the contactless frontend chip. Since the ACR1222L is based on an NXP PN532(?), the command for the contactless frontend chip would be the InDataExchange command (see the user manual):

      byte[] interfaceCommandHeader = new byte[] { (byte)0xD4, (byte)0x40, (byte)0x01 };
      byte[] interfaceCommand = Arrays.copyOf(interfaceCommandHeader, interfaceCommandHeader.length + tagCommand.length);
      System.arraycopy(tagCommand, 0, interfaceCommand, interfaceCommandHeader.length, tagCommand.length);
      

      Depending on how the reader actually activates the card, you might need to use the InCommunicateThru command instead of InDataExchange:

      byte[] interfaceCommandHeader = new byte[] { (byte)0xD4, (byte)0x42 };
      byte[] interfaceCommand = Arrays.copyOf(interfaceCommandHeader, interfaceCommandHeader.length + tagCommand.length);
      System.arraycopy(tagCommand, 0, interfaceCommand, interfaceCommandHeader.length, tagCommand.length);
      

      The pseudo APDU header can be added by:

      byte[] commandHeader = new byte[] { (byte)0xE0, (byte)0x00, (byte)0x00, (byte)0x24, (byte)0x00 };
      byte[] command = Arrays.copyOf(commandHeader, commandHeader.length + interfaceCommand.length);
      System.arraycopy(interfaceCommand, 0, command, commandHeader.length, interfaceCommand.length);
      command[4] = (byte)(interfaceCommand.length & 0x0FF);  // update Lc field
      
    2. Another option is to send commands directly using PC_to_RDR_XfrBlock. This maps to CardChannel.transmit() in the Java Smartcard IO API:

      ResponseAPDU responseApdu = cardChannel.transmit(commandAPDU);
      

      The manual of your reader is not quite clear if the same pseudo APDU header can be used over that interface. However, if you look into appendix H, you'll find a different header from wrapping into a pseudo APDU (the ACR122U legacy mode). So you could use the following:

      CommandAPDU commandAPDU = new CommandAPDU(0xFF, 0x00, 0x00, 0x00, interfaceCommand);
      

      Note that, again, you have to wrap the tag command into the InDataExchange command for the contactless frontend chip.