FileDocCategorySizeDatePackage
MifareClassic.javaAPI DocAndroid 5.1 API24225Thu Mar 12 22:22:10 GMT 2015android.nfc.tech

MifareClassic

public final class MifareClassic extends BasicTagTechnology
Provides access to MIFARE Classic properties and I/O operations on a {@link Tag}.

Acquire a {@link MifareClassic} object using {@link #get}.

MIFARE Classic is also known as MIFARE Standard.

MIFARE Classic tags are divided into sectors, and each sector is sub-divided into blocks. Block size is always 16 bytes ({@link #BLOCK_SIZE}. Sector size varies.

  • MIFARE Classic Mini are 320 bytes ({@link #SIZE_MINI}), with 5 sectors each of 4 blocks.
  • MIFARE Classic 1k are 1024 bytes ({@link #SIZE_1K}), with 16 sectors each of 4 blocks.
  • MIFARE Classic 2k are 2048 bytes ({@link #SIZE_2K}), with 32 sectors each of 4 blocks.
  • MIFARE Classic 4k} are 4096 bytes ({@link #SIZE_4K}). The first 32 sectors contain 4 blocks and the last 8 sectors contain 16 blocks.

MIFARE Classic tags require authentication on a per-sector basis before any other I/O operations on that sector can be performed. There are two keys per sector, and ACL bits determine what I/O operations are allowed on that sector after authenticating with a key. {@see #authenticateSectorWithKeyA} and {@see #authenticateSectorWithKeyB}.

Three well-known authentication keys are defined in this class: {@link #KEY_DEFAULT}, {@link #KEY_MIFARE_APPLICATION_DIRECTORY}, {@link #KEY_NFC_FORUM}.

  • {@link #KEY_DEFAULT} is the default factory key for MIFARE Classic.
  • {@link #KEY_MIFARE_APPLICATION_DIRECTORY} is the well-known key for MIFARE Classic cards that have been formatted according to the MIFARE Application Directory (MAD) specification.
  • {@link #KEY_NFC_FORUM} is the well-known key for MIFARE Classic cards that have been formatted according to the NXP specification for NDEF on MIFARE Classic.

    Implementation of this class on a Android NFC device is optional. If it is not implemented, then {@link MifareClassic} will never be enumerated in {@link Tag#getTechList}. If it is enumerated, then all {@link MifareClassic} I/O operations will be supported, and {@link Ndef#MIFARE_CLASSIC} NDEF tags will also be supported. In either case, {@link NfcA} will also be enumerated on the tag, because all MIFARE Classic tags are also {@link NfcA}.

    Note: Methods that perform I/O operations require the {@link android.Manifest.permission#NFC} permission.

Fields Summary
private static final String
TAG
public static final byte[]
KEY_DEFAULT
The default factory key.
public static final byte[]
KEY_MIFARE_APPLICATION_DIRECTORY
The well-known key for tags formatted according to the MIFARE Application Directory (MAD) specification.
public static final byte[]
KEY_NFC_FORUM
The well-known key for tags formatted according to the NDEF on MIFARE Classic specification.
public static final int
TYPE_UNKNOWN
A MIFARE Classic compatible card of unknown type
public static final int
TYPE_CLASSIC
A MIFARE Classic tag
public static final int
TYPE_PLUS
A MIFARE Plus tag
public static final int
TYPE_PRO
A MIFARE Pro tag
public static final int
SIZE_1K
Tag contains 16 sectors, each with 4 blocks.
public static final int
SIZE_2K
Tag contains 32 sectors, each with 4 blocks.
public static final int
SIZE_4K
Tag contains 40 sectors. The first 32 sectors contain 4 blocks and the last 8 sectors contain 16 blocks.
public static final int
SIZE_MINI
Tag contains 5 sectors, each with 4 blocks.
public static final int
BLOCK_SIZE
Size of a MIFARE Classic block (in bytes)
private static final int
MAX_BLOCK_COUNT
private static final int
MAX_SECTOR_COUNT
private boolean
mIsEmulated
private int
mType
private int
mSize
Constructors Summary
public MifareClassic(android.nfc.Tag tag)

hide

        super(tag, TagTechnology.MIFARE_CLASSIC);

        NfcA a = NfcA.get(tag);  // MIFARE Classic is always based on NFC a

        mIsEmulated = false;

        switch (a.getSak()) {
        case 0x01:
        case 0x08:
            mType = TYPE_CLASSIC;
            mSize = SIZE_1K;
            break;
        case 0x09:
            mType = TYPE_CLASSIC;
            mSize = SIZE_MINI;
            break;
        case 0x10:
            mType = TYPE_PLUS;
            mSize = SIZE_2K;
            // SecLevel = SL2
            break;
        case 0x11:
            mType = TYPE_PLUS;
            mSize = SIZE_4K;
            // Seclevel = SL2
            break;
        case 0x18:
            mType = TYPE_CLASSIC;
            mSize = SIZE_4K;
            break;
        case 0x28:
            mType = TYPE_CLASSIC;
            mSize = SIZE_1K;
            mIsEmulated = true;
            break;
        case 0x38:
            mType = TYPE_CLASSIC;
            mSize = SIZE_4K;
            mIsEmulated = true;
            break;
        case 0x88:
            mType = TYPE_CLASSIC;
            mSize = SIZE_1K;
            // NXP-tag: false
            break;
        case 0x98:
        case 0xB8:
            mType = TYPE_PRO;
            mSize = SIZE_4K;
            break;
        default:
            // Stack incorrectly reported a MifareClassic. We cannot handle this
            // gracefully - we have no idea of the memory layout. Bail.
            throw new RuntimeException(
                    "Tag incorrectly enumerated as MIFARE Classic, SAK = " + a.getSak());
        }
    
Methods Summary
private booleanauthenticate(int sector, byte[] key, boolean keyA)

        validateSector(sector);
        checkConnected();

        byte[] cmd = new byte[12];

        // First byte is the command
        if (keyA) {
            cmd[0] = 0x60; // phHal_eMifareAuthentA
        } else {
            cmd[0] = 0x61; // phHal_eMifareAuthentB
        }

        // Second byte is block address
        // Authenticate command takes a block address. Authenticating a block
        // of a sector will authenticate the entire sector.
        cmd[1] = (byte) sectorToBlock(sector);

        // Next 4 bytes are last 4 bytes of UID
        byte[] uid = getTag().getId();
        System.arraycopy(uid, uid.length - 4, cmd, 2, 4);

        // Next 6 bytes are key
        System.arraycopy(key, 0, cmd, 6, 6);

        try {
            if (transceive(cmd, false) != null) {
                return true;
            }
        } catch (TagLostException e) {
            throw e;
        } catch (IOException e) {
            // No need to deal with, will return false anyway
        }
        return false;
    
public booleanauthenticateSectorWithKeyA(int sectorIndex, byte[] key)
Authenticate a sector with key A.

Successful authentication of a sector with key A enables other I/O operations on that sector. The set of operations granted by key A key depends on the ACL bits set in that sector. For more information see the MIFARE Classic specification on {@see http://www.nxp.com}.

A failed authentication attempt causes an implicit reconnection to the tag, so authentication to other sectors will be lost.

This is an I/O operation and will block until complete. It must not be called from the main application thread. A blocked call will be canceled with {@link IOException} if {@link #close} is called from another thread.

Requires the {@link android.Manifest.permission#NFC} permission.

param
sectorIndex index of sector to authenticate, starting from 0
param
key 6-byte authentication key
return
true on success, false on authentication failure
throws
TagLostException if the tag leaves the field
throws
IOException if there is an I/O failure, or the operation is canceled

        return authenticate(sectorIndex, key, true);
    
public booleanauthenticateSectorWithKeyB(int sectorIndex, byte[] key)
Authenticate a sector with key B.

Successful authentication of a sector with key B enables other I/O operations on that sector. The set of operations granted by key B depends on the ACL bits set in that sector. For more information see the MIFARE Classic specification on {@see http://www.nxp.com}.

A failed authentication attempt causes an implicit reconnection to the tag, so authentication to other sectors will be lost.

This is an I/O operation and will block until complete. It must not be called from the main application thread. A blocked call will be canceled with {@link IOException} if {@link #close} is called from another thread.

Requires the {@link android.Manifest.permission#NFC} permission.

param
sectorIndex index of sector to authenticate, starting from 0
param
key 6-byte authentication key
return
true on success, false on authentication failure
throws
TagLostException if the tag leaves the field
throws
IOException if there is an I/O failure, or the operation is canceled

        return authenticate(sectorIndex, key, false);
    
public intblockToSector(int blockIndex)
Return the sector that contains a given block.

Does not cause any RF activity and does not block.

param
blockIndex index of block to lookup, starting from 0
return
sector index that contains the block

        validateBlock(blockIndex);

        if (blockIndex < 32 * 4) {
            return blockIndex / 4;
        } else {
            return 32 + (blockIndex - 32 * 4) / 16;
        }
    
public voiddecrement(int blockIndex, int value)
Decrement a value block, storing the result in the temporary block on the tag.

This is an I/O operation and will block until complete. It must not be called from the main application thread. A blocked call will be canceled with {@link IOException} if {@link #close} is called from another thread.

Requires the {@link android.Manifest.permission#NFC} permission.

param
blockIndex index of block to decrement, starting from 0
param
value non-negative to decrement by
throws
TagLostException if the tag leaves the field
throws
IOException if there is an I/O failure, or the operation is canceled

        validateBlock(blockIndex);
        validateValueOperand(value);
        checkConnected();

        ByteBuffer cmd = ByteBuffer.allocate(6);
        cmd.order(ByteOrder.LITTLE_ENDIAN);
        cmd.put( (byte) 0xC0 );
        cmd.put( (byte) blockIndex );
        cmd.putInt(value);

        transceive(cmd.array(), false);
    
public static android.nfc.tech.MifareClassicget(android.nfc.Tag tag)
Get an instance of {@link MifareClassic} for the given tag.

Does not cause any RF activity and does not block.

Returns null if {@link MifareClassic} was not enumerated in {@link Tag#getTechList}. This indicates the tag is not MIFARE Classic compatible, or this Android device does not support MIFARE Classic.

param
tag an MIFARE Classic compatible tag
return
MIFARE Classic object


                                                                     
         
        if (!tag.hasTech(TagTechnology.MIFARE_CLASSIC)) return null;
        try {
            return new MifareClassic(tag);
        } catch (RemoteException e) {
            return null;
        }
    
public intgetBlockCount()
Return the total number of MIFARE Classic blocks.

Does not cause any RF activity and does not block.

return
total number of blocks

        return mSize / BLOCK_SIZE;
    
public intgetBlockCountInSector(int sectorIndex)
Return the number of blocks in the given sector.

Does not cause any RF activity and does not block.

param
sectorIndex index of sector, starting from 0
return
number of blocks in the sector

        validateSector(sectorIndex);

        if (sectorIndex < 32) {
            return 4;
        } else {
            return 16;
        }
    
public intgetMaxTransceiveLength()
Return the maximum number of bytes that can be sent with {@link #transceive}.

return
the maximum number of bytes that can be sent with {@link #transceive}.

        return getMaxTransceiveLengthInternal();
    
public intgetSectorCount()
Return the number of MIFARE Classic sectors.

Does not cause any RF activity and does not block.

return
number of sectors

        switch (mSize) {
        case SIZE_1K:
            return 16;
        case SIZE_2K:
            return 32;
        case SIZE_4K:
            return 40;
        case SIZE_MINI:
            return 5;
        default:
            return 0;
        }
    
public intgetSize()
Return the size of the tag in bytes

One of {@link #SIZE_MINI}, {@link #SIZE_1K}, {@link #SIZE_2K}, {@link #SIZE_4K}. These constants are equal to their respective size in bytes.

Does not cause any RF activity and does not block.

return
size in bytes

        return mSize;
    
public intgetTimeout()
Get the current {@link #transceive} timeout in milliseconds.

Requires the {@link android.Manifest.permission#NFC} permission.

return
timeout value in milliseconds

        try {
            return mTag.getTagService().getTimeout(TagTechnology.MIFARE_CLASSIC);
        } catch (RemoteException e) {
            Log.e(TAG, "NFC service dead", e);
            return 0;
        }
    
public intgetType()
Return the type of this MIFARE Classic compatible tag.

One of {@link #TYPE_UNKNOWN}, {@link #TYPE_CLASSIC}, {@link #TYPE_PLUS} or {@link #TYPE_PRO}.

Does not cause any RF activity and does not block.

return
type

        return mType;
    
public voidincrement(int blockIndex, int value)
Increment a value block, storing the result in the temporary block on the tag.

This is an I/O operation and will block until complete. It must not be called from the main application thread. A blocked call will be canceled with {@link IOException} if {@link #close} is called from another thread.

Requires the {@link android.Manifest.permission#NFC} permission.

param
blockIndex index of block to increment, starting from 0
param
value non-negative to increment by
throws
TagLostException if the tag leaves the field
throws
IOException if there is an I/O failure, or the operation is canceled

        validateBlock(blockIndex);
        validateValueOperand(value);
        checkConnected();

        ByteBuffer cmd = ByteBuffer.allocate(6);
        cmd.order(ByteOrder.LITTLE_ENDIAN);
        cmd.put( (byte) 0xC1 );
        cmd.put( (byte) blockIndex );
        cmd.putInt(value);

        transceive(cmd.array(), false);
    
public booleanisEmulated()
Return true if the tag is emulated, determined at discovery time. These are actually smart-cards that emulate a MIFARE Classic interface. They can be treated identically to a MIFARE Classic tag.

hide

        return mIsEmulated;
    
public byte[]readBlock(int blockIndex)
Read 16-byte block.

This is an I/O operation and will block until complete. It must not be called from the main application thread. A blocked call will be canceled with {@link IOException} if {@link #close} is called from another thread.

Requires the {@link android.Manifest.permission#NFC} permission.

param
blockIndex index of block to read, starting from 0
return
16 byte block
throws
TagLostException if the tag leaves the field
throws
IOException if there is an I/O failure, or the operation is canceled

        validateBlock(blockIndex);
        checkConnected();

        byte[] cmd = { 0x30, (byte) blockIndex };
        return transceive(cmd, false);
    
public voidrestore(int blockIndex)
Copy from a value block to the temporary block.

This is an I/O operation and will block until complete. It must not be called from the main application thread. A blocked call will be canceled with {@link IOException} if {@link #close} is called from another thread.

Requires the {@link android.Manifest.permission#NFC} permission.

param
blockIndex index of block to copy from
throws
TagLostException if the tag leaves the field
throws
IOException if there is an I/O failure, or the operation is canceled

        validateBlock(blockIndex);
        checkConnected();

        byte[] cmd = { (byte) 0xC2, (byte) blockIndex };

        transceive(cmd, false);
    
public intsectorToBlock(int sectorIndex)
Return the first block of a given sector.

Does not cause any RF activity and does not block.

param
sectorIndex index of sector to lookup, starting from 0
return
block index of first block in sector

        if (sectorIndex < 32) {
            return sectorIndex * 4;
        } else {
            return 32 * 4 + (sectorIndex - 32) * 16;
        }
    
public voidsetTimeout(int timeout)
Set the {@link #transceive} timeout in milliseconds.

The timeout only applies to {@link #transceive} on this object, and is reset to a default value when {@link #close} is called.

Setting a longer timeout may be useful when performing transactions that require a long processing time on the tag such as key generation.

Requires the {@link android.Manifest.permission#NFC} permission.

param
timeout timeout value in milliseconds

        try {
            int err = mTag.getTagService().setTimeout(TagTechnology.MIFARE_CLASSIC, timeout);
            if (err != ErrorCodes.SUCCESS) {
                throw new IllegalArgumentException("The supplied timeout is not valid");
            }
        } catch (RemoteException e) {
            Log.e(TAG, "NFC service dead", e);
        }
    
public byte[]transceive(byte[] data)
Send raw NfcA data to a tag and receive the response.

This is equivalent to connecting to this tag via {@link NfcA} and calling {@link NfcA#transceive}. Note that all MIFARE Classic tags are based on {@link NfcA} technology.

Use {@link #getMaxTransceiveLength} to retrieve the maximum number of bytes that can be sent with {@link #transceive}.

This is an I/O operation and will block until complete. It must not be called from the main application thread. A blocked call will be canceled with {@link IOException} if {@link #close} is called from another thread.

Requires the {@link android.Manifest.permission#NFC} permission.

see
NfcA#transceive

        return transceive(data, true);
    
public voidtransfer(int blockIndex)
Copy from the temporary block to a value block.

This is an I/O operation and will block until complete. It must not be called from the main application thread. A blocked call will be canceled with {@link IOException} if {@link #close} is called from another thread.

Requires the {@link android.Manifest.permission#NFC} permission.

param
blockIndex index of block to copy to
throws
TagLostException if the tag leaves the field
throws
IOException if there is an I/O failure, or the operation is canceled

        validateBlock(blockIndex);
        checkConnected();

        byte[] cmd = { (byte) 0xB0, (byte) blockIndex };

        transceive(cmd, false);
    
private static voidvalidateBlock(int block)

        // Just looking for obvious out of bounds...
        if (block < 0 || block >= MAX_BLOCK_COUNT) {
            throw new IndexOutOfBoundsException("block out of bounds: " + block);
        }
    
private static voidvalidateSector(int sector)

        // Do not be too strict on upper bounds checking, since some cards
        // have more addressable memory than they report. For example,
        // MIFARE Plus 2k cards will appear as MIFARE Classic 1k cards when in
        // MIFARE Classic compatibility mode.
        // Note that issuing a command to an out-of-bounds block is safe - the
        // tag should report error causing IOException. This validation is a
        // helper to guard against obvious programming mistakes.
        if (sector < 0 || sector >= MAX_SECTOR_COUNT) {
            throw new IndexOutOfBoundsException("sector out of bounds: " + sector);
        }
    
private static voidvalidateValueOperand(int value)

        if (value < 0) {
            throw new IllegalArgumentException("value operand negative");
        }
    
public voidwriteBlock(int blockIndex, byte[] data)
Write 16-byte block.

This is an I/O operation and will block until complete. It must not be called from the main application thread. A blocked call will be canceled with {@link IOException} if {@link #close} is called from another thread.

Requires the {@link android.Manifest.permission#NFC} permission.

param
blockIndex index of block to write, starting from 0
param
data 16 bytes of data to write
throws
TagLostException if the tag leaves the field
throws
IOException if there is an I/O failure, or the operation is canceled

        validateBlock(blockIndex);
        checkConnected();
        if (data.length != 16) {
            throw new IllegalArgumentException("must write 16-bytes");
        }

        byte[] cmd = new byte[data.length + 2];
        cmd[0] = (byte) 0xA0; // MF write command
        cmd[1] = (byte) blockIndex;
        System.arraycopy(data, 0, cmd, 2, data.length);

        transceive(cmd, false);