2026年1月16日 星期五

具備「結帳後銷毀 (Kill)」功能的 RFID 系統模擬

具備「結帳後銷毀 (Kill)」功能的 RFID 系統模擬


這是一個非常重要的隱私保護功能。在 EPC Gen2 (ISO 18000-6C) 標準中,這被稱為 Kill Command

為什麼需要 Kill Command?

當顧客帶著印有 RFID 標籤的商品離開超市時,如果不將標籤銷毀,駭客或第三方可以利用手持讀取器追蹤顧客的行蹤,甚至得知顧客購買了哪些私密商品。

Kill Command 的特性:

  • 不可逆性:標籤一旦被「殺死」,就永遠無法再次啟動或讀取。

  • 密碼保護:必須輸入正確的 32-bit Kill Password,標籤才會執行自毀程序,防止誤殺。


具備「結帳後銷毀 (Kill)」功能的 RFID 系統模擬

我已將此功能整合進系統。現在,當支付成功後,讀取器會自動對購物籃內的商品發送銷毀指令。




標籤銷毀技術詳解

  1. 安全機制: 在日誌中您可以看到,讀取器發送的是 Kill(UID, PWD)。這模擬了 RFID 標籤內部的比較器。如果 PWD 不符,標籤會拒絕執行,這防止了惡作劇者隨意銷毀商店內的商品。

  2. 硬體層級的變更: 在真實的晶片中,執行 Kill 指令會燒斷內部的 EEPROM 寫入熔絲 (Fuse) 或鎖死狀態機。一旦完成,該晶片將不再對任何射頻訊號產生反射調變(Backscatter)。

  3. 隱私保護的驗證測試方法

    • 點擊「掃描商品」,標籤顯示為 Active

    • 點擊「結帳並銷毀」,狀態變為 Killed

    • 此時,嘗試再次點擊同一個商品按鈕,系統會提示「掃描失敗」。這模擬了標籤在現實中已經「消失」在無線電波中的狀態。

import tkinter as tk

from tkinter import ttk, messagebox

import random

import time

from threading import Thread


# --- (SecureCipher 與 BackendDatabase 類別保持不變,略作 Kill Password 擴充) ---

class BackendDatabase:

    def __init__(self):

        self.records = {

            "53 71 37 A4 F6": {"name": "張小明", "balance": 5000, "key": "AC55"},

        }

        self.products = {

            "P001": {"name": "智慧型手機", "price": 1500, "kill_pwd": "BADC"},

            "P002": {"name": "高階顯卡", "price": 2800, "kill_pwd": "DEAD"},

            "P003": {"name": "藍牙耳機", "price": 400, "kill_pwd": "FACE"}

        }


class KillEnabledRFIDApp:

    def __init__(self, root):

        self.root = root

        self.root.title("RFID 安全銷毀 (Kill Command) 模擬器")

        self.db = BackendDatabase()

        self.scanned_items = []

        self.killed_tags = set() # 記錄已銷毀的標籤 UID

        self.setup_ui()


    def setup_ui(self):

        # 狀態列

        self.lbl_status = tk.Label(self.root, text="系統已就緒", font=("Arial", 12, "bold"), bg="#2980b9", fg="white", pady=10)

        self.lbl_status.pack(fill="x")


        # 購物籃

        self.cart_tree = ttk.Treeview(self.root, columns=("ID", "Name", "Status"), show="headings", height=5)

        self.cart_tree.heading("ID", text="商品 UID")

        self.cart_tree.heading("Name", text="名稱")

        self.cart_tree.heading("Status", text="標籤狀態")

        self.cart_tree.pack(fill="x", padx=10, pady=5)


        # 商品感應按鈕區

        btn_frame = tk.Frame(self.root)

        btn_frame.pack(pady=5)

        for pid, info in self.db.products.items():

            btn = tk.Button(btn_frame, text=f"掃描 {info['name']}", command=lambda p=pid: self.scan_item(p))

            btn.pack(side=tk.LEFT, padx=5)


        # 結帳與銷毀按鈕

        self.btn_checkout = tk.Button(self.root, text="💳 結帳並銷毀標籤 (Checkout & Kill)", 

                                      command=self.start_checkout, bg="#c0392b", fg="white", font=("Arial", 10, "bold"), pady=5)

        self.btn_checkout.pack(pady=10)


        # 日誌區

        self.log = tk.Text(self.root, height=10, bg="#fdfefe", font=("Consolas", 9))

        self.log.pack(fill="both", padx=10, pady=5)


    def scan_item(self, pid):

        if pid in self.killed_tags:

            messagebox.showerror("掃描失敗", f"標籤 {pid} 已經被銷毀 (Killed),讀取器無法偵測!")

            return

        

        item = self.db.products[pid]

        self.scanned_items.append({"id": pid, "name": item['name']})

        self.cart_tree.insert("", tk.END, iid=pid, values=(pid, item['name'], "● 正常 (Active)"))

        self.log_msg(f"[感應] 讀取到商品: {item['name']} (UID: {pid})")


    def start_checkout(self):

        if not self.scanned_items: return

        Thread(target=self.checkout_and_kill_logic, daemon=True).start()


    def checkout_and_kill_logic(self):

        self.btn_checkout.config(state=tk.DISABLED)

        self.log_msg("\n--- 開始結帳程序 ---")

        time.sleep(1)


        # 1. 模擬支付過程

        self.log_msg("[支付] 張小明 支付成功。")

        

        # 2. 逐一發送 Kill 指令

        self.log_msg("\n[安全] 正在對購物籃標籤發送 Kill Command...")

        for item in self.scanned_items:

            pid = item['id']

            kill_pwd = self.db.products[pid]['kill_pwd']

            

            time.sleep(0.8)

            # 視覺化:傳輸 Kill Password

            self.log_msg(f"-> 發送指令: Kill(UID={pid}, PWD={kill_pwd})")

            

            # 模擬標籤燒毀硬體熔絲 (Fuse)

            self.killed_tags.add(pid)

            self.root.after(0, lambda p=pid: self.cart_tree.set(p, "Status", "✖ 已銷毀 (Killed)"))

            self.log_msg(f"<- 標籤 {pid} 已確認自毀。")


        self.scanned_items = []

        self.log_msg("\n--- 結帳完成,隱私保護已啟動 ---")

        self.root.after(0, lambda: self.btn_checkout.config(state=tk.NORMAL))


    def log_msg(self, msg):

        self.root.after(0, lambda: self.log.insert(tk.END, msg + "\n"))

        self.root.after(0, lambda: self.log.see(tk.END))


if __name__ == "__main__":

    root = tk.Tk()

    app = KillEnabledRFIDApp(root)

    root.mainloop()


沒有留言:

張貼留言

ASK 調變模擬器程式碼

ASK 調變模擬器程式碼 為什麼第二張圖要「放大 (Zoom)」? 1 kHz  的週期是 1000 us 。 如果不放大,第二張圖會因為波形太密而看起來像一塊實心的紅磚。 import tkinter as tk from tkinter import ttk import n...