Code example for MifareClassic

Methods: authenticateSectorWithKeyA, authenticateSectorWithKeyB, getBlockCountInSector, getSectorCount, sectorToBlock

0
     * </ul> 
     * @see #authenticate(int, byte[], boolean) 
     */ 
    public int writeBlock(int sectorIndex, int blockIndex, byte[] data,
            byte[] key, boolean useAsKeyB) {
        if (mMFC.getSectorCount()-1 < sectorIndex) {
            return 1; 
        } 
        if (mMFC.getBlockCountInSector(sectorIndex)-1 < blockIndex) {
            return 2; 
        } 
        if (data.length != 16) {
            return 3; 
        } 
        if (!authenticate(sectorIndex, key, useAsKeyB)) {
            return 4; 
        } 
        // Write block. 
        int block = mMFC.sectorToBlock(sectorIndex) + blockIndex;
        try { 
            mMFC.writeBlock(block, data);
        } catch (IOException e) {
            Log.e(LOG_TAG, "Error while writing block to tag.", e);
            return -1; 
        } 
        return 0; 
    } 
 
    /** 
     * Build Key-Value Pairs in which keys represent the sector and 
     * values are one or both of the Mifare keys (A/B). 
     * The Mifare key information must be set before calling this method 
     * (use {@link #setKeyFile(java.io.File[])}). 
     * Also the mapping range must be specified before calling this method 
     * (use {@link #setMappingRange(int, int)}).<br /><br /> 
     * The mapping works like some kind of dictionary attack. 
     * All keys are checked against the next sector 
     * with both authentication methods (A/B). If at least one key was found 
     * for a sector, the map will be extended with an entry, containing the 
     * key(s) and the information for what sector the key(s) are. You can get 
     * this Key-Value Pairs by calling {@link #getKeyMap()}. A full 
     * key map can be gained by calling this method as often as there are 
     * sectors on the tag (See {@link #getSectorCount()}). If you call 
     * this method once more after a full key map was created, it resets the 
     * key map an starts all over. 
     * @return The sector that was checked at the moment. On error it returns 
     * "-1" and resets the key map to "null". 
     * @see #getKeyMap() 
     * @see #setKeyFile(java.io.File[]) 
     * @see #setMappingRange(int, int) 
     * @see #readAsMuchAsPossible(android.util.SparseArray) 
     */ 
    public int buildNextKeyMapPart() { 
        // Clear status and key map before new walk trough sectors. 
        boolean error = false;
        if (mKeys != null && mLastSector != -1) {
            if (mKeyMapStatus == mLastSector+1) {
                mKeyMapStatus = mFirstSector;
                mKeyMap = new SparseArray<byte[][]>();
            } 
 
            byte[][] keys = new byte[2][];
            boolean[] foundKeys = new boolean[] {false, false};
            try { 
                // Check next sector against all keys (lines) with 
                // authentication method A and B. 
                for (byte[] key : mKeys) {
                    if (!foundKeys[0] &&
                            mMFC.authenticateSectorWithKeyA(
                                    mKeyMapStatus, key)) {
                        keys[0] = key;
                        foundKeys[0] = true;
                    } 
                    if (!foundKeys[1] &&
                            mMFC.authenticateSectorWithKeyB(
                                    mKeyMapStatus, key)) {
                        keys[1] = key;
                        foundKeys[1] = true;
                    } 
                    if (foundKeys[0] && foundKeys[1]) {
                        // Both keys found. Continue with next sector. 
                        break; 
                    } 
                } 
                if (foundKeys[0] || foundKeys[1]) {
                    // At least one key found. Add key(s). 
                    mKeyMap.put(mKeyMapStatus, keys);
                } 
                mKeyMapStatus++;
            } catch (Exception e) {
                Log.d(LOG_TAG, "Error while building next key map part");
                error = true;
            } 
        } else { 
            error = true;
        } 
 
        if (error) {
            mKeyMapStatus = 0;
            mKeyMap = null;
            return -1; 
        } 
        return mKeyMapStatus - 1;
    } 
 
    /** 
     * Merge the result of two {@link #readSector(int, byte[], boolean)} 
     * calls on the same sector (with different keys or authentication methods). 
     * In this case merging means empty blocks will be overwritten with non 
     * empty ones and the keys will be added correctly to the sector trailer. 
     * The access conditions will be taken from the first (firstResult) 
     * parameter if it is not null. 
     * @param firstResult First 
     * {@link #readSector(int, byte[], boolean)} result. 
     * @param secondResult Second 
     * {@link #readSector(int, byte[], boolean)} result. 
     * @return Array (sector) as result of merging the given 
     * sectors. If a block is {@link #NO_DATA} it 
     * means that none of the given sectors contained data from this block. 
     * @see #readSector(int, byte[], boolean) 
     * @see #authenticate(int, byte[], boolean) 
     */ 
    public String[] mergeSectorData(String[] firstResult,
            String[] secondResult) {
        String[] ret = null;
        if (firstResult != null || secondResult != null) {
            if ((firstResult != null && secondResult != null)
                    && firstResult.length != secondResult.length) {
                return null; 
            } 
            int length  = (firstResult != null)
                    ? firstResult.length : secondResult.length;
            ArrayList<String> blocks = new ArrayList<String>();
            // Merge data blocks. 
            for (int i = 0; i < length -1 ; i++) {
                if (firstResult != null && firstResult[i] != null
                        && !firstResult[i].equals(NO_DATA)) {
                    blocks.add(firstResult[i]);
                } else if (secondResult != null && secondResult[i] != null
                        && !secondResult[i].equals(NO_DATA)) {
                    blocks.add(secondResult[i]);
                } else { 
                    // Non of the results got the data for the block. 
                    blocks.add(NO_DATA);
                } 
            } 
            ret = blocks.toArray(new String[blocks.size() + 1]);
            int last = length - 1;
            // Merge sector trailer. 
            if (firstResult != null && firstResult[last] != null
                    && !firstResult[last].equals(NO_DATA)) {
                // Take first for sector trailer. 
                ret[last] = firstResult[last];
                if (secondResult != null && secondResult[last] != null
                        && !secondResult[last].equals(NO_DATA)) {
                    // Merge key form second result to sector trailer. 
                    ret[last] = ret[last].substring(0, 20)
                            + secondResult[last].substring(20);
                } 
            } else if (secondResult != null && secondResult[last] != null
                    && !secondResult[last].equals(NO_DATA)) {
                // No first result. Take second result as sector trailer. 
                ret[last] = secondResult[last];
            } else { 
                // No sector trailer at all. 
                ret[last] = NO_DATA;
            } 
        } 
        return ret;
    } 
 
    /** 
     * This method checks if the present tag is writable with the provided keys 
     * on the given positions (sectors, blocks). This is done by authenticating 
     * with one of the keys followed by reading and interpreting 
     * ({@link Common#getOperationInfoForBlock(byte, byte, byte, 
     * de.syss.MifareClassicTool.Common.Operations, boolean, boolean)}) of the 
     * Access Conditions. 
     * @param pos A map of positions (key = sector, value = Array of blocks). 
     * For each of these positions you will get the write information 
     * (see return values). 
     * @param keyMap A key map a generated by 
     * {@link Activities.CreateKeyMapActivity}. 
     * @return A map within a map (all with type = Integer). 
     * The key of the outer map is the sector number and the value is another 
     * map with key = block number and value = write information. 
     * The write information indicates which key is needed to write to the 
     * present tag on the given position.<br /><br /> 
     * Write informations are:<br /> 
     * <ul> 
     * <li>0 - Never</li> 
     * <li>1 - Key A</li> 
     * <li>2 - Key B</li> 
     * <li>3 - Key A|B</li> 
     * <li>4 - Key A, but AC never</li> 
     * <li>5 - Key B, but AC never</li> 
     * <li>6 - Key B, but keys never</li> 
     * <li>-1 - Error</li> 
     * <li>Inner map == null - Whole sector is dead (IO Error)</li> 
     * </ul> 
     */ 
    public HashMap<Integer, HashMap<Integer, Integer>> isWritableOnPositions(
            HashMap<Integer, int[]> pos,
            SparseArray<byte[][]> keyMap) {
        HashMap<Integer, HashMap<Integer, Integer>> ret =
                new HashMap<Integer, HashMap<Integer,Integer>>();
        for (int i = 0; i < keyMap.size(); i++) {
            int sector = keyMap.keyAt(i);
            if (pos.containsKey(sector)) {
                byte[][] keys = keyMap.get(sector);
                byte[] ac = null;
                // Authenticate. 
                if (keys[0] != null) {
                    if (authenticate(sector, keys[0], false) == false) {
                        return null; 
                    } 
                } else if (keys[1] != null) {
                    if (authenticate(sector, keys[1], true) == false) {
                        return null; 
                    } 
                } else { 
                    return null; 
                } 
                // Read Mifare Access Conditions. 
                int acBlock = mMFC.sectorToBlock(sector)
                        + mMFC.getBlockCountInSector(sector) -1;
                try { 
                    ac = mMFC.readBlock(acBlock);
                } catch (IOException e) {
                    ret.put(sector, null);
                    continue; 
                } 
                ac = Arrays.copyOfRange(ac, 6, 9);
                byte[][] acMatrix = Common.acToACMatrix(ac);
                boolean isKeyBReadable = Common.isKeyBReadable(
                        acMatrix[0][3], acMatrix[1][3], acMatrix[2][3]);
 
                // Check all Blocks with data (!= null). 
                HashMap<Integer, Integer> blockWithWriteInfo =
                        new HashMap<Integer, Integer>();
                for (int block : pos.get(sector)) {
                    if ((block == 3 && sector <= 31)
                            || (block == 15 && sector >=32)) {
                        // Sector Trailer. 
                        // Are the Access Bits writable? 
                        int acValue = Common.getOperationInfoForBlock(
                                acMatrix[0][block],
                                acMatrix[1][block],
                                acMatrix[2][block],
                                Common.Operations.WriteAC, 
                                true, isKeyBReadable);
                        // Is key A writable? (If so, key B will be writable 
                        // with the same key.) 
                        int keyABValue = Common.getOperationInfoForBlock(
                                acMatrix[0][block],
                                acMatrix[1][block],
                                acMatrix[2][block],
                                Common.Operations.WriteKeyA, 
                                true, isKeyBReadable);
 
                        int result = keyABValue;
                        if (acValue == 0 && keyABValue != 0) {
                            // Write key found, but ac-bits are not writable. 
                            result += 3;
                        } else if (acValue == 2 && keyABValue == 0) {
                            // Access Bits are writable with key B, 
                            // but keys are not writable. 
                            result = 6;
                        } 
                        blockWithWriteInfo.put(block, result);
                    } else { 
                        // Data block. 
                        blockWithWriteInfo.put(
                                block, Common.getOperationInfoForBlock(
                                        acMatrix[0][block],
                                        acMatrix[1][block],
                                        acMatrix[2][block],
                                        Common.Operations.Write, 
                                        false, isKeyBReadable));
                    } 
 
                } 
                if (blockWithWriteInfo.size() > 0) {
                    ret.put(sector, blockWithWriteInfo);
                } 
            } 
        } 
        return ret;
    } 
 
    /** 
     * Set the key files for {@link #buildNextKeyMapPart()}. 
     * @param keyFiles One or more key files. 
     * These files are simple text files with one key 
     * per line. Empty lines and lines STARTING with "#" 
     * will not be interpreted. 
     */ 
    public void setKeyFile(File[] keyFiles) {
        mKeys = new HashSet<byte[]>();
        for (File file : keyFiles) {
            String[] lines = Common.readFileLineByLine(file, false);
            if (lines != null) {
                for (String line : lines) {
                    if (!line.equals("") && line.length() == 12
                            && line.matches("[0-9A-Fa-f]+")) {
                        mKeys.add(Common.hexStringToByteArray(line));
                    } 
                } 
            } 
        } 
    } 
 
    /** 
     * Set the mapping range for {@link #buildNextKeyMapPart()}. 
     * @param firstSector Index of the first sector of the key map. 
     * @param lastSector Index of the last sector of the key map. 
     * @return True if range parameters were correct. False otherwise. 
     */ 
    public boolean setMappingRange(int firstSector, int lastSector) {
        if (firstSector >= 0 && lastSector < mMFC.getSectorCount()
                && firstSector <= lastSector) {
            mFirstSector = firstSector;
            mLastSector = lastSector;
            // Init. status of buildNextKeyMapPart to create a new key map. 
            mKeyMapStatus = lastSector+1;
            return true; 
        } 
        return false; 
    } 
 
    /** 
     * Check if the Mifare Classic tag has the factory Mifare Classic Access 
     * Conditions (0xFF0780) and the standard key A 
     * (0xFFFFFFFFFFFF). 
     * @return True if tag has factory ACs and factory key A, False otherwise. 
     */ 
    public boolean isCleanTag() { 
        int blockIndex = 0;
        for (int i = 0; i < mMFC.getSectorCount(); i++) {
            // Authenticate. 
            if (!authenticate(i, MifareClassic.KEY_DEFAULT, false)) {
                return false; 
            } 
            // Read. 
            byte[] data = null;
            blockIndex += mMFC.getBlockCountInSector(i);
            try { 
                data = mMFC.readBlock(blockIndex-1);
            } catch (IOException e) {
                Log.d(LOG_TAG, "Error while reading block from tag.");
                return false; 
            } 
            // Extract Access Conditions. 
            String ac = Common.byte2HexString(data).substring(12, 18);
            // Check Access Conditions (= Factory settings). 
            if (!ac.equals("FF0780")) {
                return false; 
            } 
        } 
        return true; 
    } 
 
    /** 
     * Authenticate to given sector of the tag. 
     * @param sectorIndex The sector to authenticate to. 
     * @param key Key for the authentication. 
     * @param useAsKeyB If true, key will be treated as key B 
     * for authentication. 
     * @return True if authentication was successful. False otherwise. 
     */ 
    private boolean authenticate(int sectorIndex, byte[] key,
            boolean useAsKeyB) {
        try { 
            if (!useAsKeyB) {
                // Key A. 
                return mMFC.authenticateSectorWithKeyA(sectorIndex, key);
            } else { 
                // Key B. 
                return mMFC.authenticateSectorWithKeyB(sectorIndex, key);
            } 
        } catch (IOException e) {
            Log.d(LOG_TAG, "Error while authenticate with tag.");
        } 
        return false;