Mifare RFID-RC522模組實驗(三):讀取與寫入資料

伴隨Mifare讀寫器模組附贈的RFID卡(或感應扣),都是Mifare Classic 1KB類型,台灣的停車場使用的感應幣,也是Mifare Classic。這種RFID卡內部有1KB的EEPROM記憶體,為了妥善管理並達到一卡多用的功能,這個記憶體空間被劃分成16個區段(sector),每個區段有4個區塊(block)區段0的區塊0包含卡片的唯一識別碼(UID,也稱為「製造商識別碼」,Manufacturer Code)。

Mifare Classic卡片的記憶體劃分方式

程式設計師可根據不同的應用場合,自行規劃儲存內容,例如:區段1可儲存員工編號、區段2儲存部門編號(可限制員工能夠進出的區域)、區段3存放停車時間…等等,一張卡片兼具多重識別用途。

認識Mifare的控制區塊(Trailer Block)

每個區段的區塊3也叫做控制區塊(Sector Trailer, Trailer Block或Security Block),如果把上圖的資料結構想像成16層大樓,控制區塊相當於每一層樓的密碼鎖;進、出該層樓必須先輸入正確的密碼,而且每層樓都有兩組密碼。

控制區塊包含金鑰A和金鑰B兩組密碼(各6位元組),以及存取控制位元(4位元組,但僅使用前3位元組)。

Mifare的控制區塊(Trailer Block)

金鑰B預設是可見的,金鑰A則因為安全考量,在掃描時,全部顯示成00。存取控制位元用於控制區段裡的每個區塊(區塊0到區塊3)是否能被存取、寫入或其他操作,並且決定要透過金鑰A或金鑰B來驗證。0xFF0780是控制區塊的「出廠預設值」,代表:

  • 金鑰A不可見;
  • 若通過金鑰A或金鑰B驗證,即可讀取或寫入該區段的區塊0~2。
  • 若通過金鑰A驗證,可讀取或改寫存取該區段的存取控制位元和金鑰B,也能改寫金鑰A。

傾印Mifare卡片資料,觀察EEPROM記憶體資料結構

練習讀取和寫入區塊之前,先用一張空白卡測試,把它的資料全部傾印出來(dump,亦即,把全部資料顯示在序列監控視窗),藉以觀察EEPROM記憶體的資料結構。

執行Arduino主功能表的「檔案→範例→MFRC522→DumpInfo」,開啟DumpInfo(傾印卡片資料)範例程式,將它上傳到Arduino板之後,請打開序列埠監控視窗。底下是感應一張卡片的結果,在序列埠監控視窗顯示卡片的所有區段數值之前,請勿移開或移動卡片。

傾印Mifare卡片資料

若在讀取資料的過程,把卡片移開感應區,將會出現“Timeout in communication”(通訊超時)錯誤訊息,請重新感應。

從卡片資料輸出結果可知:

  • 空白卡片的資料區,除了紀錄識別碼的區塊0之外,預設值都是00。
  • 每個區段最後一個區塊,都是控制區塊(Trailer Block,如:屬於區段15的區塊63)。
  • 控制區塊的前6位元組是金鑰A,預設全是0xFF,但顯示成00。
  • 最右邊的AccessBits(直譯為「存取位元」)代表各個區塊的讀寫權限設定,這些參數經過計算之後,存放在控制區塊的第6, 7, 8位元組。如欲進一步了解「存取位元」設置及其計算方式,可參閱NXP半導體的MIFARE Classic技術文件的Access conditions單元(PDF格式),或者MIFARE Classic 1K Access Bits Calculator(存取位元計算器)

讀取與寫入資料到Mifare卡片

底下列舉本單元使用到的MFRC522程式物件的方法和屬性:

  • MFRC522物件.PCD_Authenticate():驗證金鑰,相當於比對輸入密碼和卡片裡的密碼,唯通過驗證才能存取區段資料。
  • MFRC522物件.GetStatusCodeName():取得狀態碼的名稱
  • MFRC522物件.MIFARE_Read():讀取指定區塊的內容
  • MFRC522物件.MIFARE_Write():在指定區塊寫入資料
  • MFRC522物件.PICC_DumpMifareClassicSectorToSerial():在序列埠監控視窗顯示指定的區段內容

設定金鑰

讀取卡片內部資料的流程,請參閱「Mifare RFID-RC522模組實驗(一):讀取Mifare RFID卡的UID識別碼」貼文,讀、寫資料之前,都需要通過金鑰驗證。設定儲存金鑰的變數的語法如下:

設定儲存金鑰的變數

金鑰值儲存在key物件裡的keyByte陣列屬性,空白卡的金鑰A和B的出廠預設值都是6組 0xFF

金鑰值儲存在key物件裡的keyByte陣列屬性

驗證金鑰

採用「金鑰A」驗證金鑰的PCD_Authenticate()方法的語法如下,其中的&key參數將指向上一節設定的金鑰值;金鑰值和卡片的唯一識別碼,都必須透過位址引用(亦即,在參數前面加上&符號),而非直接傳入數值。trailerBlock參數存放控制區塊的編號;這個方法將傳回一個包含驗證結果(通過與否)的狀態碼(status code)

驗證金鑰的PCD_Authenticate()方法

控制區塊以及資料區塊的編號值介於0~63。以讀、寫區段1的區塊1為例,此區段的控制區塊編號為7;區塊1的編號為5。因此,驗證區段1時,trailerBlock參數要傳入7

控制區塊以及資料區塊的編號值

程式可透過上圖底下的兩則運算式求得資料區塊和控制區塊的編號值。

儲存狀態碼的status變數,請透過底下的語法宣告。

儲存狀態碼的status變數

底下的條件判斷式將在驗證未通過時,於序列埠監控視窗輸出代表「驗證失敗」的訊息:

序列埠監控視窗輸出代表「驗證失敗」的訊息

讀取區塊的MIFARE_Read()方法

讀取區塊資料的核心敘述如下,請先設定一個儲存讀取值的陣列變數,雖然區塊資料的長度是16位元組,但是MFRC522程式庫規定至少需要準備18位元組大小的陣列來存放讀取值;讀取和寫入區塊資料的方法也會傳回狀態碼。

讀取區塊的MIFARE_read()方法

底下是負責讀取區塊資料的自訂函式readBlock(),它接受三個參數:區段編號區塊編號資料陣列

上面的程式可顯示單一區塊的資料,若要傾印Mifare Classic卡片的特定區段(亦即,4個區塊),可透過MFRC522程式庫內建的PICC_DumpMifareClassicSectorToSerial()。執行此方法時,需要傳入卡片識別碼、驗證碼和區段編號,例如,底下的敘述將在序列埠監控視窗輸出整個區段15的資料:

寫入資料的MIFARE_Write()方法

寫入區塊資料的核心敘述如下,請先宣告一個16位元組的陣列變數(此處命名為blockData),在其中存入即將寫入卡片的資料:

寫入資料的MIFARE_Write()方法

假設要把資料存入區段1的區塊1,區塊編號值請設定成5。

寫入與讀取Mifare卡片資料的程式碼

本實驗的電路和零件,與「Mifare RFID-RC522模組實驗(一):讀取Mifare RFID卡的UID識別碼」貼文相同。底下的程式碼將在掃描到Mifare Classic卡片時,在區段15的區塊1,寫入“Keep Hacking!”字串:

第128行程式使用Serial.write()而非Serial.print()方法,是因為print()會把字元的ASCII數字值轉換成文字顯示,而write()則直接把字元顯示出來(請參閱這個回應)。

上傳程式之後,請開啟序列埠監控視窗,並掃描Mifare卡片,將能見到下圖的結果:

顯示寫入與讀取卡片資料

延伸閱讀

7 thoughts on “Mifare RFID-RC522模組實驗(三):讀取與寫入資料

  1. 您好,可以請問一下,如果Key A被我改成不是預設值的0xFF,要怎麼變回預設值。或是在哪裡可以輸入驗證的密碼。

    謝謝

  2. 我把
    for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
    }
    換成(aaaaa)
    key.keyByte[0] = 0x61;
    key.keyByte[1] = 0x61;
    key.keyByte[2] = 0x61;
    key.keyByte[3] = 0x61;
    key.keyByte[4] = 0x61;
    key.keyByte[5] = 0xFF;
    仍然會出現PCD_Authenticate() failed: Timeout in communication.

  3. 你好,我想要請教一下
    我現在程式打完有一塊是
    把第5腳設置為輸出,第六腳為輸入
    請問為何我第六腳要直接讀取第五腳的準位(1or0)
    不管怎樣設定(強制為0),他只會讀到1
    是硬體本身不能嗎?

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *