RFID 讀寫功能是通過模擬卡片記憶體和認證機制
使用 Python tkinter 框架模擬 MIFARE 1K RFID 卡片讀寫個人數據的應用程式。
由於在單一的 Tkinter 程式中無法直接控制 MFRC522 硬體(這需要特定的硬體庫和底層通信),此範例的 RFID 讀寫功能是通過模擬卡片記憶體和認證機制來實現的,但其流程和邏輯與您提供的 C++ 範例保持一致。
模擬流程與 C++ 程式碼的對應關係:
| C++ 程式碼動作 | Tkinter 模擬動作 | 邏輯說明 |
key.keyByte[i] = 0xFF; | self.CORRECT_KEY = "FFFFFFFFFFFF" | 模擬工廠預設的 Key A/B 密鑰。 |
mfrc522.PICC_IsNewCardPresent() | 點擊 "檢查卡片 (Check Card)" 按鈕 | 模擬卡片進入讀卡器範圍。 |
mfrc522.PCD_Authenticate(...) | 點擊 "執行認證 (Authenticate)" | 檢查使用者輸入的 Key 是否與模擬密鑰相符。 |
mfrc522.MIFARE_Write(...) | 點擊 "寫入數據 (Write Data)" | 檢查認證狀態,如果成功,將數據寫入模擬記憶體。 |
mfrc522.MIFARE_Read(...) | 點擊 "讀取數據 (Read Data)" | 檢查認證狀態,如果成功,從模擬記憶體中讀取數據。 |
卡片資料結構
Mifare Classic 1k 卡片總共有 1024 Bytes;卡片總共被分成 16 個 Sectors,每個 Sector 有 64 Bytes;每個 Sector 又被分成 4 個 Blocks,每個 Block 有 16 Bytes。

每個 Sector 的 0-2 個 Block 是資料區塊 (Data block),可以用來存放資料,每個 Sector 的第 3 個 Block 是控制區塊 (Control block),用來存放存取控制資料,裡面有 Sector Key A、Sector Key B 及存取權限。

Data block 還可以被設定成特殊的 Value block,可以用來存放 32 位元的有號整數,值區域會對整數進行備份,避免資料發生錯誤,並且會有額外四個指令:遞增、遞減、備份、還原可以執行。

存取權限的部分在修改前,通常會建議直接透過一些工具直接計算權限(搜尋 MIFARE Classic 1K Access Bits Calculator 就能找到),避免不小心設定錯誤導致整個 Block 資料被鎖死(俗稱變磚)。

第 0 個 Sector 的第 0 個 Block 是特殊的 Block,被規劃成製造商區塊 (Manufacturer block),這個區塊會在製造時寫入一個唯讀的資料,通常用來存放卡片的 UID 及製造商資料。

這是一個使用 Python tkinter 框架模擬 MIFARE 1K RFID 卡片讀寫個人數據的應用程式。
由於在單一的 Tkinter 程式中無法直接控制 MFRC522 硬體(這需要特定的硬體庫和底層通信),此範例的 RFID 讀寫功能是通過模擬卡片記憶體和認證機制來實現的,但其流程和邏輯與您提供的 C++ 範例保持一致。
模擬流程與 C++ 程式碼的對應關係:
| C++ 程式碼動作 | Tkinter 模擬動作 | 邏輯說明 |
key.keyByte[i] = 0xFF; | self.CORRECT_KEY = "FFFFFFFFFFFF" | 模擬工廠預設的 Key A/B 密鑰。 |
mfrc522.PICC_IsNewCardPresent() | 點擊 "檢查卡片 (Check Card)" 按鈕 | 模擬卡片進入讀卡器範圍。 |
mfrc522.PCD_Authenticate(...) | 點擊 "執行認證 (Authenticate)" | 檢查使用者輸入的 Key 是否與模擬密鑰相符。 |
mfrc522.MIFARE_Write(...) | 點擊 "寫入數據 (Write Data)" | 檢查認證狀態,如果成功,將數據寫入模擬記憶體。 |
mfrc522.MIFARE_Read(...) | 點擊 "讀取數據 (Read Data)" | 檢查認證狀態,如果成功,從模擬記憶體中讀取數據。 |
我將在一個文件中生成完整的 Tkinter 應用程式。
程式碼執行說明:
運行環境: 執行上述 Python 腳本,它將啟動一個 GUI 窗口。
模擬步驟:
Step 1: 檢查卡片:點擊 "檢查卡片 (Check Card)" 按鈕,模擬 PCD 偵測到一張新卡片。
Step 2: 認證:保持密鑰為預設的
FFFFFFFFFFFF,點擊 "執行認證 (Authenticate)"。如果密鑰正確,認證狀態會變為綠色,並且紀錄上次認證的區塊地址。Step 3: 寫入數據:在寫入欄位中輸入 32 個十六進制字元(或保持預設值),點擊 "寫入數據 (Write Data)"。這將更新模擬記憶體。
Step 4: 讀取數據:點擊 "讀取數據 (Read Data)"。模擬器將從記憶體中取出數據,並同時顯示其十六進制和 ASCII 轉換結果。
注意: 您必須先點擊 "檢查卡片",然後對目標 "區塊地址" 執行 "認證" 成功後,才能進行讀寫操作,這嚴格遵循了 C++ 範例中 PICC_IsNewCardPresent() 和 PCD_Authenticate() 的邏輯順序。
import tkinter as tk
from tkinter import ttk, messagebox
import binascii
class MifareDataApp:
def __init__(self, master):
self.master = master
master.title("MIFARE 1K 讀寫流程模擬 (Tkinter)")
# --- 模擬卡片記憶體與密鑰 ---
# 模擬 64 個區塊 (Blocks),每個區塊 16 個位元組,初始化為 '00'
# 數據以 Hex String 儲存 (32 個字元)
self.simulated_card_memory = ["00" * 16] * 64
# 模擬預設密鑰 (Key A/B),與 C++ 範例的 FFFFFFFFFFFF 對應
self.CORRECT_KEY = "FFFFFFFFFFFF"
# 狀態追蹤
self.is_card_present = False
self.is_authenticated = False
self.auth_block = -1 # 紀錄上次認證成功的區塊
self.create_widgets()
self.update_status()
def create_widgets(self):
# 設定樣式
style = ttk.Style()
style.configure('TButton', font=('Arial', 10), padding=5)
style.configure('TLabel', font=('Arial', 10), padding=2)
style.configure('Title.TLabel', font=('Arial', 12, 'bold'))
main_frame = ttk.Frame(self.master, padding="10 10 10 10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 1. 狀態區
status_frame = ttk.LabelFrame(main_frame, text="卡片狀態", padding="10")
status_frame.grid(row=0, column=0, columnspan=2, sticky='ew', pady=10)
self.card_status_var = tk.StringVar(value="未連接")
self.auth_status_var = tk.StringVar(value="未認證")
ttk.Label(status_frame, text="卡片存在狀態:").grid(row=0, column=0, sticky='w')
ttk.Label(status_frame, textvariable=self.card_status_var, foreground='red').grid(row=0, column=1, sticky='w')
ttk.Label(status_frame, text="認證狀態:").grid(row=1, column=0, sticky='w')
ttk.Label(status_frame, textvariable=self.auth_status_var, foreground='red').grid(row=1, column=1, sticky='w')
ttk.Button(status_frame, text="檢查卡片 (Check Card)", command=self.check_card).grid(row=0, column=2, rowspan=2, padx=20)
# 2. 參數輸入區
param_frame = ttk.LabelFrame(main_frame, text="操作參數", padding="10")
param_frame.grid(row=1, column=0, sticky='ew', padx=5, pady=5)
# 區塊地址
ttk.Label(param_frame, text="區塊地址 (0-63):").grid(row=0, column=0, sticky='w', padx=5, pady=5)
self.block_address_var = tk.StringVar(value="2") # 預設 Block 2
ttk.Entry(param_frame, textvariable=self.block_address_var, width=5).grid(row=0, column=1, sticky='w', padx=5, pady=5)
# 密鑰
ttk.Label(param_frame, text="密鑰 Key (12 Hex):").grid(row=1, column=0, sticky='w', padx=5, pady=5)
self.key_var = tk.StringVar(value=self.CORRECT_KEY) # 預設正確密鑰
ttk.Entry(param_frame, textvariable=self.key_var, width=15).grid(row=1, column=1, sticky='w', padx=5, pady=5)
# 3. 寫入數據區
write_frame = ttk.LabelFrame(main_frame, text="寫入數據 (16 Bytes / 32 Hex Characters)", padding="10")
write_frame.grid(row=2, column=0, sticky='ew', padx=5, pady=5)
self.data_to_write_var = tk.StringVar(value=self.string_to_hex("Rui Santos - RNT", 16))
ttk.Entry(write_frame, textvariable=self.data_to_write_var, width=35).grid(row=0, column=0, columnspan=2, pady=5)
ttk.Button(write_frame, text="執行認證 (Authenticate)", command=self.authenticate).grid(row=1, column=0, pady=5, sticky='ew')
ttk.Button(write_frame, text="寫入數據 (Write Data)", command=self.write_data).grid(row=1, column=1, pady=5, sticky='ew')
# 4. 讀取數據區
read_frame = ttk.LabelFrame(main_frame, text="讀取數據", padding="10")
read_frame.grid(row=1, column=1, rowspan=2, sticky='nsew', padx=5, pady=5)
self.read_data_var = tk.StringVar(value="")
self.read_ascii_var = tk.StringVar(value="")
ttk.Button(read_frame, text="讀取數據 (Read Data)", command=self.read_data).grid(row=0, column=0, sticky='ew', pady=5)
ttk.Label(read_frame, text="Hex (32 Chars):", font=('Courier', 10)).grid(row=1, column=0, sticky='w', pady=(10, 2))
ttk.Label(read_frame, textvariable=self.read_data_var, wraplength=200, foreground='green', font=('Courier', 10)).grid(row=2, column=0, sticky='w', padx=5)
ttk.Label(read_frame, text="ASCII 轉換:", font=('Courier', 10)).grid(row=3, column=0, sticky='w', pady=(10, 2))
ttk.Label(read_frame, textvariable=self.read_ascii_var, wraplength=200, foreground='green', font=('Courier', 10)).grid(row=4, column=0, sticky='w', padx=5)
# 5. 訊息日誌
self.log_text = tk.StringVar(value="等待操作...")
ttk.Label(main_frame, textvariable=self.log_text, wraplength=480, justify=tk.LEFT, foreground='black', background='#f0f0f0').grid(row=3, column=0, columnspan=2, sticky='ew', pady=10, padx=5)
# 讓列寬度自適應
main_frame.grid_columnconfigure(0, weight=1)
main_frame.grid_columnconfigure(1, weight=1)
def update_status(self):
# 更新卡片和認證狀態顯示
self.card_status_var.set("存在" if self.is_card_present else "未連接")
self.auth_status_var.set(f"成功 ({self.auth_block})" if self.is_authenticated else "未認證")
# 設置顏色
card_color = 'green' if self.is_card_present else 'red'
auth_color = 'green' if self.is_authenticated else 'red'
self.master.winfo_children()[0].winfo_children()[0].winfo_children()[1].config(foreground=card_color)
self.master.winfo_children()[0].winfo_children()[0].winfo_children()[3].config(foreground=auth_color)
def log_message(self, message):
self.log_text.set(message)
def string_to_hex(self, s, length):
# 將字串轉換為 Hex,並用 '00' 填充至指定長度
hex_str = binascii.hexlify(s.encode('ascii')).decode('utf-8').upper()
# 確保長度為 16 bytes (32 hex chars)
return hex_str.ljust(length * 2, '0')
def hex_to_string(self, hex_str):
# 將 Hex 轉換為字串,並移除末尾的空位元組 (0x00)
try:
bytes_data = binascii.unhexlify(hex_str.encode('utf-8'))
return bytes_data.decode('ascii').rstrip('\x00')
except binascii.Error:
return "數據錯誤"
def validate_inputs(self, check_auth=True):
# 檢查區塊地址
try:
block = int(self.block_address_var.get())
if not (0 <= block <= 63):
self.log_message("錯誤:區塊地址必須在 0 到 63 之間。")
return None, None
except ValueError:
self.log_message("錯誤:區塊地址無效。")
return None, None
# 檢查密鑰長度
key = self.key_var.get().upper()
if len(key) != 12 or not all(c in '0123456789ABCDEF' for c in key):
self.log_message("錯誤:密鑰必須是 12 個十六進制字元。")
return None, None
# 檢查卡片和認證狀態
if not self.is_card_present:
self.log_message("錯誤:請先檢查卡片是否在場 (PICC_IsNewCardPresent)。")
return None, None
if check_auth and (not self.is_authenticated or self.auth_block != block):
self.log_message(f"錯誤:請先使用 Key A/B 認證區塊 {block} (PCD_Authenticate)。")
return None, None
return block, key
# --- 模擬 MFRC522 API 函式 ---
def check_card(self):
# 模擬 PICC_IsNewCardPresent() 和 PICC_ReadCardSerial()
self.is_card_present = True
self.is_authenticated = False # 卡片重新連線,認證失效
self.auth_block = -1
self.update_status()
self.log_message("偵測到新的 MIFARE 1K 卡片。UID: F1AABBCC (模擬)。請執行認證。")
def authenticate(self):
# 模擬 PCD_Authenticate(0x60, blockAddress, &key, &(mfrc522.uid))
block, key = self.validate_inputs(check_auth=False)
if block is None:
return
if key == self.CORRECT_KEY:
self.is_authenticated = True
self.auth_block = block
self.update_status()
self.log_message(f"認證成功!區塊 {block} 已成功認證,可以使用讀寫操作。")
else:
self.is_authenticated = False
self.auth_block = -1
self.update_status()
self.log_message("認證失敗 (Authentication failed)。密鑰不正確。")
def write_data(self):
# 模擬 MIFARE_Write(blockAddress, newBlockData, 16)
block, key = self.validate_inputs(check_auth=True)
if block is None:
return
# 檢查數據長度
data_hex = self.data_to_write_var.get().upper()
if len(data_hex) != 32 or not all(c in '0123456789ABCDEF' for c in data_hex):
self.log_message("錯誤:寫入數據必須是 32 個十六進制字元 (16 Bytes)。")
return
# 執行寫入
self.simulated_card_memory[block] = data_hex
self.log_message(f"數據寫入成功 (Data written successfully)!區塊 {block} 的數據已更新。")
def read_data(self):
# 模擬 MIFARE_Read(blockAddress, blockDataRead, &bufferblocksize)
block, key = self.validate_inputs(check_auth=True)
if block is None:
return
# 執行讀取
read_hex = self.simulated_card_memory[block]
read_ascii = self.hex_to_string(read_hex)
self.read_data_var.set(read_hex)
self.read_ascii_var.set(read_ascii)
self.log_message(f"讀取成功 (Read successfully)!已從區塊 {block} 讀取 16 Bytes 數據。")
if __name__ == "__main__":
root = tk.Tk()
app = MifareDataApp(root)
root.mainloop()

沒有留言:
張貼留言