ISO/IEC DIS9789-2 標準中的「三步驟雙向加密認證(Three-Step Mutual Authentication)」
這是一個涉及到 資訊安全、射頻識別(RFID/NFC)高階安全認證 以及 圖形化使用者介面(GUI) 的整合實作。
在 ISO/IEC 的安全標準中,「三步驟異頻收發(Three-Step Mutual Authentication with Cryptographic Challenge-Response)」是防範重放攻擊(Replay Attack)與非法竊聽(Eavesdropping)的核心技術。它的基本原理是:讀卡機(PCD)與卡片(PICC)雙方不直接在空中傳送密碼與真實的 UID(唯一原始單一序號),而是透過隨機亂數(Nonce)、外加密碼(Key)進行加密綁定與驗證。
為了清楚展示這「三步驟」是如何運作並保護資料的,底下為您撰寫一個 Python + Tkinter 的動態視覺化模擬程式。
「三步驟異頻認證」核心邏輯說明
步驟一(讀卡機 -> 卡片):讀卡機向卡片發送認證請求,並附帶一個讀卡機生成的隨機亂數 $R_A$。
步驟二(卡片 -> 讀卡機):卡片收到後,自己也生成一個隨機亂數 $R_B$。接著,卡片使用雙方共享的「外加密碼(Key)」與加密演算法(此處模擬使用 AES 精神),將(卡片真實 UID + $R_A$ + $R_B$)進行加密封包並回傳。
步驟三(讀卡機 -> 卡片):讀卡機用相同的密碼解密,解讀出唯一的原始 UID 並驗證 $R_A$ 是否正確(防止非法入侵)。若正確,讀卡機再將 $R_B$ 加密回傳給卡片。卡片驗證 $R_B$ 成功後,雙方才建立信任鏈,允許後續讀寫。
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
import random
import hashlib
# 模擬簡單的加密與解密函數(基於 SHA256/XOR 示意,代表標準中的安全加密算法)
def encrypt_data(plain_text, key):
# 將密鑰轉為雜湊值作為密鑰流
key_hash = hashlib.sha256(key.encode()).hexdigest()
cipher_text = ""
for i in range(max(len(plain_text), len(key_hash))):
p_ch = plain_text[i % len(plain_text)]
k_ch = key_hash[i % len(key_hash)]
cipher_text += chr(ord(p_ch) ^ ord(k_ch))
return cipher_text.encode('utf-8').hex()
def decrypt_data(cipher_hex, key):
try:
cipher_text = bytes.fromhex(cipher_hex).decode('utf-8')
key_hash = hashlib.sha256(key.encode()).hexdigest()
plain_text = ""
for i in range(max(len(cipher_text), len(key_hash))):
c_ch = cipher_text[i % len(cipher_text)]
k_ch = key_hash[i % len(key_hash)]
plain_text += chr(ord(c_ch) ^ ord(k_ch))
return plain_text
except:
return "【解密失敗/密碼錯誤】"
class AuthenticationSimApp:
def __init__(self, root):
self.root = root
self.root.title("ISO/IEC DIS9789-2 三步驟異頻收發安全認證模擬器")
self.root.geometry("850x650")
# --- 原始安全數據 ---
self.REAL_UID = "UID-9527-A8B9" # 卡片內部的唯一原始單一序號
self.CORRECT_KEY = "NFC_Secure_Key_2026" # 系統預設的外加密碼
# 運作暫存變數
self.R_A = ""
self.R_B = ""
self.step = 0
self.setup_ui()
def setup_ui(self):
# 標題
title_lbl = ttk.Label(self.root, text="ISO/IEC DIS9789-2 三步驟安全認證技術展示", font=("Arial", 16, "bold"), foreground="#1A5276")
title_lbl.pack(pady=10)
# 參數設定區
config_frame = ttk.LabelFrame(self.root, text=" 系統內部機密參數 (防偽起點) ")
config_frame.pack(fill=tk.X, padx=15, pady=5)
ttk.Label(config_frame, text="卡片內部原始唯一序號 (UID):").grid(row=0, column=0, padx=10, pady=5, sticky="e")
ttk.Label(config_frame, text=self.REAL_UID, font=("Consolas", 11, "bold"), foreground="blue").grid(row=0, column=1, padx=10, pady=5, sticky="w")
ttk.Label(config_frame, text="請輸入認證密碼 (外加密碼):").grid(row=1, column=0, padx=10, pady=5, sticky="e")
self.key_entry = ttk.Entry(config_frame, width=25)
self.key_entry.insert(0, "NFC_Secure_Key_2026") # 預設正確密碼
self.key_entry.grid(row=1, column=1, padx=10, pady=5, sticky="w")
# 流程控制與空中狀態區
self.run_frame = ttk.LabelFrame(self.root, text=" 空中接口 (Air Interface) 數據監聽監控 ")
self.run_frame.pack(fill=tk.BOTH, expand=True, padx=15, pady=10)
# 三步驟動態顯示看板
self.step1_box = tk.Text(self.run_frame, height=3, width=90, bg="#F2F4F4", state=tk.DISABLED, font=("Microsoft JhengHei", 10))
self.step1_box.pack(pady=5)
self.step2_box = tk.Text(self.run_frame, height=3, width=90, bg="#F2F4F4", state=tk.DISABLED, font=("Microsoft JhengHei", 10))
self.step2_box.pack(pady=5)
self.step3_box = tk.Text(self.run_frame, height=3, width=90, bg="#F2F4F4", state=tk.DISABLED, font=("Microsoft JhengHei", 10))
self.step3_box.pack(pady=5)
# 驗證結論看板
self.result_label = ttk.Label(self.run_frame, text="等待認證啟動...", font=("Arial", 12, "bold"), foreground="gray")
self.result_label.pack(pady=10)
# 按鈕控制區
btn_frame = ttk.Frame(self.root)
btn_frame.pack(pady=10)
self.btn_next = ttk.Button(btn_frame, text="啟動 / 執行下一步步驟", command=self.next_step)
self.btn_next.pack(side=tk.LEFT, padx=10)
btn_reset = ttk.Button(btn_frame, text="重置模擬", command=self.reset_sim)
btn_reset.pack(side=tk.LEFT, padx=10)
def update_box(self, box, text, bg_color):
box.config(state=tk.NORMAL)
box.delete("1.0", tk.END)
box.insert(tk.END, text)
box.config(bg=bg_color, state=tk.DISABLED)
def next_step(self):
user_key = self.key_entry.get()
if not user_key:
messagebox.showwarning("警告", "請輸入外加密碼!")
return
self.step += 1
if self.step == 1:
# 【步驟一】讀卡機 -> 卡片:發送隨機挑戰碼 R_A
self.R_A = str(random.randint(100000, 999999))
msg = f"【步驟一:讀卡機 -> 卡片】\n發送認證請求。讀卡機生成隨機亂數 R_A = {self.R_A}\n此時空中傳輸內容:【明文 R_A,此時駭客竊聽也拿不到 UID 或密碼】"
self.update_box(self.step1_box, msg, "#EBF5FB") # 淺藍色代表進行中
self.result_label.config(text="步驟一完成:挑戰碼 R_A 已發送。", foreground="blue")
elif self.step == 2:
# 【步驟二】卡片 -> 讀卡機:回傳加密後的 (UID + R_A + R_B)
self.R_B = str(random.randint(100000, 999999))
# 卡片內部進行加密:將 原始UID + R_A + R_B 綁定
raw_packet = f"{self.REAL_UID}|{self.R_A}|{self.R_B}"
# 使用正確的卡片內置密碼進行加密
self.cipher_step2 = encrypt_data(raw_packet, self.CORRECT_KEY)
msg = f"【步驟二:卡片 -> 讀卡機】\n卡片生成亂數 R_B = {self.R_B}。使用內置外加密碼將 (UID + R_A + R_B) 打包加密。\n空中攔截到的密文:{self.cipher_step2}\n【安全防護:防非法讀取!沒有密碼的入侵者在此步驟無法解讀出原始唯一序號 UID】"
self.update_box(self.step2_box, msg, "#EBF5FB")
self.result_label.config(text="步驟二完成:卡片已回傳加密權杖(Token)。", foreground="blue")
elif self.step == 3:
# 【步驟三】讀卡機解讀、驗證並回應
# 讀卡機嘗試用使用者輸入的密碼解密步驟二傳回的密文
decrypted_str = decrypt_data(self.cipher_step2, user_key)
if "|" in decrypted_str:
parts = decrypted_str.split("|")
parsed_uid = parts[0]
parsed_ra = parts[1]
parsed_rb = parts[2]
# 驗證 R_A 是否與步驟一相同 (防止重放攻擊)
if parsed_ra == self.R_A:
# 讀卡機回應加密後的 R_B 給卡片以完成雙向認證
cipher_step3 = encrypt_data(parsed_rb, user_key)
msg = f"【步驟三:讀卡機 -> 卡片】\n後台成功經由密碼解讀唯一原始單一序號:【{parsed_uid}】\n驗證 R_A 匹配成功!讀卡機回傳 R_B 的加密值:{cipher_step3} 給卡片確認身份。"
self.update_box(self.step3_box, msg, "#EAF2F8")
# 最終安全判定
if user_key == self.CORRECT_KEY:
self.result_label.config(text=f"【認證成功】成功解讀原始序號:{parsed_uid}。安全阻斷非法入侵與非法讀取!", foreground="green")
else:
self.result_label.config(text="【認證失敗】雖然格式符合,但外加密碼非授權密碼。", foreground="red")
else:
self.fail_display("R_A 驗證失敗,疑似遭受外部重放入侵!")
else:
# 解密出來是亂碼(密碼錯誤)
msg = f"【步驟三:讀卡機 -> 卡片】\n讀卡機使用錯誤的密碼進行解密...\n解讀結果:{decrypted_str}\n無法取得唯一原始單一序號,拒絕存取!"
self.update_box(self.step3_box, msg, "#FDEDEC")
self.result_label.config(text="【認證失敗】非法入侵/密碼錯誤!無法解讀原始單一序號。", foreground="red")
self.btn_next.config(state=tk.DISABLED)
def fail_display(self, reason):
msg = f"【驗證失敗】\n原因:{reason}"
self.update_box(self.step3_box, msg, "#FDEDEC")
self.result_label.config(text="【拒絕存取】非法入侵!", foreground="red")
def reset_sim(self):
self.step = 0
self.R_A = ""
self.R_B = ""
self.update_box(self.step1_box, "", "#F2F4F4")
self.update_box(self.step2_box, "", "#F2F4F4")
self.update_box(self.step3_box, "", "#F2F4F4")
self.result_label.config(text="等待認證啟動...", foreground="gray")
self.btn_next.config(state=tk.NORMAL)
if __name__ == "__main__":
root = tk.Tk()
app = AuthenticationSimApp(root)
root.mainloop()
這份程式碼的核心任務,是把 ISO/IEC DIS9789-2 標準中的「三步驟雙向加密認證(Three-Step Mutual Authentication)」流程用圖形化介面(GUI)展示出來。
我們可以將這段程式碼拆解為三大區塊:加密/解密底層、介面初始化設定、以及核心三步驟控制邏輯,並為你逐行說明:
一、 基礎模組匯入與加解密底層 (工具準備)
這部分負責引進所需的庫,並建立一個模擬商用加密(如 AES/DES)邏輯的測試加解密函數。
import tkinter as tk # 匯入內建的 GUI 圖形介面庫
from tkinter import ttk # 匯入美化版控制元件(按鈕、框架等)
from tkinter import messagebox # 匯入彈出式警告視窗模組
import random # 匯入亂數庫,用來生成每次認證都不同的隨機挑戰碼(Nonce)
import hashlib # 匯入雜湊庫,用來把密碼轉換成固定長度的密鑰流
1. 模擬流加密函數 (encrypt_data)
在實際標準中會使用高級對稱加密(如 AES-128)。這裡用 XOR 運算來模擬這個行為。
def encrypt_data(plain_text, key):
# 將輸入的外加密碼(key)透過 SHA-256 轉成長度 64 碼的十六進位雜湊值,作為密鑰流
key_hash = hashlib.sha256(key.encode()).hexdigest()
cipher_text = ""
# 逐字元將明文(plain_text)與密鑰流進行 XOR (^) 互斥或運算
for i in range(max(len(plain_text), len(key_hash))):
p_ch = plain_text[i % len(plain_text)]
k_ch = key_hash[i % len(key_hash)]
cipher_text += chr(ord(p_ch) ^ ord(k_ch))
# 將混淆後的結果轉成 16 進位字串(hex)回傳,方便在空中接口(文字框)顯示
return cipher_text.encode('utf-8').hex()
2. 模擬流解密函數 (decrypt_data)
def decrypt_data(cipher_hex, key):
try:
# 將 16 進位密文字串還原為原始位元組與字元
cipher_text = bytes.fromhex(cipher_hex).decode('utf-8')
key_hash = hashlib.sha256(key.encode()).hexdigest()
plain_text = ""
# 使用相同的外加密碼進行第二次 XOR 運算(XOR 的特徵:雙重 XOR 會還原明文)
for i in range(max(len(cipher_text), len(key_hash))):
c_ch = cipher_text[i % len(cipher_text)]
k_ch = key_hash[i % len(key_hash)]
plain_text += chr(ord(c_ch) ^ ord(k_ch))
return plain_text
except:
# 如果格式不對或解密出錯,直接回傳錯誤提示
return "【解密失敗/密碼錯誤】"
二、 類別初始化與 GUI 佈局 (__init__ & setup_ui)
這段負責撐起整個軟體視窗,並擺放「密碼輸入框」以及「三步驟空中數據監控看板」。
class AuthenticationSimApp:
def __init__(self, root):
self.root = root
self.root.title("ISO/IEC DIS9789-2 三步驟異頻收發安全認證模擬器")
self.root.geometry("850x650") # 規定視窗大小
# --- 原始安全數據(存放在硬體安全模組中的真實資料) ---
self.REAL_UID = "UID-9527-A8B9" # 卡片唯一的原始單一序號
self.CORRECT_KEY = "NFC_Secure_Key_2026" # 系統後台與卡片共享的正確外加密碼
# 認證運作時的暫存變數
self.R_A = "" # 讀卡機挑戰碼
self.R_B = "" # 卡片挑戰碼
self.step = 0 # 目前執行到第幾步
self.setup_ui() # 呼叫介面佈局
介面佈局主體 (setup_ui)
def setup_ui(self):
# 建立大標題文字
title_lbl = ttk.Label(self.root, text="ISO/IEC DIS9789-2 三步驟安全認證技術展示", font=("Arial", 16, "bold"), foreground="#1A5276")
title_lbl.pack(pady=10)
# 1. 建立「系統內部機密參數」區塊
config_frame = ttk.LabelFrame(self.root, text=" 系統內部機密參數 (防偽起點) ")
config_frame.pack(fill=tk.X, padx=15, pady=5)
# 顯示卡片內部的真實 UID (模擬實體卡片晶片內的唯讀區)
ttk.Label(config_frame, text="卡片內部原始唯一序號 (UID):").grid(row=0, column=0, padx=10, pady=5, sticky="e")
ttk.Label(config_frame, text=self.REAL_UID, font=("Consolas", 11, "bold"), foreground="blue").grid(row=0, column=1, padx=10, pady=5, sticky="w")
# 建立讓使用者輸入密碼的輸入框(Entry)
ttk.Label(config_frame, text="請輸入認證密碼 (外加密碼):").grid(row=1, column=0, padx=10, pady=5, sticky="e")
self.key_entry = ttk.Entry(config_frame, width=25)
self.key_entry.insert(0, "NFC_Secure_Key_2026") # 預設先填入正確密碼,方便測試
self.key_entry.grid(row=1, column=1, padx=10, pady=5, sticky="w")
# 2. 建立「空中接口數據監聽」區塊 (用來展示在空氣中傳輸的機密數據狀態)
self.run_frame = ttk.LabelFrame(self.root, text=" 空中接口 (Air Interface) 數據監聽監控 ")
self.run_frame.pack(fill=tk.BOTH, expand=True, padx=15, pady=10)
# 建立三個不可編輯(DISABLED)的 Text 文字框,用來分別顯示三步驟的空中狀態
self.step1_box = tk.Text(self.run_frame, height=3, width=90, bg="#F2F4F4", state=tk.DISABLED, font=("Microsoft JhengHei", 10))
self.step1_box.pack(pady=5)
self.step2_box = tk.Text(self.run_frame, height=3, width=90, bg="#F2F4F4", state=tk.DISABLED, font=("Microsoft JhengHei", 10))
self.step2_box.pack(pady=5)
self.step3_box = tk.Text(self.run_frame, height=3, width=90, bg="#F2F4F4", state=tk.DISABLED, font=("Microsoft JhengHei", 10))
self.step3_box.pack(pady=5)
# 最下方的結果大標題
self.result_label = ttk.Label(self.run_frame, text="等待認證啟動...", font=("Arial", 12, "bold"), foreground="gray")
self.result_label.pack(pady=10)
# 3. 建立最底部的控制按鈕
btn_frame = ttk.Frame(self.root)
btn_frame.pack(pady=10)
# 當點擊此按鈕時,觸發下一步邏輯(next_step)
self.btn_next = ttk.Button(btn_frame, text="啟動 / 執行下一步步驟", command=self.next_step)
self.btn_next.pack(side=tk.LEFT, padx=10)
# 重置按鈕
btn_reset = ttk.Button(btn_frame, text="重置模擬", command=self.reset_sim)
btn_reset.pack(side=tk.LEFT, padx=10)
# 輔助函式:用來動態更新文字框的內容與背景顏色
def update_box(self, box, text, bg_color):
box.config(state=tk.NORMAL) # 解鎖文字框
box.delete("1.0", tk.END) # 清空
box.insert(tk.END, text) # 填入新進展
box.config(bg=bg_color, state=tk.DISABLED) # 重新鎖定,防使用者手動修改
三、 三步驟防禦機制核心控制邏輯 (next_step)
這是全程式最重要的核心。當使用者每點一次「執行下一步步驟」,它就會推進一個狀態。
def next_step(self):
user_key = self.key_entry.get() # 抓取使用者在密碼框輸入的字串
if not user_key:
messagebox.showwarning("警告", "請輸入外加密碼!")
return
self.step += 1 # 步驟計數加 1
【第 1 步】讀卡機發出隨機挑戰碼 ($R_A$)
if self.step == 1:
# 隨機產生一組 6 位數亂數作為 R_A
self.R_A = str(random.randint(100000, 999999))
msg = f"【步驟一:讀卡機 -> 卡片】\n發送認證請求。讀卡機生成隨機亂數 R_A = {self.R_A}\n此時空中傳輸內容:【明文 R_A,此時駭客竊聽也拿不到 UID 或密碼】"
self.update_box(self.step1_box, msg, "#EBF5FB") # 變為淺藍色代表進行中
self.result_label.config(text="步驟一完成:挑戰碼 R_A 已發送。", foreground="blue")
【第 2 步】卡片端加密打包回傳 ($UID + R_A + R_B$)
卡片收到 $R_A$ 後,拒絕明文傳送 UID。而是用正確的外加密碼將它鎖起來。
elif self.step == 2:
# 卡片自己也生成一組 6 位數隨機挑戰碼 R_B
self.R_B = str(random.randint(100000, 999999))
# 將 (真實唯獨 UID) + (讀卡機發的 R_A) + (卡片發的 R_B) 用管線符號 | 綁定成一條字串
raw_packet = f"{self.REAL_UID}|{self.R_A}|{self.R_B}"
# 關鍵:卡片使用自己內部晶片寫死的「正確外加密碼(CORRECT_KEY)」將整包資料加密
self.cipher_step2 = encrypt_data(raw_packet, self.CORRECT_KEY)
msg = f"【步驟二:卡片 -> 讀卡機】\n卡片生成亂數 R_B = {self.R_B}。使用內置外加密碼將 (UID + R_A + R_B) 打包加密。\n空中攔截到的密文:{self.cipher_step2}\n【安全防護:防非法讀取!沒有密碼的入侵者在此步驟無法解讀出原始唯一序號 UID】"
self.update_box(self.step2_box, msg, "#EBF5FB")
self.result_label.config(text="步驟二完成:卡片已回傳加密權杖(Token)。", foreground="blue")
【第 3 步】讀卡機解讀、經對驗證與回應
此時讀卡機要透過解密來嘗試解讀、經對唯一原始序號,同時防範重放攻擊。
elif self.step == 3:
# 讀卡機收到密文後,嘗試用「使用者輸入的密碼 (user_key)」進行解密
decrypted_str = decrypt_data(self.cipher_step2, user_key)
# 如果密碼正確,解開的明文裡必然包含管線符號 "|"
if "|" in decrypted_str:
parts = decrypted_str.split("|") # 依據 "|" 將字串拆解回三個元素
parsed_uid = parts[0] # 解讀出來的原始唯一序號 (UID)
parsed_ra = parts[1] # 解讀出來的 R_A
parsed_rb = parts[2] # 解讀出來的 R_B
# 【經對驗證 1】:檢查解密出來的 R_A 是否等於剛才步驟一發出去的 R_A
if parsed_ra == self.R_A:
# 讀卡機把解出來的 R_B 再次加密,發還給卡片(證明讀卡機也擁有相同密碼)
cipher_step3 = encrypt_data(parsed_rb, user_key)
msg = f"【步驟三:讀卡機 -> 卡片】\n後台成功經由密碼解讀唯一原始單一序號:【{parsed_uid}】\n驗證 R_A 匹配成功!讀卡機回傳 R_B 的加密值:{cipher_step3} 給卡片確認身份。"
self.update_box(self.step3_box, msg, "#EAF2F8")
# 【經對驗證 2】:判定整體系統權限。如果密碼與預設完全相符
if user_key == self.CORRECT_KEY:
self.result_label.config(text=f"【認證成功】成功解讀原始序號:{parsed_uid}。安全阻斷非法入侵與非法讀取!", foreground="green")
else:
self.result_label.config(text="【認證失敗】雖然格式符合,但外加密碼非授權密碼。", foreground="red")
else:
# R_A 不符,代表這可能是駭客拿昨天錄下來的舊密文直接重播(重放攻擊),予以拒絕
self.fail_display("R_A 驗證失敗,疑似遭受外部重放入侵!")
else:
# 密碼完全錯誤時,解密出來會是一堆亂碼,找不到 "|"
msg = f"【步驟三:讀卡機 -> 卡片】\n讀卡機使用錯誤的密碼進行解密...\n解讀結果:{decrypted_str}\n無法取得唯一原始單一序號,拒絕存取!"
self.update_box(self.step3_box, msg, "#FDEDEC") # 亮紅燈
self.result_label.config(text="【認證失敗】非法入侵/密碼錯誤!無法解讀原始單一序號。", foreground="red")
self.btn_next.config(state=tk.DISABLED) # 認證結束,鎖定「下一步」按鈕
四、 其他輔助機制 (重置功能)
# 失敗時的面板顯示處理
def fail_display(self, reason):
msg = f"【驗證失敗】\n原因:{reason}"
self.update_box(self.step3_box, msg, "#FDEDEC")
self.result_label.config(text="【拒絕存取】非法入侵!", foreground="red")
# 清空所有暫存,回歸初始狀態
def reset_sim(self):
self.step = 0
self.R_A = ""
self.R_B = ""
self.update_box(self.step1_box, "", "#F2F4F4")
self.update_box(self.step2_box, "", "#F2F4F4")
self.update_box(self.step3_box, "", "#F2F4F4")
self.result_label.config(text="等待認證啟動...", foreground="gray")
self.btn_next.config(state=tk.NORMAL)
# 程式執行進入點
if __name__ == "__main__":
root = tk.Tk()
app = AuthenticationSimApp(root)
root.mainloop() # 開啟事件無窮迴圈,維持視窗運作





沒有留言:
張貼留言