Code example for MifareClassic

Methods: authenticateSectorWithKeyA, authenticateSectorWithKeyB, getBlockCountInSector, getSectorCount, sectorToBlock

0
	 *         <li>-1 - Error while writing to tag.</li> 
	 *         </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(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(File[]) 
	 * @see #setMappingRange(int, int) 
	 * @see #readAsMuchAsPossible(SparseArray) 
	 */ 
	public int buildNextKeyMapPart() { 
		// Clear status and key map before new walk through sectors. 
		boolean error = false;
		if (mKeysWithOrder != 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 : mKeysWithOrder) {
					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);
					// Key reuse is very likely, so try these first 
					// for the next sector. 
					if (foundKeys[0]) {
						mKeysWithOrder.remove(keys[0]);
						mKeysWithOrder.add(0, keys[0]);
					} 
					if (foundKeys[1]) {
						mKeysWithOrder.remove(keys[1]);
						mKeysWithOrder.add(0, keys[1]);
					} 
				} 
				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][3], acMatrix[1][3], acMatrix[2][3], 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][3], acMatrix[1][3], acMatrix[2][3], 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. 
						int acBitsForBlock = block;
						// Handle Mifare Classic 4k Tags. 
						if (sector >= 32) {
							if (block >= 0 && block <= 4) {
								acBitsForBlock = 0;
							} else if (block >= 5 && block <= 9) {
								acBitsForBlock = 1;
							} else if (block >= 10 && block <= 14) {
								acBitsForBlock = 2;
							} 
						} 
						blockWithWriteInfo.put(block, Common.getOperationInfoForBlock(acMatrix[0][acBitsForBlock], acMatrix[1][acBitsForBlock],
								acMatrix[2][acBitsForBlock], Common.Operations.Write, false, isKeyBReadable));
					} 
 
				} 
				if (blockWithWriteInfo.size() > 0) {
					ret.put(sector, blockWithWriteInfo);
				} 
			} 
		} 
		return ret;
	} 
 
	/** 
	 * Set the key files for {@link #buildNextKeyMapPart()}. Key duplicates from the key file will be removed. 
	 *  
	 * @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 */) { 
 
		HashSet<byte[]> keys = new HashSet<byte[]>();
		keys.add(Common.hexStringToByteArray("FFFFFFFFFFFF"));
		keys.add(Common.hexStringToByteArray("A0A1A2A3A4A5"));
		keys.add(Common.hexStringToByteArray("D3F7D3F7D3F7"));
		keys.add(Common.hexStringToByteArray("000000000000"));
		keys.add(Common.hexStringToByteArray("A0B0C0D0E0F0"));
		keys.add(Common.hexStringToByteArray("A1B1C1D1E1F1"));
		keys.add(Common.hexStringToByteArray("B0B1B2B3B4B5"));
		keys.add(Common.hexStringToByteArray("4D3A99C351DD"));
		keys.add(Common.hexStringToByteArray("1A982C7E459A"));
		keys.add(Common.hexStringToByteArray("AABBCCDDEEFF"));
 
		mKeysWithOrder = new ArrayList<byte[]>(keys);
 
		/* 
		 * HashSet<byte[]> keys = 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]+")) { 
		 * keys.add(Common.hexStringToByteArray(line)); } } } } if (keys.size() > 0) { mKeysWithOrder = new ArrayList<byte[]>(keys); } 
		 */ 
	} 
 
	/** 
	 * 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; 
	} 
 
	/** 
	 * 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;