2026年5月20日 星期三

AES-128(進階加密標準)

 


NXP 為了修正 CRYPTO1 的漏洞而引進了更安全的 AES-128(進階加密標準),接下來我們就用 Python + tkinter 來實作一個 AES-128 的加密解密展示工具。

AES-128 是目前全球政府與金融機構(包含新一代的智慧卡 MIFARE DESFire)廣泛使用的對稱式加密標準,安全性極高。

實作前的準備

Python 內建庫沒有完整的 AES 加密,我們需要使用目前最主流、安全的密碼學庫 pycryptodome。請先在終端機執行以下指令安裝:


pip install pycryptodome


AES-128 與 CRYPTO1 的關鍵技術對比

透過這個 GUI 程式,你可以體驗到現代密碼學(AES)與舊型 NXP 密碼(CRYPTO1)本質上的巨大進步:

技術特性舊型 CRYPTO1 (MIFARE Classic)現代 AES-128 (MIFARE DESFire 等)
演算法類型流密碼 (Stream Cipher):逐個字元與密鑰流進行 XOR 運算。方塊加密 (Block Cipher):將資料切成 16 位元組的方塊,群組進行複雜混淆。
金鑰長度48-bit (僅需幾秒即可暴力破解)。128-bit (以目前人類科技,宇宙毀滅前也無法暴力破解)。
初始向量 (IV)無,通常依賴簡單的硬體隨機數(已被逆向攻破)。有 (如 CBC 模式):即使密碼與明文都相同,只要 IV 不同,每次產生的密文就完全不同。
安全性評級🔴 已被淘汰 / 易受複製攻擊🟢 極度安全 / 金融、軍事級標準

程式中的核心密碼學概念

  1. 方塊填充 (Padding):AES 加密時,不論你的資料多短(即使只輸入一個字),它都必須被填充到 16 位元組的整數倍(如程式中的 pad() 函數),再送入演算法處理。

  2. CBC 模式與 IV (初始向量):在 CBC (Cipher Block Chaining) 模式下,前一個資料塊的密文會與下一個資料塊的明文混合。IV 則是第一個方塊的「引導劑」。這確保了晶片在每次交易時,就算寫入相同的扣款金額,在空氣中被攔截到的無線訊號(HEX 密文)也完全不同,徹底杜絕了防重放攻擊(Replay Attack)。



import tkinter as tk

from tkinter import messagebox, ttk

import os

from Crypto.Cipher import AES

from Crypto.Util.Padding import pad, unpad


class AES128CryptoApp:

    def __init__(self, root):

        self.root = root

        self.root.title("NXP 升級方案:AES-128 安全加解密系統")

        self.root.geometry("620x600")

        self.root.resizable(False, False)

        

        # 標題

        title_label = tk.Label(root, text="AES-128 (CBC 模式) 密碼學工作台", font=("Arial", 16, "bold"), fg="#1B5E20")

        title_label.pack(pady=10)

        

        # 說明

        intro_text = "相較於已被破解的 CRYPTO1,AES-128 是目前 NXP 高安全晶片(如 DESFire)的核心演算法。\n本系統展示對稱式區塊加密,包含自動填補(Padding)與初始向量(IV)控制。"

        intro_label = tk.Label(root, text=intro_text, font=("Microsoft JhengHei", 9), justify="center", fg="#555")

        intro_label.pack(pady=5)

        

        # --- 參數設定區塊 ---

        param_frame = tk.LabelFrame(root, text=" 1. AES 密鑰參數設定 (對稱金鑰與 IV) ", font=("Arial", 10, "bold"), padx=10, pady=10)

        param_frame.pack(fill="x", padx=20, pady=10)

        

        # 金鑰 (Key) - 16 Bytes

        tk.Label(param_frame, text="128-bit 金鑰 (必須剛好 16 個字元/Bytes):").grid(row=0, column=0, sticky="w")

        self.key_entry = tk.Entry(param_frame, width=35, font=("Courier", 10))

        self.key_entry.insert(0, "NXP_DESFire_Key1") 

        self.key_entry.grid(row=0, column=1, pady=5, padx=5)

        

        # 初始向量 (IV) - 16 Bytes

        tk.Label(param_frame, text="初始向量 IV (必須剛好 16 個字元/Bytes):").grid(row=1, column=0, sticky="w")

        self.iv_entry = tk.Entry(param_frame, width=35, font=("Courier", 10))

        self.iv_entry.insert(0, "InitVector123456") 

        self.iv_entry.grid(row=1, column=1, pady=5, padx=5)

        

        # 隨機生成按鈕

        rand_btn = tk.Button(param_frame, text="🎲 隨機生成全新 Key & IV", command=self.generate_random_params, bg="#81C784")

        rand_btn.grid(row=2, column=0, columnspan=2, pady=5)


        # --- 操作區塊 ---

        io_frame = tk.LabelFrame(root, text=" 2. 明文輸入與控制 ", font=("Arial", 10, "bold"), padx=10, pady=10)

        io_frame.pack(fill="x", padx=20, pady=5)

        

        tk.Label(io_frame, text="請輸入要加密的明文資料 (無長度限制):").pack(anchor="w")

        self.plain_entry = tk.Entry(io_frame, width=65, font=("Microsoft JhengHei", 10))

        self.plain_entry.insert(0, "NXP 晶片內部機密:帳戶卡片餘額為 $5,200 元")

        self.plain_entry.pack(pady=5)

        

        btn_frame = tk.Frame(io_frame)

        btn_frame.pack(pady=5)

        

        enc_btn = tk.Button(btn_frame, text="🔒 執行 AES 加密", command=self.aes_encrypt, bg="#E53935", fg="white", font=("Arial", 10, "bold"), width=15)

        enc_btn.pack(side="left", padx=10)

        

        dec_btn = tk.Button(btn_frame, text="🔓 執行 AES 解密", command=self.aes_decrypt, bg="#43A047", fg="white", font=("Arial", 10, "bold"), width=15)

        dec_btn.pack(side="left", padx=10)


        # --- 運算結果區塊 ---

        res_frame = tk.LabelFrame(root, text=" 3. 密碼學運算結果顯示區 ", font=("Arial", 10, "bold"), padx=10, pady=10)

        res_frame.pack(fill="both", expand=True, padx=20, pady=10)

        

        # 密文顯示

        tk.Label(res_frame, text="加密後的密文 (十六進位 HEX 顯示):", fg="#A30000", font=("Arial", 9, "bold")).pack(anchor="w")

        self.cipher_output = tk.Text(res_frame, height=3, width=65, bg="#FBE9E7", font=("Courier", 10))

        self.cipher_output.pack(pady=5)

        

        # 解密顯示

        tk.Label(res_frame, text="解密還原後的明文:", fg="#1B5E20", font=("Arial", 9, "bold")).pack(anchor="w")

        self.plain_output = tk.Text(res_frame, height=2, width=65, bg="#E8F5E9", font=("Microsoft JhengHei", 10))

        self.plain_output.pack(pady=5)


    def generate_random_params(self):

        """ 隨機生成強固的 16 位元組密鑰與 IV """

        # 使用 os.urandom 生成隨機二進位,並轉為 16 字元的文字

        rand_key = os.urandom(16).hex()[:16]

        rand_iv = os.urandom(16).hex()[:16]

        

        self.key_entry.delete(0, tk.END)

        self.key_entry.insert(0, rand_key)

        self.iv_entry.delete(0, tk.END)

        self.iv_entry.insert(0, rand_iv)

        

        # 清空下方的結果,避免混淆

        self.cipher_output.delete("1.0", tk.END)

        self.plain_output.delete("1.0", tk.END)


    def get_key_and_iv(self):

        """ 讀取並檢查 Key 和 IV 是否符合 AES-128 標準 """

        key = self.key_entry.get().encode('utf-8')

        iv = self.iv_entry.get().encode('utf-8')

        

        if len(key) != 16 or len(iv) != 16:

            messagebox.showerror(

                "規格錯誤", 

                f"AES-128 要求金鑰與 IV 必須剛好為 16 位元組 (16個英文字元)!\n\n"

                f"您目前輸入:\n金鑰長度: {len(key)} Bytes\nIV 長度: {len(iv)} Bytes"

            )

            return None, None

        return key, iv


    def aes_encrypt(self):

        """ AES 加密邏輯 """

        key, iv = self.get_key_and_iv()

        if not key: return

        

        # 讀取並清空舊結果

        plaintext = self.plain_entry.get().encode('utf-8')

        self.cipher_output.delete("1.0", tk.END)

        self.plain_output.delete("1.0", tk.END) # 加密時順便清空舊的解密結果

        

        if not plaintext:

            messagebox.showwarning("提示", "請先輸入明文資料再執行加密!")

            return


        try:

            # 1. 初始化 AES CBC 模式

            cipher = AES.new(key, AES.MODE_CBC, iv)

            # 2. 自動填充明文至 16 的倍數 (PKCS7 標準)

            padded_data = pad(plaintext, AES.block_size)

            # 3. 加密

            ciphertext = cipher.encrypt(padded_data)

            

            # 將二進位密文轉成 HEX 十六進位字串顯示

            hex_cipher = ciphertext.hex().upper()

            

            # 寫入文字框並強制刷新

            self.cipher_output.insert("1.0", hex_cipher)

            self.root.update_idletasks()

            

        except Exception as e:

            messagebox.showerror("加密失敗", f"錯誤原因: {str(e)}")


    def aes_decrypt(self):

        """ AES 解密邏輯 (修正版:包含強制清空與狀態異常捕獲) """

        key, iv = self.get_key_and_iv()

        if not key: return

        

        # 1. 獲取密文並去除前後空白與換行

        hex_cipher = self.cipher_output.get("1.0", tk.END).strip()

        

        # 2. 【核心修正】每次點擊解密,先強制清空舊的解密文字框

        self.plain_output.delete("1.0", tk.END)

        

        if not hex_cipher:

            messagebox.showwarning("提示", "密文框內無資料!請先執行加密,或手動填入 HEX 密文。")

            return

            

        try:

            # 3. 將 HEX 字串還原為二進位

            ciphertext = bytes.fromhex(hex_cipher)

            

            # 4. 建立解密器 (對稱加密,Key 與 IV 必須與加密時完全相同)

            cipher = AES.new(key, AES.MODE_CBC, iv)

            

            # 5. 解密

            decrypted_padded = cipher.decrypt(ciphertext)

            

            # 6. 解除填充 (Unpad) 並用 UTF-8 解碼回人類可讀文字

            original_text = unpad(decrypted_padded, AES.block_size).decode('utf-8')

            

            # 7. 寫入解密顯示框,並強制通知 Tkinter GUI 刷新渲染

            self.plain_output.insert("1.0", original_text)

            self.root.update_idletasks()

            

        except ValueError:

            # 密碼學特點:如果 Key/IV 錯了,解密後的區塊尾端不會符合 PKCS7 填充規則,unpad 會直接報 ValueError

            messagebox.showerror(

                "解密失敗 (密鑰不匹配)", 

                "無法解密!原因如下:\n"

                "1. 您可能點擊了『隨機生成 Key & IV』,導致目前的密鑰與當初加密時的密鑰不一致。\n"

                "2. 密文 (HEX) 內容在傳輸或複製時遭到篡改。"

            )

        except Exception as e:

            messagebox.showerror("系統錯誤", f"解密時發生非預期錯誤: {str(e)}")


if __name__ == "__main__":

    root = tk.Tk()

    app = AES128CryptoApp(root)

    root.mainloop()



測試建議步驟(感受密碼學的安全特性)

  1. 正常測試:直接點擊 🔒 執行 AES 加密 $\to$ 紅色框出現密文 $\to$ 接著點擊 🔓 執行 AES 解密 $\to$ 綠色框完美還原明文。

  2. 安全干擾測試(模擬駭客改密鑰)

    • 先點擊 🔒 執行 AES 加密 產生密文。

    • 此時點擊按鈕 🎲 隨機生成全新 Key & IV(此時金鑰已經變了,但密文還在)。

    • 這時候點擊 🔓 執行 AES 解密,程式會立刻捕捉到異常並跳出 「解密失敗 (密鑰不匹配)」 的警告,這證明了 AES 演算法極高的防偽與保密能力!

沒有留言:

張貼留言

wokwi 使用在哪一時間使用比較容易 組譯成功

 Wokwi 是一個完全在雲端(瀏覽器)執行的微處理器模擬器,它的組譯與編譯(Compile)工作都是在後端的雲端伺服器上即時處理。 原則上,Wokwi 的伺服器架構相當彈性,隨時都可以使用。但如果從 伺服器負載與網路延遲 的角度來看,以下幾個時間點通常反應最快、最不容易因為伺服...