MifareClassic.java revision e45083b11bef915f713379fb4106dd2ebd897d03
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.nfc.tech; 18 19import android.nfc.Tag; 20import android.nfc.TagLostException; 21import android.os.RemoteException; 22 23import java.io.IOException; 24 25/** 26 * Technology class representing MIFARE Classic tags (also known as MIFARE Standard). 27 * 28 * <p>Support for this technology type is optional. If the NFC stack doesn't support this technology 29 * MIFARE Classic tags will still be scanned, but will only show the NfcA technology. 30 * 31 * <p>MIFARE Classic tags have sectors that each contain blocks. The block size is constant at 32 * 16 bytes, but the number of sectors and the sector size varies by product. MIFARE has encryption 33 * built in and each sector has two keys associated with it, as well as ACLs to determine what 34 * level acess each key grants. Before operating on a sector you must call either 35 * {@link #authenticateSector(int, byte[], boolean)} or 36 * {@link #authenticateBlock(int, byte[], boolean)} to gain authorize your request. 37 */ 38public final class MifareClassic extends BasicTagTechnology { 39 /** 40 * The well-known default MIFARE read key. All keys are set to this at the factory. 41 * Using this key will effectively make the payload in the sector public. 42 */ 43 public static final byte[] KEY_DEFAULT = 44 {(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF,(byte)0xFF}; 45 /** 46 * The well-known, default MIFARE Application Directory read key. 47 */ 48 public static final byte[] KEY_MIFARE_APPLICATION_DIRECTORY = 49 {(byte)0xA0,(byte)0xA1,(byte)0xA2,(byte)0xA3,(byte)0xA4,(byte)0xA5}; 50 /** 51 * The well-known, default read key for NDEF data on a MIFARE Classic 52 */ 53 public static final byte[] KEY_NFC_FORUM = 54 {(byte)0xD3,(byte)0xF7,(byte)0xD3,(byte)0xF7,(byte)0xD3,(byte)0xF7}; 55 56 /** A MIFARE Classic tag */ 57 public static final int TYPE_CLASSIC = 0; 58 /** A MIFARE Plus tag */ 59 public static final int TYPE_PLUS = 1; 60 /** A MIFARE Pro tag */ 61 public static final int TYPE_PRO = 2; 62 /** A Mifare Classic compatible card that does not match the other types */ 63 public static final int TYPE_OTHER = -1; 64 65 /** The tag contains 16 sectors, each holding 4 blocks. */ 66 public static final int SIZE_1K = 1024; 67 /** The tag contains 32 sectors, each holding 4 blocks. */ 68 public static final int SIZE_2K = 2048; 69 /** 70 * The tag contains 40 sectors. The first 32 sectors contain 4 blocks and the last 8 sectors 71 * contain 16 blocks. 72 */ 73 public static final int SIZE_4K = 4096; 74 /** The tag contains 5 sectors, each holding 4 blocks. */ 75 public static final int SIZE_MINI = 320; 76 77 /** Size of a Mifare Classic block (in bytes) */ 78 public static final int BLOCK_SIZE = 16; 79 80 private static final int MAX_BLOCK_COUNT = 256; 81 private static final int MAX_SECTOR_COUNT = 40; 82 83 private boolean mIsEmulated; 84 private int mType; 85 private int mSize; 86 87 /** 88 * Returns an instance of this tech for the given tag. If the tag doesn't support 89 * this tech type null is returned. 90 * 91 * @param tag The tag to get the tech from 92 */ 93 public static MifareClassic get(Tag tag) { 94 if (!tag.hasTech(TagTechnology.MIFARE_CLASSIC)) return null; 95 try { 96 return new MifareClassic(tag); 97 } catch (RemoteException e) { 98 return null; 99 } 100 } 101 102 /** @hide */ 103 public MifareClassic(Tag tag) throws RemoteException { 104 super(tag, TagTechnology.MIFARE_CLASSIC); 105 106 NfcA a = NfcA.get(tag); // Mifare Classic is always based on NFC a 107 108 mIsEmulated = false; 109 110 switch (a.getSak()) { 111 case 0x08: 112 mType = TYPE_CLASSIC; 113 mSize = SIZE_1K; 114 break; 115 case 0x09: 116 mType = TYPE_CLASSIC; 117 mSize = SIZE_MINI; 118 break; 119 case 0x10: 120 mType = TYPE_PLUS; 121 mSize = SIZE_2K; 122 // SecLevel = SL2 123 break; 124 case 0x11: 125 mType = TYPE_PLUS; 126 mSize = SIZE_4K; 127 // Seclevel = SL2 128 break; 129 case 0x18: 130 mType = TYPE_CLASSIC; 131 mSize = SIZE_4K; 132 break; 133 case 0x28: 134 mType = TYPE_CLASSIC; 135 mSize = SIZE_1K; 136 mIsEmulated = true; 137 break; 138 case 0x38: 139 mType = TYPE_CLASSIC; 140 mSize = SIZE_4K; 141 mIsEmulated = true; 142 break; 143 case 0x88: 144 mType = TYPE_CLASSIC; 145 mSize = SIZE_1K; 146 // NXP-tag: false 147 break; 148 case 0x98: 149 case 0xB8: 150 mType = TYPE_PRO; 151 mSize = SIZE_4K; 152 break; 153 default: 154 // Stack incorrectly reported a MifareClassic. We cannot handle this 155 // gracefully - we have no idea of the memory layout. Bail. 156 throw new RuntimeException( 157 "Tag incorrectly enumerated as Mifare Classic, SAK = " + a.getSak()); 158 } 159 } 160 161 /** Returns the type of the tag, determined at discovery time */ 162 public int getType() { 163 return mType; 164 } 165 166 /** Returns the size of the tag in bytes, determined at discovery time */ 167 public int getSize() { 168 return mSize; 169 } 170 171 /** Returns true if the tag is emulated, determined at discovery time. 172 * These are actually smart-cards that emulate a Mifare Classic interface. 173 * They can be treated identically to a Mifare Classic tag. 174 * @hide 175 */ 176 public boolean isEmulated() { 177 return mIsEmulated; 178 } 179 180 /** Returns the number of sectors on this tag, determined at discovery time */ 181 public int getSectorCount() { 182 switch (mSize) { 183 case SIZE_1K: 184 return 16; 185 case SIZE_2K: 186 return 32; 187 case SIZE_4K: 188 return 40; 189 case SIZE_MINI: 190 return 5; 191 default: 192 return 0; 193 } 194 } 195 196 /** Returns the total block count, determined at discovery time */ 197 public int getBlockCount() { 198 return mSize / BLOCK_SIZE; 199 } 200 201 /** Returns the block count for the given sector, determined at discovery time */ 202 public int getBlockCountInSector(int sectorIndex) { 203 validateSector(sectorIndex); 204 205 if (sectorIndex < 32) { 206 return 4; 207 } else { 208 return 16; 209 } 210 } 211 212 /** Return the sector index of a given block */ 213 public int blockToSector(int blockIndex) { 214 validateBlock(blockIndex); 215 216 if (blockIndex < 32 * 4) { 217 return blockIndex / 4; 218 } else { 219 return 32 + (blockIndex - 32 * 4) / 16; 220 } 221 } 222 223 /** Return the first block of a given sector */ 224 public int sectorToBlock(int sectorIndex) { 225 if (sectorIndex < 32) { 226 return sectorIndex * 4; 227 } else { 228 return 32 * 4 + (sectorIndex - 32) * 16; 229 } 230 } 231 232 // Methods that require connect() 233 /** 234 * Authenticate a sector. 235 * <p>Every sector has an A and B key with different access privileges, 236 * this method attempts to authenticate against the A key. 237 * <p>This requires a that the tag be connected. 238 */ 239 public boolean authenticateSectorWithKeyA(int sectorIndex, byte[] key) throws IOException { 240 return authenticate(sectorIndex, key, true); 241 } 242 243 /** 244 * Authenticate a sector. 245 * <p>Every sector has an A and B key with different access privileges, 246 * this method attempts to authenticate against the B key. 247 * <p>This requires a that the tag be connected. 248 */ 249 public boolean authenticateSectorWithKeyB(int sectorIndex, byte[] key) throws IOException { 250 return authenticate(sectorIndex, key, false); 251 } 252 253 private boolean authenticate(int sector, byte[] key, boolean keyA) throws IOException { 254 validateSector(sector); 255 checkConnected(); 256 257 byte[] cmd = new byte[12]; 258 259 // First byte is the command 260 if (keyA) { 261 cmd[0] = 0x60; // phHal_eMifareAuthentA 262 } else { 263 cmd[0] = 0x61; // phHal_eMifareAuthentB 264 } 265 266 // Second byte is block address 267 // Authenticate command takes a block address. Authenticating a block 268 // of a sector will authenticate the entire sector. 269 cmd[1] = (byte) sectorToBlock(sector); 270 271 // Next 4 bytes are last 4 bytes of UID 272 byte[] uid = getTag().getId(); 273 System.arraycopy(uid, uid.length - 4, cmd, 2, 4); 274 275 // Next 6 bytes are key 276 System.arraycopy(key, 0, cmd, 6, 6); 277 278 try { 279 if (transceive(cmd, false) != null) { 280 return true; 281 } 282 } catch (TagLostException e) { 283 throw e; 284 } catch (IOException e) { 285 // No need to deal with, will return false anyway 286 } 287 return false; 288 } 289 290 /** 291 * Read 16-byte block. 292 * <p>This requires a that the tag be connected. 293 * @throws IOException 294 */ 295 public byte[] readBlock(int blockIndex) throws IOException { 296 validateBlock(blockIndex); 297 checkConnected(); 298 299 byte[] cmd = { 0x30, (byte) blockIndex }; 300 return transceive(cmd, false); 301 } 302 303 /** 304 * Write 16-byte block. 305 * <p>This requires a that the tag be connected. 306 * @throws IOException 307 */ 308 public void writeBlock(int blockIndex, byte[] data) throws IOException { 309 validateBlock(blockIndex); 310 checkConnected(); 311 if (data.length != 16) { 312 throw new IllegalArgumentException("must write 16-bytes"); 313 } 314 315 byte[] cmd = new byte[data.length + 2]; 316 cmd[0] = (byte) 0xA0; // MF write command 317 cmd[1] = (byte) blockIndex; 318 System.arraycopy(data, 0, cmd, 2, data.length); 319 320 transceive(cmd, false); 321 } 322 323 /** 324 * Increment a value block, and store the result in temporary memory. 325 * @param block 326 * @throws IOException 327 */ 328 public void increment(int blockIndex) throws IOException { 329 validateBlock(blockIndex); 330 checkConnected(); 331 332 byte[] cmd = { (byte) 0xC1, (byte) blockIndex }; 333 334 transceive(cmd, false); 335 } 336 337 /** 338 * Decrement a value block, and store the result in temporary memory. 339 * @param block 340 * @throws IOException 341 */ 342 public void decrement(int blockIndex) throws IOException { 343 validateBlock(blockIndex); 344 checkConnected(); 345 346 byte[] cmd = { (byte) 0xC0, (byte) blockIndex }; 347 348 transceive(cmd, false); 349 } 350 351 /** 352 * Copy from temporary memory to value block. 353 * @param block 354 * @throws IOException 355 */ 356 public void transfer(int blockIndex) throws IOException { 357 validateBlock(blockIndex); 358 checkConnected(); 359 360 byte[] cmd = { (byte) 0xB0, (byte) blockIndex }; 361 362 transceive(cmd, false); 363 } 364 365 /** 366 * Copy from value block to temporary memory. 367 * @param block 368 * @throws IOException 369 */ 370 public void restore(int blockIndex) throws IOException { 371 validateBlock(blockIndex); 372 checkConnected(); 373 374 byte[] cmd = { (byte) 0xC2, (byte) blockIndex }; 375 376 transceive(cmd, false); 377 } 378 379 /** 380 * Send raw NfcA data to a tag and receive the response. 381 * <p> 382 * This method will block until the response is received. It can be canceled 383 * with {@link #close}. 384 * <p>Requires {@link android.Manifest.permission#NFC} permission. 385 * <p>This requires a that the tag be connected. 386 * 387 * @param data bytes to send 388 * @return bytes received in response 389 * @throws IOException if the target is lost or connection closed 390 */ 391 public byte[] transceive(byte[] data) throws IOException { 392 return transceive(data, true); 393 } 394 395 private void validateSector(int sector) { 396 // Do not be too strict on upper bounds checking, since some cards 397 // have more addressable memory than they report. For example, 398 // Mifare Plus 2k cards will appear as Mifare Classic 1k cards when in 399 // Mifare Classic compatibility mode. 400 // Note that issuing a command to an out-of-bounds block is safe - the 401 // tag should report error causing IOException. This validation is a 402 // helper to guard against obvious programming mistakes. 403 if (sector < 0 || sector >= MAX_SECTOR_COUNT) { 404 throw new IndexOutOfBoundsException("sector out of bounds: " + sector); 405 } 406 } 407 408 private void validateBlock(int block) { 409 // Just looking for obvious out of bounds... 410 if (block < 0 || block >= MAX_BLOCK_COUNT) { 411 throw new IndexOutOfBoundsException("block out of bounds: " + block); 412 } 413 } 414} 415