PN532 akses memory NFC MIFARE ISO14443

MIFARE classic merupakan kartu tanpa kontak buatan NXP Semiconductors yang sudah umum digunakan sebagai nfc-tag yang dapat dibuat menjadi seperti e-wallet, kartu keamanan, kartu absen maupun kartu untuk memberikan perintah kepada HP. Dalam kartu NFC ada berbagai format yang umum digunakan seperti NDEF, APDU ataupun format MIFARE classic biasa, pada artikel ini akan dibahas cara mengakses memory MIFARE classic menggunakan NFC reader PN532 dan mikrokontroler. Perlengkapan yang perlu dipersiapkan adalah sebagai berikut :

  1. PN532 Board
  2. Jumper male female
  3. NFC Tag ISO14443, seperti: EL-MF1A
  4. Arduino Mega 2560 beserta USB nya

Langkah-langkah

Langkah 1 : Penjelasan Memory MIFARE Classic 1K EV-1 ISO 14443 type A

Komunikasi yang digunakan untuk melakukan akses ke memory dari PN532 adalah I2C, untuk setting dan persiapan pada PN532 dapat dilihat pada artikel dengan link berikut Persiapan Hardware dan Software.

Setelah persiapan sudah dilakukan pertama-tama perlu diketahui bahwa isi memori daripada MIFARE classic 1K terbagi menjadi .

gambar 1

Setiap blok mengandung 16 byte data, jika dilihat pada block 0 yang sudah terisi data, dimana 4 byte pertama merupakan UID dari kartu MIFARE Classic dan 12 byte sisanya merupakan kode unik produksi dari kartu tersebut, sehingga isi dalam blok 0 tidak dapat diganti, namun ada beberapa jenis kartu yang membebaskan penggantian isi dari blok 0.

Selanjutnya pada setiap sektor terdapat 4 blok, misalkan dilihat pada sektor 2 di gambar 1, block 11 yang disebut sebagai sector trailer terisi sebuah data yang dapat mengatur hak akses atas sektor tersebut dan juga kode otentikasi sebelum mengakses setiap block pada sektor tersebut, dari sini dapat dilihat sebelum kita dapat mengakses sebuah blok dari masing masing memory, kita perlu melakukan otentikasi. Berikut adalah format dari sector trailer.

gambar 2

Sebelum menjelaskan fungsi key A dan key B dalam sector trailer pada gambar , pahami dahulu pengaturan format pada access bits jika dilihat dalam byte 6 hingga byte 8 ada berbagai variabel bit yang berisi kebalikan dari variabel yang lain misalnya pada byte 6 bit 4 hingga bit 7 merupakan kebalikan dari byte 8 bit 0 hingga bit 3 hal ini berfungsi sebagai validasi pengisian data sehingga jika pengisian diisi secara random maka hak akses atas sektor tersebut otomatis akan terhapus secara permanen, maka dari itu perlu berhati-hati untuk mengisi nilai pada byte 6 – 8 pada sector trailer, penjelasannya dapat dilihat dibawah ini.

(data random)

  1. byte 6 0x14
  2. byte 7 0x05
  3. byte 8 0x20
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
0x1400001110
0x0500000101
0x2000100000

Sudah terlihat jelas data tidak sesuai dengan format pada gambar 2, maka data diatas akan menyebabkan hilangnya akses pada sector yang diatur secara permanen.

Selanjutnya adalah contoh dari data yang benar seperti pada gambar 1.

  1. byte 6 0xFF
  2. byte 7 0x07
  3. byte 8 0x80
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
0xFF11111111
0x0700000111
0x8010000000

Jika data sudah sesuai format seperti tabel diatas maka dapat dijabarkan menjadi seperti berikut :

  • (C1,C2,C3)0 = 0 0 0 (mengontrol data block 0 dapat read write increment decrement transfer restore)
  • (C1,C2,C3)1 = 0 0 0 (mengontrol data block 0 dapat read write increment decrement transfer restore)
  • (C1,C2,C3)2 = 0 0 0 (mengontrol data block 0 dapat read write increment decrement transfer restore)
  • (C1,C2,C3)3 = 0 0 1 (mengontrol sector trailer dapat read write)

untuk definisi hak akses urutan bit datas dapat dilihat pada tabel dibawah ini.

Hak Akses Sector Trailer

gambar 3

Hak Akses Block Data

gambar 4

Yang dimaksud dari key A dan key B pada sector trailer merupakan otentikasi opsional yang dilakukan sesuai dengan bit akses dari sektor trailer.

Langkah 2: Penjelasan kode

Program yang digunakan untuk melakukan akses dari kartu memori NFC adalah program dibagian paling bawah, cara menggunakannya adalah dengan bantuan serial monitor dengan format sebagai berikut.

KondisiStarterModeNomor Blok 1Nomor Blok 2
1/r2540
2/r250
3/r
4/w25
  • kondisi 1 adalah melakukan pembacaan memori pada blok 25 hingga 40
  • kondisi 2 adalah melakukan pembacaan memori pada blok 25
  • kondisi 3 adalah melakukan pembacaan memori keseluruhan
  • kondisi 4 adalah melakukan pengisian variabel pada blok 25

Kode dibawah sudah termasuk validasi pengisian data pada blok sector trailer sehingga jika format access bit sector trailer tidak sesuai maka blok tersebut tidak akan ditulis.

#include <Wire.h>
#include <PN532_I2C.h>
#include <PN532.h>
#include <NfcAdapter.h>
 
PN532_I2C pn532i2c(Wire);
PN532 nfc(pn532i2c);
 
uint8_t defaulKey[6]={255,255,255,255,255,255};
uint8_t keyuniversalb[6] =  {255,255,255,255,255,255};
uint8_t isiData[16] = {0xff,0xff,0xff,0xff,0xff,0xff,0x7f,0x07,0x88,0x10,0xff,0xff,0xff,0xff,0xff,0xff};
bool    auth = false;
 
uint8_t success;
uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };
uint8_t uidLength;
bool authenticated = false;
bool readMode,writeMode;
uint8_t data[16];
int tes;
 
 
 
void setup() {
  Serial.begin(115200);
  nfc.begin();
 
  uint32_t versiondata = nfc.getFirmwareVersion();
  if (! versiondata) {
  Serial.print("Didn't find PN53x board");
  while (1); // halt
  }
 
  Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX);
  Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC);
  Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC);
 
}
 
void loop() {
  if(Serial.available()){
    String input = Serial.readStringUntil("\n");
    if(input.substring(0,1)=="/"){
      if(input.substring(1,2)=="r") readMode = true;
      if(input.substring(1,2)=="w") writeMode = true;
        if(readMode){
          if(input.length()==4){
            check(0,63);
            input="";
          }
          if(input != ""){
            int idx;
            input = input.substring(2);
            idx = input.indexOf(',');
            if(idx>0){
              Serial.println(idx);
              Serial.println(input);
              int input1 = input.substring(0,idx).toInt();
              int input2 = input.substring(idx+1).toInt();
              Serial.println("ini data 1 ---> "+(String)input1 + " ----> ini data 2 ----> " + (String)input2);
              if((input1>0 && input1<64) &&(input2>0 && input2<64) && (input2 > input1)){
                check(input1,input2);
                input="";
              }else{
                Serial.println("Input Tidak Valid\n\n\n");
              }
            }else{
              int checkNum = input.toInt();
              if(checkNum>=0 && checkNum<64){
                check(checkNum,checkNum);
                input="";
              }
            }
          }
 
      }else if(writeMode){
        //isi data block sebesar 16 byte didalam array
        int wInput;
        input = input.substring(2);
        wInput = input.toInt();
        if(wInput == 0 || wInput > 63 || wInput < 0){
          Serial.println("Input Tidak Valid");
        }else if(wInput %4 == 3){
          Serial.println("Pengecekan Block \n\n\n");
          if(blockCheck(isiData[6],isiData[7],isiData[8])){
            Serial.println("Block Valid\n\n\n");
            writeBlock(wInput , isiData, 16);
          }
          else{
            Serial.println("Block Tidak Valid");
          }
        }
        else{
          writeBlock(wInput , isiData, 16);
          input="";
        }
      }
    }
  }
}
 
void flushSerial(){
  while(Serial.available()>0){
    Serial.read();
  }
}
 
void check(uint8_t block1,uint8_t block2){
  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);
  if(success){
    Serial.println("\n\nFound an ISO14443A card");
    nfc.PrintHex(uid, uidLength);
    Serial.println("");
    if(uidLength == 4){
      for(int currentBlock = block1;currentBlock<=block2;currentBlock++){
        success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, currentBlock, auth, keyuniversal);
        if(currentBlock%4 == 0 && block1 != block2){
          Serial.print("------------------------Sector ");Serial.print(currentBlock/4, DEC);Serial.println("-------------------------");
        }
        if(!success){
          Serial.println("Authentication error");
          Serial.println("Block ");Serial.print(currentBlock, DEC);Serial.println(" unable to authenticate");
        }else{
          success = nfc.mifareclassic_ReadDataBlock(currentBlock, data);
          if(success){
            Serial.print("Block ");
            Serial.print(currentBlock, DEC);
            if(currentBlock<10) Serial.print("  ");
            if(currentBlock>10) Serial.print(" ");
            nfc.PrintHexChar(data, 16);
          }
          else{
            Serial.print("Block ");
            Serial.print(currentBlock, DEC);
            Serial.println(" unable to read this block");
          }
        }
      }
 
    }
    else if(uidLength == 7){
      Serial.println("This is Mifare Ultralight Card");
    }
  }
  readMode = false;
}
 
void writeBlock(int currentBlock,uint8_t arrayBlock[],int panjang){
  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);
  if(success){
    Serial.println("Found an ISO14443A card");
    nfc.PrintHex(uid, uidLength);
    Serial.println("");
    if(uidLength == 4){
      success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, currentBlock, auth, keyuniversal);
      if(!success){
        Serial.println("Authentication error");
        Serial.println("Block ");Serial.print(currentBlock, DEC);Serial.println(" unable to authenticate");
      }else{
        success = nfc.mifareclassic_WriteDataBlock(currentBlock, arrayBlock);
        if(success){
          Serial.println("Data berhasil diubah\n\n");
        }
        else{
          Serial.print("Block ");
          Serial.print(currentBlock, DEC);
          Serial.println(" unable to Write this block");
        }
      }
 
 
    }
    else if(uidLength == 7){
      Serial.println("This is Mifare Ultralight Card");
    }
  }
  writeMode = false;
 
}
 
bool blockCheck(byte b6, byte b7,byte b8){
  bool valid = false;
  if(((b6 & 0xF0) >> 4) ^ 0x0F == b8 & 0x0F) {
    if((b6 & 0x0F) ^ 0x0F == ((b7 & 0xF0) >> 4)) {
      if((b7 & 0x0F) ^ 0x0F == (b8 & 0xF0) >> 4) {
        valid = true;
      }
    }
  }
  return valid;
}
 

Untuk tutorial dalam bentuk video dapat dilihat dalam video berikut