2025年11月9日 星期日

CRYPTO1 串流加密 (Stream Cipher) 算法模擬

 CRYPTO1 串流加密 (Stream Cipher) 算法模擬

MFRC522 晶片是 NXP Semiconductors 設計的 13.56 MHz RFID 讀寫器晶片,它主要用於與 MIFARE Classic 系列非接觸式智慧卡進行通訊。

它內部支援的加密算法就是 CRYPTO1


🔒 CRYPTO1 加密算法概述

CRYPTO1 是 NXP Semiconductors 在 1994 年為其 MIFARE Classic 系列卡片設計的專有 (Proprietary) 加密算法。它是一種串流加密 (Stream Cipher) 算法,用於在讀卡器 (如 MFRC522) 和卡片之間進行數據認證和加密通訊。

1. 🗝️ 密鑰類型與長度

  • 類型: 對稱加密 (Symmetric Encryption)。讀卡器和卡片使用相同的 48 位元密鑰 (Key) 進行認證和加解密。

  • 密鑰長度: 48 位元 (bits)。MIFARE Classic 卡片的每個扇區 (Sector) 都儲存著兩把 48 位元密鑰 (Key A 和 Key B)。

2. ⚙️ 核心機制:串流加密 (Stream Cipher)

CRYPTO1 是一種基於過濾器產生器 (Filter Generator) 的串流加密算法,其核心組成部分包括:

  • 線性回饋移位暫存器 (LFSR, Linear Feedback Shift Register): 這是密碼的核心狀態,它是一個 48 位元的暫存器,用於儲存和更新加密的內部狀態。

  • 非線性函數 (Non-linear Function/Filter): 這個函數接收來自 LFSR 的特定位元輸入(通常是 20 個位元),然後輸出 1 個位元的密鑰串流 (Keystream)

3. 握手與認證過程(三步認證)

在讀寫器和 MIFARE Classic 卡片開始交換加密數據之前,它們必須先進行三步認證 (Three-Pass Authentication) 過程,這個過程同時也是密鑰狀態初始化的過程:

  1. 讀卡器挑戰 (Reader Challenge): 讀卡器(如 MFRC522)發送一個隨機數(Nonce)給卡片。

  2. 卡片回應 (Card Response): 卡片使用其內部狀態和密鑰,對讀卡器的挑戰進行加密運算並返回一個回應。

  3. 讀卡器驗證並最終確定狀態: 讀卡器和卡片分別進行一系列的加密和狀態更新,最終兩者都必須達到相同的內部 LFSR 狀態。

  • 只有當雙方都擁有相同的 48 位元密鑰,且認證過程成功,LFSR 才會初始化到相同的、用於通訊的狀態。

  • 認證成功後,所有的數據交換都會使用 CRYPTO1 算法產生的密鑰串流進行 XOR 運算來加密和解密。

4. 🔒 安全性與現狀

  • 安全性的依賴: CRYPTO1 的安全性最初是基於算法的保密性(Security by Obscurity),因為 NXP 並未公開其內部結構。

  • 現狀: 在 2007-2008 年間,安全研究人員透過逆向工程 (Reverse Engineering) 成功破解了 CRYPTO1 算法的細節,並公開了其弱點。

    • 弱點: 由於 LFSR 的結構和認證協議中的缺陷,攻擊者可以透過多次發送挑戰/回應,有效地推斷出並還原 48 位元密鑰

  • 結果:

    • MIFARE Classic 卡片(使用 CRYPTO1)現在被認為是不安全的,容易被駭客使用工具在數秒到數分鐘內破解。

    • MFRC522 讀寫器因支援此算法,雖然仍被廣泛使用於非安全性要求高的應用(如學生專題、非門禁系統),但在需要高安全性的場合,已被 NXP 的新產品(如 MIFARE DESFire 或 MIFARE Plus)及更強大的算法(如 AES)取代。

總結來說: MFRC522 支援的 CRYPTO1 是一種 48 位元對稱串流加密算法,其主要功能是為 MIFARE Classic 卡片提供認證和數據加密,但現已被證明存在嚴重安全漏洞。

示範替代方案:模擬 CRYPTO1 的核心特性

鑑於上述限制,我將提供一個 Tkinter 應用程式,它使用一個模擬的 CRYPTO1 核心函數(基於已知算法原理和位元操作的簡易 XOR 串流)來展示其 對稱串流 的特性,但不使用真正的、複雜的 CRYPTO1 核心代碼。

這個範例著重於展示:

  • **48 位元(6 個十六進制位元組)**密鑰輸入。

  • 對稱加密:加密和解密使用相同的密鑰

  • 串流加密:基於密鑰的輸出與明文進行 XOR 運算

💻 Python 程式碼 (模擬 CRYPTO1 串流加密)




import tkinter as tk

from tkinter import messagebox

from base64 import b64encode, b64decode

import hashlib

import binascii


# --- 模擬 CRYPTO1 核心功能 ---


def crypto1_stream_cipher_simulate(key_hex, data_bytes, encrypt=True):

    """

    模擬 CRYPTO1 串流加密的流程:

    1. 使用 48 位元 Key 生成一個簡單的 Keystream (密鑰串流)。

    2. 將數據與 Keystream 進行 XOR 運算。

    

    Args:

        key_hex (str): 48 位元密鑰的十六進制表示 (12個字符,如 'AABBCCDDEEFF')。

        data_bytes (bytes): 要加密或解密的位元組數據。

        encrypt (bool): True 為加密,False 為解密 (串流加密加解密相同)。

        

    Returns:

        bytes or None: 加密/解密後的位元組數據。

    """

    try:

        # 1. 密鑰處理:將 48-bit HEX 轉換為 6-byte Key

        # CRYPTO1 Key 長度為 48 bits (6 bytes)

        key_bytes = binascii.unhexlify(key_hex.zfill(12)) 

        

        if len(key_bytes) != 6:

            raise ValueError("密鑰長度錯誤,必須是 48 bits (6 bytes)。")

            

        # 2. 模擬 Keystream (密鑰串流) 生成

        # 實際 CRYPTO1 使用複雜的 LFSR,這裡使用 Key 的 SHA256 Hash 作為 Keystream 的種子

        # 為了讓 Keystream 長度足夠,我們重複 Hash 流程直到長度滿足數據長度

        

        keystream = b''

        current_seed = key_bytes

        

        while len(keystream) < len(data_bytes):

            # 使用 Key 和上一個 Hash 作為新的種子

            hash_obj = hashlib.sha256(current_seed)

            chunk = hash_obj.digest()

            keystream += chunk

            current_seed = chunk # 更新種子,確保 Keystream 隨機性

            

        keystream = keystream[:len(data_bytes)] # 截斷至數據長度

        

        # 3. 串流加密/解密 (XOR 運算)

        result_bytes = bytes([data_bytes[i] ^ keystream[i] for i in range(len(data_bytes))])

        

        return result_bytes


    except ValueError as e:

        messagebox.showerror("錯誤", f"密鑰或數據格式錯誤。\n詳細: {e}")

        return None

    except Exception as e:

        messagebox.showerror("錯誤", f"發生未知錯誤: {e}")

        return None


# --- Tkinter GUI 界面 ---


class CRYPTO1App:

    def __init__(self, master):

        self.master = master

        master.title("MFRC522 CRYPTO1 模擬加解密 (Tkinter)")


        # --- 密鑰輸入 ---

        tk.Label(master, text="**48-bit 密鑰 (Key, 需 12 位十六進制字符)**:").grid(row=0, column=0, sticky='w', padx=10, pady=5)

        self.key_entry = tk.Entry(master, width=30)

        self.key_entry.grid(row=0, column=1, padx=10, pady=5, sticky='ew')

        self.key_entry.insert(0, "AABBCCDDEEFF") # 預設 48 bit (6 bytes) HEX Key


        # --- 加密區塊 ---

        tk.Label(master, text="--- 加密 (Encryption) ---", fg='blue').grid(row=1, column=0, columnspan=2, pady=10)

        

        tk.Label(master, text="明文 (Plaintext):").grid(row=2, column=0, sticky='w', padx=10, pady=5)

        self.plaintext_entry = tk.Text(master, height=5, width=30)

        self.plaintext_entry.grid(row=2, column=1, padx=10, pady=5, sticky='ew')

        

        self.encrypt_button = tk.Button(master, text="執行加密", command=self.handle_encrypt, bg='lightgreen')

        self.encrypt_button.grid(row=3, column=0, columnspan=2, pady=10)


        tk.Label(master, text="密文 (Ciphertext, Base64):").grid(row=4, column=0, sticky='w', padx=10, pady=5)

        self.ciphertext_output = tk.Text(master, height=5, width=30)

        self.ciphertext_output.grid(row=4, column=1, padx=10, pady=5, sticky='ew')


        # --- 解密區塊 ---

        tk.Label(master, text="--- 解密 (Decryption) ---", fg='red').grid(row=5, column=0, columnspan=2, pady=10)

        

        tk.Label(master, text="輸入密文 (Base64):").grid(row=6, column=0, sticky='w', padx=10, pady=5)

        self.decrypt_ciphertext_input = tk.Text(master, height=5, width=30)

        self.decrypt_ciphertext_input.grid(row=6, column=1, padx=10, pady=5, sticky='ew')

        

        self.decrypt_button = tk.Button(master, text="執行解密", command=self.handle_decrypt, bg='salmon')

        self.decrypt_button.grid(row=7, column=0, columnspan=2, pady=10)


        tk.Label(master, text="解密結果 (Plaintext):").grid(row=8, column=0, sticky='w', padx=10, pady=5)

        self.decrypted_output = tk.Text(master, height=5, width=30)

        self.decrypted_output.grid(row=8, column=1, padx=10, pady=5, sticky='ew')

        

        # --- 控制按鈕區塊 ---

        control_frame = tk.Frame(master)

        control_frame.grid(row=9, column=0, columnspan=2, pady=15)

        

        tk.Button(control_frame, text="🗑️ 清除所有欄位", command=self.clear_fields, bg='#FFFACD').pack(side=tk.LEFT, padx=10)

        tk.Button(control_frame, text="❌ 結束離開", command=master.quit, bg='#FFB6C1').pack(side=tk.LEFT, padx=10)



    def handle_encrypt(self):

        """處理加密按鈕事件。"""

        key_hex = self.key_entry.get().upper()

        plaintext = self.plaintext_entry.get("1.0", tk.END).strip()

        

        if not key_hex or not plaintext:

            messagebox.showwarning("警告", "密鑰和明文都不能為空!")

            return

        

        try:

            plaintext_bytes = plaintext.encode('utf-8')

        except:

            messagebox.showerror("錯誤", "明文編碼失敗。")

            return


        ciphertext_bytes = crypto1_stream_cipher_simulate(key_hex, plaintext_bytes, encrypt=True)

        

        if ciphertext_bytes is not None:

            # 串流加密結果轉為 Base64 顯示

            b64_ciphertext = b64encode(ciphertext_bytes).decode('utf-8')

            

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

            self.ciphertext_output.insert(tk.END, b64_ciphertext)

            

            # 自動複製到解密輸入框

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

            self.decrypt_ciphertext_input.insert(tk.END, b64_ciphertext)

            messagebox.showinfo("成功", "加密完成!")


    def handle_decrypt(self):

        """處理解密按鈕事件。"""

        key_hex = self.key_entry.get().upper()

        b64_ciphertext = self.decrypt_ciphertext_input.get("1.0", tk.END).strip()

        

        if not key_hex or not b64_ciphertext:

            messagebox.showwarning("警告", "密鑰和密文都不能為空!")

            return


        try:

            # Base64 解碼回位元組

            ciphertext_bytes = b64decode(b64_ciphertext)

        except binascii.Error:

            messagebox.showerror("錯誤", "密文不是有效的 Base64 格式。")

            return


        decrypted_bytes = crypto1_stream_cipher_simulate(key_hex, ciphertext_bytes, encrypt=False)

        

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

        if decrypted_bytes is not None:

            try:

                decrypted_text = decrypted_bytes.decode('utf-8')

                self.decrypted_output.insert(tk.END, decrypted_text)

                messagebox.showinfo("成功", "解密完成!")

            except:

                messagebox.showerror("錯誤", "解密結果無法解碼為 UTF-8 字串,可能是密鑰錯誤或密文被篡改。")



    def clear_fields(self):

        """清除所有 Text 欄位。"""

        

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

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

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

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

        

        messagebox.showinfo("清除成功", "所有明文與密文相關欄位已清空。")



if __name__ == "__main__":

    root = tk.Tk()

    app = CRYPTO1App(root)

    root.mainloop()


沒有留言:

張貼留言

ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite

 ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite  ESP32 VS Code 程式 ; PlatformIO Project Configuration File ; ;   Build op...