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) 過程,這個過程同時也是密鑰狀態初始化的過程:
讀卡器挑戰 (Reader Challenge): 讀卡器(如 MFRC522)發送一個隨機數(Nonce)給卡片。
卡片回應 (Card Response): 卡片使用其內部狀態和密鑰,對讀卡器的挑戰進行加密運算並返回一個回應。
讀卡器驗證並最終確定狀態: 讀卡器和卡片分別進行一系列的加密和狀態更新,最終兩者都必須達到相同的內部 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()

沒有留言:
張貼留言