RFID Authentication (認證機制)
MIFARE 1K 卡片選擇的流程,Authentication (認證) 是讀卡器 (PCD) 在讀取或寫入卡片資料前最關鍵的安全步驟。
我將分為兩部分來解釋:首先說明 MIFARE 1K 的認證機制,然後提供一個使用 Python Tkinter 模擬該流程的程式碼架構。
第一部分:MIFARE 1K 認證流程 (Authentication) 說明
MIFARE Classic 1K 卡片將其記憶體分為 16 個扇區 (Sectors),每個扇區包含 4 個區塊 (Blocks)。每個扇區的最後一個區塊稱為扇區尾部 (Sector Trailer),用於儲存該扇區的兩個密鑰:Key A 和 Key B,以及存取控制位元 (Access Bits)。
認證流程的目的是:讀卡器必須證明它知道存取該扇區所需的正確密鑰。
1. 認證的必要條件
選擇卡片: 必須先完成前一步驟(防衝突與 SAK 判斷),單一卡片已被選定。
指定目標: 讀卡器必須指定要存取哪一個扇區中的哪一個區塊。
提供密鑰: 讀卡器必須使用該扇區的正確 Key A 或 Key B 進行認證。
2. 認證的標準三步握手 (Three-Pass Handshake) 流程
為了確保安全性,MIFARE Classic 採用一種類似 Challenge-Response 的三步握手機制,密鑰本身不會在空中傳輸:
| 步驟 | 動作主體 | 傳輸內容 (抽象化) | 目的 |
| 1. 請求認證 | 讀卡器 (PCD) -> 卡片 (PICC) | 認證命令 (AUTH) + Key Type (A 或 B) + Block Address + UID | 讀卡器發送請求,並使用提供的 Key 對數據進行加密計算。 |
| 2. 卡片挑戰 | 讀卡器 (PCD) <- 卡片 (PICC) | Nonce 1 (卡片發出的隨機數) | 卡片向讀卡器發出挑戰,並用其內部儲存的 Key A/B 對 Nonce 1 進行加密計算。 |
| 3. 讀卡器響應 | 讀卡器 (PCD) -> 卡片 (PICC) | Nonce 2 (讀卡器發出的隨機數) + 加密計算結果 | 讀卡器用其知道的 Key 對 Nonce 1 進行相同計算,並將結果及 Nonce 2 傳回給卡片。 |
| 4. 認證完成 | 讀卡器 (PCD) <- 卡片 (PICC) | 加密計算結果 (卡片版本) | 卡片比較讀卡器的結果與自己的計算結果。如果一致,卡片回覆一個加密後的響應,並進入認證成功狀態。 |
只有在認證成功後,讀卡器才能根據扇區尾部的存取控制位元 (Access Bits) 對該扇區的資料區塊進行讀取或寫入操作。
第二部分:Python Tkinter 認證流程模擬
import tkinter as tk
from tkinter import ttk
import random
class MifareAuthenticationSimulator:
def __init__(self, master):
self.master = master
master.title("MIFARE 1K 認證流程 (Tkinter 模擬)")
# 模擬正確密鑰 (Key A)
self.CORRECT_KEY = "FFFFFFFFFFFF"
# 狀態變數
self.status_text = tk.StringVar()
self.log_text = tk.StringVar()
self.card_access_status = tk.StringVar()
# 初始化介面
self.create_widgets()
self.reset_simulator()
def create_widgets(self):
# 輸入區塊:密鑰和區塊地址
input_frame = ttk.LabelFrame(self.master, text="認證參數 (模擬)")
input_frame.grid(row=0, column=0, padx=10, pady=10, sticky="ew")
ttk.Label(input_frame, text="密鑰 (Key A/B, 12字元HEX):").grid(row=0, column=0, padx=5, pady=5, sticky='w')
self.key_entry = ttk.Entry(input_frame, width=20)
self.key_entry.grid(row=0, column=1, padx=5, pady=5, sticky='w')
self.key_entry.insert(0, "FFFFFFFFFFFF") # 預設輸入正確密鑰
ttk.Label(input_frame, text="區塊地址 (0-63, 模擬):").grid(row=1, column=0, padx=5, pady=5, sticky='w')
self.block_entry = ttk.Entry(input_frame, width=20)
self.block_entry.grid(row=1, column=1, padx=5, pady=5, sticky='w')
self.block_entry.insert(0, "4") # 模擬區塊 4 (扇區 1 的第一個資料區塊)
# 操作按鈕
self.auth_button = ttk.Button(self.master, text="執行認證 (Authentication)", command=self.start_authentication)
self.auth_button.grid(row=1, column=0, padx=10, pady=5, sticky="ew")
# 流程紀錄
log_frame = ttk.LabelFrame(self.master, text="三步握手流程紀錄")
log_frame.grid(row=2, column=0, padx=10, pady=5, sticky="ew")
self.log_label = ttk.Label(log_frame, textvariable=self.log_text, wraplength=450, justify=tk.LEFT, font=('Arial', 10, 'italic'))
self.log_label.grid(row=0, column=0, padx=5, pady=5, sticky='w')
# 最終結果
result_frame = ttk.LabelFrame(self.master, text="認證結果")
result_frame.grid(row=3, column=0, padx=10, pady=5, sticky="ew")
self.result_label = ttk.Label(result_frame, textvariable=self.card_access_status, font=('Arial', 12, 'bold'), foreground='black')
self.result_label.grid(row=0, column=0, padx=5, pady=5, sticky='w')
# 重置按鈕
self.reset_button = ttk.Button(self.master, text="重置", command=self.reset_simulator)
self.reset_button.grid(row=4, column=0, padx=10, pady=5, sticky="ew")
def reset_simulator(self):
self.log_text.set("請輸入密鑰和區塊地址,然後點擊 '執行認證'。")
self.card_access_status.set("當前狀態:未認證")
self.result_label.config(foreground='black')
self.auth_button.config(state=tk.NORMAL)
def generate_nonce(self):
# 模擬產生 4 byte 隨機數 (Nonce)
return f"{random.getrandbits(32):08X}"
def start_authentication(self):
# 獲取使用者輸入
key_input = self.key_entry.get().upper()
block_input = self.block_entry.get()
self.log_text.set("")
self.card_access_status.set("認證進行中...")
self.result_label.config(foreground='orange')
self.auth_button.config(state=tk.DISABLED)
log = ""
# --- 步驟 1: 讀卡器請求認證 ---
log += f"1. 讀卡器 (PCD) 發送認證請求:\n"
log += f" - 參數:使用 Key A, 區塊地址 {block_input}, 卡片 UID。\n"
# --- 步驟 2: 卡片發出挑戰 (Nonce 1) ---
nonce1 = self.generate_nonce()
log += f"2. 卡片 (PICC) 回覆挑戰:\n"
log += f" - 傳輸:發送隨機數 Nonce 1 ({nonce1})。\n"
# --- 步驟 3: 讀卡器響應與密鑰檢查 ---
log += f"3. 讀卡器執行密鑰計算並回覆:\n"
is_key_correct = (key_input == self.CORRECT_KEY)
if is_key_correct:
# 密鑰正確 - 模擬成功響應
log += f" - 內部:讀卡器使用正確密鑰 ({key_input}) 對 Nonce 1 進行加密計算。\n"
log += f" - 傳輸:發送 Nonce 2 和加密計算結果給卡片。\n"
# --- 步驟 4: 卡片驗證 ---
log += f"4. 卡片內部驗證:\n"
log += f" - 結果:讀卡器的計算結果與卡片內部計算結果一致。\n"
self.card_access_status.set("認證成功!現在可以讀寫區塊資料。")
self.result_label.config(foreground='green')
else:
# 密鑰錯誤 - 模擬失敗響應
log += f" - 內部:讀卡器使用錯誤密鑰 ({key_input}) 進行計算。\n"
log += f" - 傳輸:卡片收到結果後,發現計算不符。\n"
self.card_access_status.set("認證失敗!密鑰不匹配或流程出錯。")
self.result_label.config(foreground='red')
self.log_text.set(log)
self.auth_button.config(state=tk.NORMAL)
if __name__ == "__main__":
root = tk.Tk()
app = MifareAuthenticationSimulator(root)
# 設置視窗大小,讓內容更容易呈現
root.geometry("500x500")
root.mainloop()





沒有留言:
張貼留言