2025年11月9日 星期日

對稱加密 (Symmetric Encryption) 非對稱加密 (Asymmetric Encryption)

 對稱加密 (Symmetric Encryption) 非對稱加密 (Asymmetric Encryption)


🔑 對稱加密 (Symmetric Encryption) 算法

對稱加密算法通常被設計為區塊加密 (Block Cipher) 或串流加密 (Stream Cipher),用於高效地處理大量數據。

算法名稱簡介與特色密鑰長度狀態與應用
AES (Advanced Encryption Standard)目前最常用且最安全的標準。 採用「列昂達爾 (Rijndael)」算法,將數據分成 128 位元的區塊進行加密。128、192 或 256 位元極廣泛應用:HTTPS/TLS、檔案加密、金融機構、VPN 等。
DES (Data Encryption Standard)早期 (1970 年代) 的標準,採用 64 位元區塊加密。實際只有 56 位元已過時且不安全:因密鑰長度太短,極易被暴力破解,已停用。
3DES (Triple DES)對 DES 的改良,使用三個不同的密鑰對數據進行三次 DES 運算 (加密-解密-加密)。112 或 168 位元安全性較高,但運算速度慢,正逐漸被 AES 取代
ChaCha20一種高效能的串流加密算法,通常與 Poly1305 認證碼結合使用 (ChaCha20-Poly1305)。256 位元現代應用:在 TLS 協定和手機等資源受限設備上,是 AES 的高效替代方案。

🔍 對稱加密的核心挑戰

對稱加密的安全性不在於算法本身,而在於密鑰的保密與交換。如果密鑰在傳輸過程中洩露,整個加密系統就會被破解。


🔒 非對稱加密 (Asymmetric Encryption) 算法

非對稱加密的安全性依賴於難以在合理時間內解決的複雜數學問題

算法名稱核心數學難題主要用途狀態與應用
RSA (Rivest-Shamir-Adleman)大數因數分解問題:找出一個極大整數的兩個質因數。 (例如:已知 $N = p \times q$,難以從 $N$ 推導出 $p$$q$)加密/解密數位簽章最傳統、應用最廣:用於 HTTPS/TLS 的連線建立、數位憑證、密鑰交換。
ECC (Elliptic Curve Cryptography,橢圓曲線密碼學)橢圓曲線離散對數問題 (ECDLP):在橢圓曲線上進行特定的點運算難以反向推導。加密/解密數位簽章 (ECDSA)高效率:在達到與 RSA 相同的安全性時,ECC 可以使用更短的密鑰長度 (例如 256 位元的 ECC 約等於 3072 位元的 RSA),因此在行動設備和物聯網 (IoT) 中更受青睞。
DSA (Digital Signature Algorithm)離散對數問題專門用於數位簽章 (不可用於加密數據)常用於軟體發布的簽章驗證。
Diffie-Hellman (DH)離散對數問題 (與 DSA 類似,但應用不同)專門用於密鑰交換 (Key Exchange)在不安全的通道上,讓雙方安全地協商出一個共享的對稱密鑰。

對稱加密 (Symmetric Encryption)

  • 密鑰數量: 一個 (單一密鑰/共享密鑰)。

  • 運作方式: 使用同一把密鑰進行加密解密

  • 優點:

    • 速度快:加密和解密運算簡單,效率高,適合處理大量數據

    • 資源要求低:運算資源消耗少。

  • 缺點:

    • 密鑰交換問題:通訊雙方必須在安全的環境下共享這把密鑰。如果密鑰在傳輸過程中被竊聽,安全性將完全喪失。

    • 密鑰管理複雜:如果與許多人通訊,需要管理很多不同的共享密鑰。

  • 應用案例:

    • 大量資料加密:例如檔案加密 (AES, DES, 3DES 等算法)。

    • HTTPS 協定中的資料傳輸階段:在連線建立後,使用對稱加密進行實際的資料傳輸以提高效率。




1

測試明文: 

RFID 電磁反向散射耦合系統是一種遠距離的 RFID 通訊方式,它利用讀寫器發射電磁波,當電磁波到達電子標籤時,標籤會將接收到的部分能量轉化為自身工作能量,並將接收到的部分電磁波以一種「反射」的方式(稱為反向散射)將資料傳送回讀寫器


import tkinter as tk

from tkinter import messagebox

from Crypto.Cipher import AES

from Crypto.Random import get_random_bytes

from base64 import b64encode, b64decode

import os


# --- AES 加密/解密 核心功能 (與原程式碼相同) ---


def aes_encrypt(key_str, plaintext):

    """使用 AES-256 (EAX 模式) 進行加密。"""

    try:

        # 確保密鑰長度為 32 bytes (256 bits)

        key = key_str.encode('utf-8')

        if len(key) != 32:

            key = key.ljust(32, b'\0') # 簡單填充

        

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

        

        cipher = AES.new(key, AES.MODE_EAX)

        

        ciphertext, tag = cipher.encrypt_and_digest(data)

        

        return b64encode(ciphertext).decode('utf-8'), \

               b64encode(cipher.nonce).decode('utf-8'), \

               b64encode(tag).decode('utf-8')


    except ValueError as e:

        messagebox.showerror("錯誤", f"加密失敗:密鑰長度無效或數據錯誤。\n詳細: {e}")

        return None

    except Exception as e:

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

        return None



def aes_decrypt(key_str, base64_ciphertext, base64_nonce, base64_tag):

    """使用 AES-256 (EAX 模式) 進行解密。"""

    try:

        # 密鑰處理方式必須與加密時相同

        key = key_str.encode('utf-8')

        if len(key) != 32:

            key = key.ljust(32, b'\0')

            

        # 將 Base64 字符串解碼回二進制數據

        ciphertext = b64decode(base64_ciphertext)

        nonce = b64decode(base64_nonce)

        tag = b64decode(base64_tag)

        

        cipher = AES.new(key, AES.MODE_EAX, nonce=nonce)

        

        plaintext = cipher.decrypt(ciphertext)

        

        # 驗證數據的完整性和真實性

        cipher.verify(tag)

        

        return plaintext.decode('utf-8')


    except ValueError as e:

        messagebox.showerror("錯誤", f"解密失敗:密鑰錯誤或密文被竄改。\n詳細: {e}")

        return None

    except Exception as e:

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

        return None


# --- Tkinter GUI 界面 ---


class AESApp:

    def __init__(self, master):

        self.master = master

        master.title("AES 對稱加解密工具 (Tkinter)")


        # 暫存 Nonce 和 Tag,以便解密時使用

        self.nonce_storage = tk.StringVar()

        self.tag_storage = tk.StringVar()


        # --- 密鑰輸入 ---

        tk.Label(master, text="**密鑰 (Key, 需 32 字元長度)**:").grid(row=0, column=0, sticky='w', padx=10, pady=5)

        self.key_entry = tk.Entry(master, width=50, show="*")

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

        self.key_entry.insert(0, "A"*32) 


        # --- 加密區塊 ---

        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=50)

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

        

        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):").grid(row=4, column=0, sticky='w', padx=10, pady=5)

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

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


        # --- 解密區塊 ---

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

        

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

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

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

        

        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=50)

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

        

        # 顯示 Nonce 和 Tag

        tk.Label(master, text="Nonce (用於 EAX):", fg='gray').grid(row=9, column=0, sticky='w', padx=10, pady=2)

        tk.Label(master, textvariable=self.nonce_storage, fg='gray', wraplength=400).grid(row=9, column=1, sticky='w', padx=10, pady=2)

        

        tk.Label(master, text="Tag (認證標籤):", fg='gray').grid(row=10, column=0, sticky='w', padx=10, pady=2)

        tk.Label(master, textvariable=self.tag_storage, fg='gray', wraplength=400).grid(row=10, column=1, sticky='w', padx=10, pady=2)


        # --- 新增的控制按鈕區塊 ---

        

        control_frame = tk.Frame(master)

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

        

        # 1. 清除按鈕

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

        clear_button.pack(side=tk.LEFT, padx=10)

        

        # 2. 結束按鈕

        quit_button = tk.Button(control_frame, text="❌ 結束離開", command=master.quit, bg='#FFB6C1')

        quit_button.pack(side=tk.LEFT, padx=10)



    def handle_encrypt(self):

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

        key_str = self.key_entry.get()

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

        

        if not key_str or not plaintext:

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

            return


        result = aes_encrypt(key_str, plaintext)

        

        if result:

            b64_ciphertext, b64_nonce, b64_tag = result

            

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

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

            

            self.nonce_storage.set(b64_nonce)

            self.tag_storage.set(b64_tag)

            

            # 自動複製到解密輸入框

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

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

            messagebox.showinfo("成功", "加密完成!請將密文、Nonce 和 Tag 一同傳輸。")


    def handle_decrypt(self):

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

        key_str = self.key_entry.get()

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

        b64_nonce = self.nonce_storage.get()

        b64_tag = self.tag_storage.get()

        

        if not key_str or not b64_ciphertext or not b64_nonce or not b64_tag:

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

            return


        decrypted_text = aes_decrypt(key_str, b64_ciphertext, b64_nonce, b64_tag)

        

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

        if decrypted_text is not None:

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

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


    def clear_fields(self):

        """清除所有 Text 欄位和儲存的 Nonce/Tag。"""

        

        # 清空 Text widgets

        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)

        

        # 清空儲存的 Nonce 和 Tag

        self.nonce_storage.set("")

        self.tag_storage.set("")

        

        # 保持密鑰欄位不變 (可選,這裡選擇保留方便連續測試)

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

        

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



if __name__ == "__main__":

    root = tk.Tk()

    app = AESApp(root)

    root.mainloop()


非對稱加密 (Asymmetric Encryption)

  • 密鑰數量: 一對 (公鑰和私鑰)。

  • 運作方式:

    • 公鑰 (Public Key): 可以公開分享,用於加密訊息。

    • 私鑰 (Private Key): 必須嚴格保密,用於解密公鑰加密的訊息。

    • 用公鑰加密的數據,只能用對應的私鑰解密。

  • 優點:

    • 無需安全通道共享密鑰:公鑰可以公開傳輸,解決了密鑰交換的難題。

    • 可用於數位簽章:私鑰可用於簽署文件,公鑰用於驗證簽章,以確保資料完整性和不可否認性。

  • 缺點:

    • 速度慢:運算複雜度高,加密和解密速度比對稱加密慢得多。

    • 密鑰長度較長:通常需要更大的密鑰長度來達到與對稱加密相同的安全等級 (例如 2048 位元)。

  • 應用案例:

    • 安全密鑰交換:在 SSL/TLS (HTTPS) 協定中,用於安全地交換對稱加密的工作階段密鑰

    • 數位憑證:用於驗證網站伺服器的身份。

    • 數位簽章:確保訊息發送者的身份和訊息未被篡改。






測試明文: 

RFID 電磁反向散射耦合系統是一種遠距離的 RFID 通訊方式,它利用讀寫器發射電磁波,當電磁波到達電子標籤時,標籤會將接收到的部分能量轉化為自身工作能量,並將接收到的部分電磁波以一種「反射」的方式(稱為反向散射)將資料傳送回讀寫器


import tkinter as tk
from tkinter import messagebox
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from base64 import b64encode, b64decode
import os

# --- RSA 密鑰生成與加解密核心功能 ---

class RSAEncryptor:
    """
    處理 RSA 密鑰生成和加解密操作的類別。
    使用 PKCS1_OAEP 模式確保安全性。
    """
    def __init__(self, key_length=2048):
        self.key_length = key_length
        self.private_key = None
        self.public_key = None
        
    def generate_keys(self):
        """生成 RSA 密鑰對 (私鑰和公鑰)。"""
        try:
            # 1. 生成私鑰 (包含公鑰資訊)
            self.private_key = RSA.generate(self.key_length)
            # 2. 從私鑰中提取公鑰
            self.public_key = self.private_key.publickey()
            
            # 將密鑰轉換為 PEM 格式字串以便在 GUI 中顯示
            private_pem = self.private_key.export_key().decode('utf-8')
            public_pem = self.public_key.export_key().decode('utf-8')
            
            return private_pem, public_pem
        except Exception as e:
            messagebox.showerror("密鑰生成錯誤", f"RSA 密鑰生成失敗: {e}")
            return None, None
            
    def encrypt(self, public_pem, plaintext):
        """使用公鑰加密明文。"""
        try:
            # 導入公鑰
            key = RSA.import_key(public_pem)
            # 創建 PKCS1 OAEP 加密器 (較 PKCS1_v1_5 更安全)
            cipher_rsa = PKCS1_OAEP.new(key)
            
            data = plaintext.encode('utf-8')
            
            # 檢查數據長度 (RSA 有加密數據塊長度限制)
            max_len = self.key_length // 8 - 42 # PKCS1_OAEP 的長度限制
            if len(data) > max_len:
                messagebox.showwarning("警告", f"明文過長 ({len(data)} 位元組)。RSA 建議最大長度約為 {max_len} 位元組。")
                data = data[:max_len] # 截斷處理

            ciphertext = cipher_rsa.encrypt(data)
            
            # 將二進制密文轉換為 Base64 字符串
            return b64encode(ciphertext).decode('utf-8')

        except Exception as e:
            messagebox.showerror("加密錯誤", f"RSA 加密失敗: {e}")
            return None

    def decrypt(self, private_pem, base64_ciphertext):
        """使用私鑰解密密文。"""
        try:
            # 導入私鑰
            key = RSA.import_key(private_pem)
            # 創建 PKCS1 OAEP 解密器
            cipher_rsa = PKCS1_OAEP.new(key)
            
            # 將 Base64 密文解碼為二進制數據
            ciphertext = b64decode(base64_ciphertext)
            
            plaintext = cipher_rsa.decrypt(ciphertext)
            
            return plaintext.decode('utf-8')

        except ValueError as e:
            messagebox.showerror("解密錯誤", "私鑰錯誤、密文被篡改或格式不正確。")
            return None
        except Exception as e:
            messagebox.showerror("解密錯誤", f"RSA 解密失敗: {e}")
            return None


# --- Tkinter GUI 界面 ---

class RSAApp:
    def __init__(self, master):
        self.master = master
        master.title("RSA 非對稱加解密工具 (Tkinter)")

        self.rsa_encryptor = RSAEncryptor()

        # 設定 Grid 佈局
        self.setup_ui_elements()

        # 自動生成密鑰
        self.generate_and_display_keys()

    def setup_ui_elements(self):
        """初始化所有的 GUI 組件。"""
        
        # --- 密鑰顯示區 ---
        key_frame = tk.LabelFrame(self.master, text="🔐 RSA 密鑰對 (2048-bit)", padx=5, pady=5)
        key_frame.grid(row=0, column=0, columnspan=2, padx=10, pady=10, sticky='ew')
        
        tk.Label(key_frame, text="公鑰 (Public Key):").grid(row=0, column=0, sticky='w', padx=5, pady=2)
        self.public_key_text = tk.Text(key_frame, height=5, width=65)
        self.public_key_text.grid(row=1, column=0, padx=5, pady=2)
        
        tk.Label(key_frame, text="私鑰 (Private Key):").grid(row=2, column=0, sticky='w', padx=5, pady=2)
        self.private_key_text = tk.Text(key_frame, height=5, width=65)
        self.private_key_text.grid(row=3, column=0, padx=5, pady=2)
        
        tk.Button(key_frame, text="重新生成密鑰", command=self.generate_and_display_keys, bg='#ADD8E6').grid(row=4, column=0, pady=5)

        # --- 加密區塊 ---
        encrypt_frame = tk.LabelFrame(self.master, text="🔒 加密 (使用公鑰)", padx=5, pady=5)
        encrypt_frame.grid(row=1, column=0, padx=10, pady=5, sticky='ew')
        
        tk.Label(encrypt_frame, text="輸入明文:").pack(padx=5, pady=2, anchor='w')
        self.plaintext_entry = tk.Text(encrypt_frame, height=3, width=30)
        self.plaintext_entry.pack(padx=5, pady=2)
        
        tk.Button(encrypt_frame, text="公鑰加密", command=self.handle_encrypt, bg='lightgreen').pack(pady=5)
        
        tk.Label(encrypt_frame, text="加密結果 (密文):").pack(padx=5, pady=2, anchor='w')
        self.ciphertext_output = tk.Text(encrypt_frame, height=5, width=30)
        self.ciphertext_output.pack(padx=5, pady=2)

        # --- 解密區塊 ---
        decrypt_frame = tk.LabelFrame(self.master, text="🔓 解密 (使用私鑰)", padx=5, pady=5)
        decrypt_frame.grid(row=1, column=1, padx=10, pady=5, sticky='ew')
        
        tk.Label(decrypt_frame, text="輸入密文 (需 Base64 編碼):").pack(padx=5, pady=2, anchor='w')
        self.decrypt_ciphertext_input = tk.Text(decrypt_frame, height=5, width=30)
        self.decrypt_ciphertext_input.pack(padx=5, pady=2)
        
        tk.Button(decrypt_frame, text="私鑰解密", command=self.handle_decrypt, bg='salmon').pack(pady=5)
        
        tk.Label(decrypt_frame, text="解密結果 (明文):").pack(padx=5, pady=2, anchor='w')
        self.decrypted_output = tk.Text(decrypt_frame, height=3, width=30)
        self.decrypted_output.pack(padx=5, pady=2)
        
        # --- 控制按鈕區塊 ---
        control_frame = tk.Frame(self.master)
        control_frame.grid(row=2, column=0, columnspan=2, pady=10)
        
        tk.Button(control_frame, text="🗑️ 清除所有欄位", command=self.clear_fields, bg='#FFFACD').pack(side=tk.LEFT, padx=10)
        tk.Button(control_frame, text="❌ 結束離開", command=self.master.quit, bg='#FFB6C1').pack(side=tk.LEFT, padx=10)


    def generate_and_display_keys(self):
        """生成並在 Text 欄位中顯示新的密鑰。"""
        private_pem, public_pem = self.rsa_encryptor.generate_keys()
        
        if private_pem and public_pem:
            # 清空並顯示私鑰
            self.private_key_text.delete("1.0", tk.END)
            self.private_key_text.insert(tk.END, private_pem)
            
            # 清空並顯示公鑰
            self.public_key_text.delete("1.0", tk.END)
            self.public_key_text.insert(tk.END, public_pem)
            
            messagebox.showinfo("密鑰生成", f"已成功生成 {self.rsa_encryptor.key_length}-bit RSA 密鑰對!")
            self.clear_fields(keep_keys=True) # 清空數據欄位

    def handle_encrypt(self):
        """處理加密按鈕事件:公鑰加密明文。"""
        public_pem = self.public_key_text.get("1.0", tk.END).strip()
        plaintext = self.plaintext_entry.get("1.0", tk.END).strip()
        
        if not public_pem or not plaintext:
            messagebox.showwarning("警告", "公鑰和明文都不能為空!")
            return

        b64_ciphertext = self.rsa_encryptor.encrypt(public_pem, plaintext)
        
        if b64_ciphertext:
            # 顯示密文
            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)


    def handle_decrypt(self):
        """處理解密按鈕事件:私鑰解密密文。"""
        private_pem = self.private_key_text.get("1.0", tk.END).strip()
        b64_ciphertext = self.decrypt_ciphertext_input.get("1.0", tk.END).strip()
        
        if not private_pem or not b64_ciphertext:
            messagebox.showwarning("警告", "私鑰和密文都不能為空!")
            return

        decrypted_text = self.rsa_encryptor.decrypt(private_pem, b64_ciphertext)
        
        self.decrypted_output.delete("1.0", tk.END)
        if decrypted_text is not None:
            self.decrypted_output.insert(tk.END, decrypted_text)
            messagebox.showinfo("成功", "解密完成!")

    def clear_fields(self, keep_keys=False):
        """清除所有 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)
        
        # 選擇是否保留密鑰欄位
        if not keep_keys:
            self.private_key_text.delete("1.0", tk.END)
            self.public_key_text.delete("1.0", tk.END)
        
        if not keep_keys:
            messagebox.showinfo("清除成功", "所有欄位已清空。")
        


if __name__ == "__main__":
    root = tk.Tk()
    app = RSAApp(root)
    root.mainloop()


總結比較表格

特性對稱加密 (Symmetric)非對稱加密 (Asymmetric)
密鑰數量一個 (加密/解密都用同一個)一對 (公鑰用於加密,私鑰用於解密)
運算速度 (高效) (運算複雜)
安全性依賴密鑰安全交換密鑰交換較安全,但運算複雜度較高
密鑰共享必須透過安全通道傳輸密鑰公鑰可公開,私鑰保密
典型算法AES、DES、3DESRSA、ECC
主要用途大量數據加密傳輸密鑰交換、數位簽章、身份驗證

實際應用中 (例如 HTTPS),通常會結合兩者

  1. 首先使用非對稱加密 (較慢但安全) 來安全地交換一個對稱加密的工作階段密鑰

  2. 然後使用交換到的對稱加密 (較快且高效) 來傳輸實際的大量資料

沒有留言:

張貼留言

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...