Search code examples
c#xamarinnfcmifare

Mifare Classic Authentication failure on some devices until the tag has been read with the Mifare Classic Tool


I'm trying to read user data from sector 0 block 1 of Mifare Classic cards in a Xamarin Forms Android app. On most devices it works perfectly every time, but on a Unitech EA510 the authentication consistently fails UNLESS I first read the card using the Mifare Classic Tool (https://play.google.com/store/apps/details?id=de.syss.MifareClassicTool). As soon as I've detected any tag with the Mifare Classic Tool my code works perfectly every time. I've checked the Mifare Classic Tool source code to see what else it's doing and borrowed the patchTag logic (it didn't help!).

  1. Scan tag with the app first time: Mifare Classic tag authentication failure: 8250A9B9
  2. Scan tag using the Mifare Classic Tool
  3. Re-scan the tag with the app: Mifare Classic tag found: 8250A9B9 10240472
  4. Scan another the tag with the app: Mifare Classic tag found: 8250A9C7 10395802
public void OnTagDiscovered(Android.Nfc.Tag tag)
{
    string tagId = string.Empty;
    string[] techList = tag.GetTechList();
    string sUid = string.Empty;
    if (Array.IndexOf(techList, "android.nfc.tech.MifareClassic") > -1) {
        MifareClassic mfc = null;
        try {
            sUid = BitConverter.ToString(tag.GetId()).Replace("-", "");
            mfc = MifareClassic.Get(tag);
            mfc.Connect();
            bool bAuthenticated = false;
            if (mfc.AuthenticateSectorWithKeyA(0, MifareClassic.KeyDefault.ToArray()))
                bAuthenticated = true;
            else if (mfc.AuthenticateSectorWithKeyB(0, MifareClassic.KeyDefault.ToArray()))
                bAuthenticated = true;
            else if (mfc.AuthenticateSectorWithKeyA(0, MifareClassic.KeyMifareApplicationDirectory.ToArray()))
                bAuthenticated = true;
            else if (mfc.AuthenticateSectorWithKeyB(0, MifareClassic.KeyMifareApplicationDirectory.ToArray()))
                bAuthenticated = true;
            else if (mfc.AuthenticateSectorWithKeyA(0, MifareClassic.KeyNfcForum.ToArray()))
                bAuthenticated = true;
            else if (mfc.AuthenticateSectorWithKeyB(0, MifareClassic.KeyNfcForum.ToArray()))
                bAuthenticated = true;
            if (bAuthenticated) {
                int blockOffset = mfc.SectorToBlock(0);
                byte[] block = mfc.ReadBlock(blockOffset + 1); // Sector 0/block 0 is the UID so go straight to block 1 for the payload
                tagId = System.Text.Encoding.ASCII.GetString(block);
                if (tagId.Length == 1 && tagId[0] == 0x04)
                    // Ignore if ReadBlock returns a single byte 0x04, which appears to happen if there's a delay between the tag discovery and ReadBlock (e.g. running in the debugger) and we don't want to treat it as a conventional tag by acciden
                    tagId = string.Empty;
                else if (tagId.Length > 0 && tagId.Contains('\0')) 
                    tagId = tagId.Substring(0, tagId.IndexOf('\0'));
            } else {
                Console.WriteLine($"Mifare Classic tag authentication failure: {sUid}");
            }
        }
        catch (Exception ex) {
            Console.WriteLine($"Mifare Classic tag read exception: {ex.Message}");
        }
        finally {
            if (mfc != null && mfc.IsConnected)
                mfc.Close();
            if (tagId != string.Empty)
                Console.WriteLine($"Mifare Classic tag found: {sUid} {tagId}");
        }
    }
    if (tagId != string.Empty)
        Do stuff
}


Solution

  • That looks a super hacky way of trying to enable Mifare Classic support when the phone does not support it and it won't fix all devices.

    But your problem is the hack relies on modifying the Tag Object that your App has created from the Intent when you are using the old API enableForegrounDispatch

    This old API has some other issues, where as you are using the newer and better enableReaderMode API (because you are using the method OnTagDiscovered) which gets directly give the Tag Object.

    I've not been through the Android code to work all this out but my guess is that it is some form of shared memory object, it is though defined as a "oneway" callback.

    Thus as your App process is not the creator of the Tag object you don't have the ability to modify it.

    So this hack will probably only work if you are using the old enableForegrounDispatch where you de-Parcel the Parcelled object in your own App ( Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG); ) and have full access to Tag object because your process created it.

    The reason why your code works after using Mifare Classic Tool App is that once the Tag Hardware has been authenticated, it stays in the "authenticated" state until it is removed from the RF field and it looses power.