中級者向け No.32

暗号化・復号ツール

テキスト・ファイルをAES/RSA暗号化・復号するツール。cryptographyライブラリで本格的な暗号化を実装します。

🎯 難易度: ★★★ 📦 ライブラリ: tkinter(標準ライブラリ), cryptography ⏱️ 制作時間: 30〜90分

1. アプリ概要

テキスト・ファイルをAES/RSA暗号化・復号するツール。cryptographyライブラリで本格的な暗号化を実装します。

このアプリは中級カテゴリに分類される実践的なGUIアプリです。使用ライブラリは tkinter(標準ライブラリ)・cryptography で、難易度は ★★★ です。

Pythonでは tkinter を使うことで、クロスプラットフォームなGUIアプリを簡単に作成できます。このアプリを通じて、ウィジェットの配置・イベント処理・データ管理など、GUI開発の実践的なスキルを習得できます。

ソースコードは完全な動作状態で提供しており、コピーしてそのまま実行できます。まずは実行して動作を確認し、その後コードを読んで仕組みを理解していきましょう。カスタマイズセクションでは機能拡張のアイデアも紹介しています。

GUIアプリ開発は、プログラミングの楽しさを実感できる最も効果的な学習方法のひとつです。アプリを作ることで、変数・関数・クラス・イベント処理など、プログラミングの重要な概念が自然と身についていきます。このアプリをきっかけに、オリジナルアプリの開発にも挑戦してみてください。

2. 機能一覧

  • 暗号化・復号ツールのメイン機能
  • 直感的なGUIインターフェース
  • 入力値のバリデーション
  • エラーハンドリング
  • 結果の見やすい表示
  • キーボードショートカット対応

3. 事前準備・環境

ℹ️
動作確認環境

Python 3.10 以上 / Windows・Mac・Linux すべて対応

以下の環境で動作確認しています。

  • Python 3.10 以上
  • OS: Windows 10/11・macOS 12+・Ubuntu 20.04+

4. 完全なソースコード

💡
コードのコピー方法

右上の「コピー」ボタンをクリックするとコードをクリップボードにコピーできます。

app32.py
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import os
import base64
import hashlib
import secrets

try:
    from cryptography.fernet import Fernet
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import rsa, padding
    from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
    CRYPTO_AVAILABLE = True
except ImportError:
    CRYPTO_AVAILABLE = False


class App32:
    """暗号化・復号ツール"""

    def __init__(self, root):
        self.root = root
        self.root.title("暗号化・復号ツール")
        self.root.geometry("860x620")
        self.root.configure(bg="#1a1a2e")
        self._fernet = None
        self._rsa_private = None
        self._rsa_public = None
        self._build_ui()

    def _build_ui(self):
        # ヘッダー
        header = tk.Frame(self.root, bg="#16213e", pady=8)
        header.pack(fill=tk.X)
        tk.Label(header, text="🔐 暗号化・復号ツール",
                 font=("Noto Sans JP", 13, "bold"),
                 bg="#16213e", fg="#e94560").pack(side=tk.LEFT, padx=12)

        if not CRYPTO_AVAILABLE:
            tk.Label(self.root,
                     text="⚠ cryptographyライブラリが未インストールです "
                          "(pip install cryptography)。",
                     bg="#fff3cd", fg="#856404", font=("Arial", 9),
                     anchor="w", padx=8).pack(fill=tk.X)

        notebook = ttk.Notebook(self.root)
        notebook.pack(fill=tk.BOTH, expand=True, padx=6, pady=6)

        # ── タブ1: Fernet(AES-128 CBC)──────────────────────────
        fernet_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(fernet_tab, text="🔑 Fernet (AES)")
        self._build_fernet_tab(fernet_tab)

        # ── タブ2: RSA ──────────────────────────────────────────
        rsa_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(rsa_tab, text="🔒 RSA")
        self._build_rsa_tab(rsa_tab)

        # ── タブ3: ハッシュ ─────────────────────────────────────
        hash_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(hash_tab, text="🔎 ハッシュ")
        self._build_hash_tab(hash_tab)

        # ── タブ4: Base64 ───────────────────────────────────────
        b64_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(b64_tab, text="📄 Base64")
        self._build_b64_tab(b64_tab)

        self.status_var = tk.StringVar(value="アルゴリズムを選択してください")
        tk.Label(self.root, textvariable=self.status_var,
                 bg="#16213e", fg="#858585", font=("Arial", 9),
                 anchor="w", padx=8).pack(fill=tk.X, side=tk.BOTTOM)

    # ── Fernet タブ ──────────────────────────────────────────────

    def _build_fernet_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}

        # キー設定
        key_f = ttk.LabelFrame(parent, text="キー設定", padding=8)
        key_f.pack(fill=tk.X, padx=8, pady=6)
        row1 = tk.Frame(key_f, bg=key_f.cget("background"))
        row1.pack(fill=tk.X)
        tk.Label(row1, text="パスワード:", **lbl_s,
                 bg=row1.cget("bg")).pack(side=tk.LEFT)
        self.fernet_pw_var = tk.StringVar()
        ttk.Entry(row1, textvariable=self.fernet_pw_var, show="*",
                  width=30).pack(side=tk.LEFT, padx=6)
        ttk.Button(row1, text="🔑 キー生成",
                   command=self._gen_fernet_key).pack(side=tk.LEFT, padx=4)
        ttk.Button(row1, text="🎲 ランダム生成",
                   command=self._random_fernet_key).pack(side=tk.LEFT, padx=4)

        row2 = tk.Frame(key_f, bg=key_f.cget("background"))
        row2.pack(fill=tk.X, pady=4)
        tk.Label(row2, text="Fernetキー:", **lbl_s,
                 bg=row2.cget("bg")).pack(side=tk.LEFT)
        self.fernet_key_var = tk.StringVar()
        ttk.Entry(row2, textvariable=self.fernet_key_var,
                  width=60).pack(side=tk.LEFT, padx=6, fill=tk.X, expand=True)

        # テキスト入出力
        io_f = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        io_f.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

        left = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(left, weight=1)
        tk.Label(left, text="入力テキスト", **lbl_s).pack(anchor="w", pady=2)
        self.fernet_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                     font=("Courier New", 11), relief=tk.FLAT,
                                     height=12)
        self.fernet_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(right, weight=1)
        tk.Label(right, text="出力テキスト", **lbl_s).pack(anchor="w", pady=2)
        self.fernet_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                      font=("Courier New", 11), relief=tk.FLAT,
                                      height=12, state=tk.DISABLED)
        self.fernet_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="🔒 暗号化",
                   command=self._fernet_encrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="🔓 復号",
                   command=self._fernet_decrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="📋 出力をコピー",
                   command=lambda: self._copy_output(self.fernet_output)
                   ).pack(side=tk.LEFT, padx=8)

    def _gen_fernet_key(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        pw = self.fernet_pw_var.get()
        if not pw:
            messagebox.showwarning("警告", "パスワードを入力してください")
            return
        salt = b"pythonland_salt_"
        kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32,
                          salt=salt, iterations=100000)
        key = base64.urlsafe_b64encode(kdf.derive(pw.encode()))
        self.fernet_key_var.set(key.decode())
        self._fernet = Fernet(key)
        self.status_var.set("Fernetキーを生成しました")

    def _random_fernet_key(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        key = Fernet.generate_key()
        self.fernet_key_var.set(key.decode())
        self._fernet = Fernet(key)
        self.status_var.set("ランダムFernetキーを生成しました")

    def _get_fernet(self):
        key_str = self.fernet_key_var.get().strip()
        if not key_str:
            messagebox.showwarning("警告", "キーを生成してください")
            return None
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return None
        try:
            return Fernet(key_str.encode())
        except Exception as e:
            messagebox.showerror("エラー", f"無効なキー: {e}")
            return None

    def _fernet_encrypt(self):
        f = self._get_fernet()
        if not f:
            return
        text = self.fernet_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            token = f.encrypt(text.encode("utf-8"))
            self._set_output(self.fernet_output, token.decode())
            self.status_var.set("暗号化しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _fernet_decrypt(self):
        f = self._get_fernet()
        if not f:
            return
        text = self.fernet_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            plain = f.decrypt(text.encode())
            self._set_output(self.fernet_output, plain.decode("utf-8"))
            self.status_var.set("復号しました")
        except Exception as e:
            messagebox.showerror("復号エラー",
                                 "復号に失敗しました。キーまたはデータが不正です。\n" + str(e))

    # ── RSA タブ ──────────────────────────────────────────────────

    def _build_rsa_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}

        key_f = ttk.LabelFrame(parent, text="RSAキーペア生成", padding=8)
        key_f.pack(fill=tk.X, padx=8, pady=6)
        btn_row = tk.Frame(key_f, bg=key_f.cget("background"))
        btn_row.pack(fill=tk.X)
        tk.Label(btn_row, text="鍵長:", bg=btn_row.cget("bg"),
                 fg="#c9d1d9").pack(side=tk.LEFT)
        self.rsa_bits_var = tk.StringVar(value="2048")
        ttk.Combobox(btn_row, textvariable=self.rsa_bits_var,
                     values=["1024", "2048", "4096"],
                     state="readonly", width=8).pack(side=tk.LEFT, padx=6)
        ttk.Button(btn_row, text="🔑 キーペア生成",
                   command=self._gen_rsa_keys).pack(side=tk.LEFT, padx=8)

        paned = ttk.PanedWindow(key_f, orient=tk.HORIZONTAL)
        paned.pack(fill=tk.X, pady=4)
        for title, attr in [("公開鍵 (暗号化用)", "rsa_pub_text"),
                             ("秘密鍵 (復号用)", "rsa_priv_text")]:
            f = tk.Frame(paned, bg="#1a1a2e")
            paned.add(f, weight=1)
            tk.Label(f, text=title, **lbl_s).pack(anchor="w")
            txt = tk.Text(f, bg="#0d1117", fg="#c9d1d9",
                          font=("Courier New", 8), height=6, state=tk.DISABLED)
            txt.pack(fill=tk.X)
            setattr(self, attr, txt)

        io_f = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        io_f.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

        left = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(left, weight=1)
        tk.Label(left, text="入力テキスト", **lbl_s).pack(anchor="w")
        self.rsa_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                  font=("Courier New", 11), height=8)
        self.rsa_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(right, weight=1)
        tk.Label(right, text="出力テキスト", **lbl_s).pack(anchor="w")
        self.rsa_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                   font=("Courier New", 11), height=8,
                                   state=tk.DISABLED)
        self.rsa_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="🔒 公開鍵で暗号化",
                   command=self._rsa_encrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="🔓 秘密鍵で復号",
                   command=self._rsa_decrypt).pack(side=tk.LEFT, padx=8)

    def _gen_rsa_keys(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        bits = int(self.rsa_bits_var.get())
        self.status_var.set(f"RSA {bits}bitキーを生成中...")
        self.root.update()
        try:
            self._rsa_private = rsa.generate_private_key(
                public_exponent=65537, key_size=bits)
            self._rsa_public = self._rsa_private.public_key()
            priv_pem = self._rsa_private.private_bytes(
                serialization.Encoding.PEM,
                serialization.PrivateFormat.TraditionalOpenSSL,
                serialization.NoEncryption()).decode()
            pub_pem = self._rsa_public.public_bytes(
                serialization.Encoding.PEM,
                serialization.PublicFormat.SubjectPublicKeyInfo).decode()
            self._set_output(self.rsa_pub_text, pub_pem)
            self._set_output(self.rsa_priv_text, priv_pem)
            self.status_var.set(f"RSA {bits}bitキーペアを生成しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _rsa_encrypt(self):
        if not CRYPTO_AVAILABLE or not self._rsa_public:
            messagebox.showwarning("警告", "先にキーペアを生成してください")
            return
        text = self.rsa_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            cipher = self._rsa_public.encrypt(
                text.encode("utf-8"),
                padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                             algorithm=hashes.SHA256(), label=None))
            self._set_output(self.rsa_output, base64.b64encode(cipher).decode())
            self.status_var.set("RSA暗号化しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _rsa_decrypt(self):
        if not CRYPTO_AVAILABLE or not self._rsa_private:
            messagebox.showwarning("警告", "先にキーペアを生成してください")
            return
        text = self.rsa_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            cipher = base64.b64decode(text)
            plain = self._rsa_private.decrypt(
                cipher,
                padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                             algorithm=hashes.SHA256(), label=None))
            self._set_output(self.rsa_output, plain.decode("utf-8"))
            self.status_var.set("RSA復号しました")
        except Exception as e:
            messagebox.showerror("復号エラー", str(e))

    # ── ハッシュ タブ ──────────────────────────────────────────────

    def _build_hash_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}
        tk.Label(parent, text="テキスト:", **lbl_s).pack(anchor="w", padx=8, pady=4)
        self.hash_input = tk.Text(parent, bg="#0d1117", fg="#c9d1d9",
                                   font=("Courier New", 11), height=6,
                                   relief=tk.FLAT)
        self.hash_input.pack(fill=tk.X, padx=8)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(fill=tk.X, padx=8, pady=6)
        for algo in ["MD5", "SHA1", "SHA256", "SHA512", "SHA3-256"]:
            ttk.Button(btn_f, text=algo,
                       command=lambda a=algo: self._compute_hash(a)
                       ).pack(side=tk.LEFT, padx=4)

        # ファイルハッシュ
        file_f = ttk.LabelFrame(parent, text="ファイルハッシュ", padding=6)
        file_f.pack(fill=tk.X, padx=8, pady=4)
        file_row = tk.Frame(file_f, bg=file_f.cget("background"))
        file_row.pack(fill=tk.X)
        self.hash_file_var = tk.StringVar()
        ttk.Entry(file_row, textvariable=self.hash_file_var,
                  width=40).pack(side=tk.LEFT, fill=tk.X, expand=True)
        ttk.Button(file_row, text="参照",
                   command=self._browse_hash_file).pack(side=tk.LEFT, padx=4)
        ttk.Button(file_row, text="SHA256 計算",
                   command=self._file_hash).pack(side=tk.LEFT, padx=4)

        tk.Label(parent, text="ハッシュ結果:", **lbl_s).pack(anchor="w", padx=8, pady=4)
        self.hash_output = tk.Text(parent, bg="#0d1117", fg="#3fb950",
                                    font=("Courier New", 11), height=10,
                                    state=tk.DISABLED, relief=tk.FLAT)
        self.hash_output.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

    def _compute_hash(self, algo):
        text = self.hash_input.get("1.0", tk.END).strip()
        data = text.encode("utf-8")
        algo_map = {
            "MD5": hashlib.md5,
            "SHA1": hashlib.sha1,
            "SHA256": hashlib.sha256,
            "SHA512": hashlib.sha512,
            "SHA3-256": hashlib.sha3_256,
        }
        h = algo_map[algo](data).hexdigest()
        result = f"[{algo}]\n{h}\n\n"
        self.hash_output.config(state=tk.NORMAL)
        self.hash_output.insert(tk.END, result)
        self.hash_output.config(state=tk.DISABLED)
        self.status_var.set(f"{algo}: {h[:20]}...")

    def _browse_hash_file(self):
        path = filedialog.askopenfilename()
        if path:
            self.hash_file_var.set(path)

    def _file_hash(self):
        path = self.hash_file_var.get()
        if not path or not os.path.exists(path):
            messagebox.showwarning("警告", "ファイルを選択してください")
            return
        h = hashlib.sha256()
        with open(path, "rb") as f:
            for chunk in iter(lambda: f.read(65536), b""):
                h.update(chunk)
        result = f"[SHA256] {os.path.basename(path)}\n{h.hexdigest()}\n\n"
        self.hash_output.config(state=tk.NORMAL)
        self.hash_output.insert(tk.END, result)
        self.hash_output.config(state=tk.DISABLED)

    # ── Base64 タブ ──────────────────────────────────────────────

    def _build_b64_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}
        paned = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        paned.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)

        left = tk.Frame(paned, bg="#1a1a2e")
        paned.add(left, weight=1)
        tk.Label(left, text="入力", **lbl_s).pack(anchor="w")
        self.b64_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                  font=("Courier New", 11), relief=tk.FLAT)
        self.b64_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(paned, bg="#1a1a2e")
        paned.add(right, weight=1)
        tk.Label(right, text="出力", **lbl_s).pack(anchor="w")
        self.b64_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                   font=("Courier New", 11), relief=tk.FLAT,
                                   state=tk.DISABLED)
        self.b64_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="▶ Base64 エンコード",
                   command=self._b64_encode).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="◀ Base64 デコード",
                   command=self._b64_decode).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="📋 コピー",
                   command=lambda: self._copy_output(self.b64_output)
                   ).pack(side=tk.LEFT, padx=8)

    def _b64_encode(self):
        text = self.b64_input.get("1.0", tk.END).strip()
        encoded = base64.b64encode(text.encode("utf-8")).decode()
        self._set_output(self.b64_output, encoded)
        self.status_var.set("Base64エンコードしました")

    def _b64_decode(self):
        text = self.b64_input.get("1.0", tk.END).strip()
        try:
            decoded = base64.b64decode(text).decode("utf-8")
            self._set_output(self.b64_output, decoded)
            self.status_var.set("Base64デコードしました")
        except Exception as e:
            messagebox.showerror("エラー", f"デコード失敗: {e}")

    # ── ユーティリティ ────────────────────────────────────────────

    def _set_output(self, widget, text):
        widget.config(state=tk.NORMAL)
        widget.delete("1.0", tk.END)
        widget.insert("1.0", text)
        widget.config(state=tk.DISABLED)

    def _copy_output(self, widget):
        text = widget.get("1.0", tk.END).strip()
        self.root.clipboard_clear()
        self.root.clipboard_append(text)
        self.status_var.set("クリップボードにコピーしました")


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

5. コード解説

暗号化・復号ツールのコードを詳しく解説します。クラスベースの設計で各機能を整理して実装しています。

クラス設計とコンストラクタ

App32クラスにアプリの全機能をまとめています。__init__メソッドでウィンドウの基本設定を行い、_build_ui()でUI構築、process()でメイン処理を担当します。この分離により、各メソッドの責任が明確になりコードが読みやすくなります。

import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import os
import base64
import hashlib
import secrets

try:
    from cryptography.fernet import Fernet
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import rsa, padding
    from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
    CRYPTO_AVAILABLE = True
except ImportError:
    CRYPTO_AVAILABLE = False


class App32:
    """暗号化・復号ツール"""

    def __init__(self, root):
        self.root = root
        self.root.title("暗号化・復号ツール")
        self.root.geometry("860x620")
        self.root.configure(bg="#1a1a2e")
        self._fernet = None
        self._rsa_private = None
        self._rsa_public = None
        self._build_ui()

    def _build_ui(self):
        # ヘッダー
        header = tk.Frame(self.root, bg="#16213e", pady=8)
        header.pack(fill=tk.X)
        tk.Label(header, text="🔐 暗号化・復号ツール",
                 font=("Noto Sans JP", 13, "bold"),
                 bg="#16213e", fg="#e94560").pack(side=tk.LEFT, padx=12)

        if not CRYPTO_AVAILABLE:
            tk.Label(self.root,
                     text="⚠ cryptographyライブラリが未インストールです "
                          "(pip install cryptography)。",
                     bg="#fff3cd", fg="#856404", font=("Arial", 9),
                     anchor="w", padx=8).pack(fill=tk.X)

        notebook = ttk.Notebook(self.root)
        notebook.pack(fill=tk.BOTH, expand=True, padx=6, pady=6)

        # ── タブ1: Fernet(AES-128 CBC)──────────────────────────
        fernet_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(fernet_tab, text="🔑 Fernet (AES)")
        self._build_fernet_tab(fernet_tab)

        # ── タブ2: RSA ──────────────────────────────────────────
        rsa_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(rsa_tab, text="🔒 RSA")
        self._build_rsa_tab(rsa_tab)

        # ── タブ3: ハッシュ ─────────────────────────────────────
        hash_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(hash_tab, text="🔎 ハッシュ")
        self._build_hash_tab(hash_tab)

        # ── タブ4: Base64 ───────────────────────────────────────
        b64_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(b64_tab, text="📄 Base64")
        self._build_b64_tab(b64_tab)

        self.status_var = tk.StringVar(value="アルゴリズムを選択してください")
        tk.Label(self.root, textvariable=self.status_var,
                 bg="#16213e", fg="#858585", font=("Arial", 9),
                 anchor="w", padx=8).pack(fill=tk.X, side=tk.BOTTOM)

    # ── Fernet タブ ──────────────────────────────────────────────

    def _build_fernet_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}

        # キー設定
        key_f = ttk.LabelFrame(parent, text="キー設定", padding=8)
        key_f.pack(fill=tk.X, padx=8, pady=6)
        row1 = tk.Frame(key_f, bg=key_f.cget("background"))
        row1.pack(fill=tk.X)
        tk.Label(row1, text="パスワード:", **lbl_s,
                 bg=row1.cget("bg")).pack(side=tk.LEFT)
        self.fernet_pw_var = tk.StringVar()
        ttk.Entry(row1, textvariable=self.fernet_pw_var, show="*",
                  width=30).pack(side=tk.LEFT, padx=6)
        ttk.Button(row1, text="🔑 キー生成",
                   command=self._gen_fernet_key).pack(side=tk.LEFT, padx=4)
        ttk.Button(row1, text="🎲 ランダム生成",
                   command=self._random_fernet_key).pack(side=tk.LEFT, padx=4)

        row2 = tk.Frame(key_f, bg=key_f.cget("background"))
        row2.pack(fill=tk.X, pady=4)
        tk.Label(row2, text="Fernetキー:", **lbl_s,
                 bg=row2.cget("bg")).pack(side=tk.LEFT)
        self.fernet_key_var = tk.StringVar()
        ttk.Entry(row2, textvariable=self.fernet_key_var,
                  width=60).pack(side=tk.LEFT, padx=6, fill=tk.X, expand=True)

        # テキスト入出力
        io_f = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        io_f.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

        left = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(left, weight=1)
        tk.Label(left, text="入力テキスト", **lbl_s).pack(anchor="w", pady=2)
        self.fernet_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                     font=("Courier New", 11), relief=tk.FLAT,
                                     height=12)
        self.fernet_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(right, weight=1)
        tk.Label(right, text="出力テキスト", **lbl_s).pack(anchor="w", pady=2)
        self.fernet_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                      font=("Courier New", 11), relief=tk.FLAT,
                                      height=12, state=tk.DISABLED)
        self.fernet_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="🔒 暗号化",
                   command=self._fernet_encrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="🔓 復号",
                   command=self._fernet_decrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="📋 出力をコピー",
                   command=lambda: self._copy_output(self.fernet_output)
                   ).pack(side=tk.LEFT, padx=8)

    def _gen_fernet_key(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        pw = self.fernet_pw_var.get()
        if not pw:
            messagebox.showwarning("警告", "パスワードを入力してください")
            return
        salt = b"pythonland_salt_"
        kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32,
                          salt=salt, iterations=100000)
        key = base64.urlsafe_b64encode(kdf.derive(pw.encode()))
        self.fernet_key_var.set(key.decode())
        self._fernet = Fernet(key)
        self.status_var.set("Fernetキーを生成しました")

    def _random_fernet_key(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        key = Fernet.generate_key()
        self.fernet_key_var.set(key.decode())
        self._fernet = Fernet(key)
        self.status_var.set("ランダムFernetキーを生成しました")

    def _get_fernet(self):
        key_str = self.fernet_key_var.get().strip()
        if not key_str:
            messagebox.showwarning("警告", "キーを生成してください")
            return None
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return None
        try:
            return Fernet(key_str.encode())
        except Exception as e:
            messagebox.showerror("エラー", f"無効なキー: {e}")
            return None

    def _fernet_encrypt(self):
        f = self._get_fernet()
        if not f:
            return
        text = self.fernet_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            token = f.encrypt(text.encode("utf-8"))
            self._set_output(self.fernet_output, token.decode())
            self.status_var.set("暗号化しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _fernet_decrypt(self):
        f = self._get_fernet()
        if not f:
            return
        text = self.fernet_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            plain = f.decrypt(text.encode())
            self._set_output(self.fernet_output, plain.decode("utf-8"))
            self.status_var.set("復号しました")
        except Exception as e:
            messagebox.showerror("復号エラー",
                                 "復号に失敗しました。キーまたはデータが不正です。\n" + str(e))

    # ── RSA タブ ──────────────────────────────────────────────────

    def _build_rsa_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}

        key_f = ttk.LabelFrame(parent, text="RSAキーペア生成", padding=8)
        key_f.pack(fill=tk.X, padx=8, pady=6)
        btn_row = tk.Frame(key_f, bg=key_f.cget("background"))
        btn_row.pack(fill=tk.X)
        tk.Label(btn_row, text="鍵長:", bg=btn_row.cget("bg"),
                 fg="#c9d1d9").pack(side=tk.LEFT)
        self.rsa_bits_var = tk.StringVar(value="2048")
        ttk.Combobox(btn_row, textvariable=self.rsa_bits_var,
                     values=["1024", "2048", "4096"],
                     state="readonly", width=8).pack(side=tk.LEFT, padx=6)
        ttk.Button(btn_row, text="🔑 キーペア生成",
                   command=self._gen_rsa_keys).pack(side=tk.LEFT, padx=8)

        paned = ttk.PanedWindow(key_f, orient=tk.HORIZONTAL)
        paned.pack(fill=tk.X, pady=4)
        for title, attr in [("公開鍵 (暗号化用)", "rsa_pub_text"),
                             ("秘密鍵 (復号用)", "rsa_priv_text")]:
            f = tk.Frame(paned, bg="#1a1a2e")
            paned.add(f, weight=1)
            tk.Label(f, text=title, **lbl_s).pack(anchor="w")
            txt = tk.Text(f, bg="#0d1117", fg="#c9d1d9",
                          font=("Courier New", 8), height=6, state=tk.DISABLED)
            txt.pack(fill=tk.X)
            setattr(self, attr, txt)

        io_f = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        io_f.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

        left = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(left, weight=1)
        tk.Label(left, text="入力テキスト", **lbl_s).pack(anchor="w")
        self.rsa_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                  font=("Courier New", 11), height=8)
        self.rsa_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(right, weight=1)
        tk.Label(right, text="出力テキスト", **lbl_s).pack(anchor="w")
        self.rsa_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                   font=("Courier New", 11), height=8,
                                   state=tk.DISABLED)
        self.rsa_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="🔒 公開鍵で暗号化",
                   command=self._rsa_encrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="🔓 秘密鍵で復号",
                   command=self._rsa_decrypt).pack(side=tk.LEFT, padx=8)

    def _gen_rsa_keys(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        bits = int(self.rsa_bits_var.get())
        self.status_var.set(f"RSA {bits}bitキーを生成中...")
        self.root.update()
        try:
            self._rsa_private = rsa.generate_private_key(
                public_exponent=65537, key_size=bits)
            self._rsa_public = self._rsa_private.public_key()
            priv_pem = self._rsa_private.private_bytes(
                serialization.Encoding.PEM,
                serialization.PrivateFormat.TraditionalOpenSSL,
                serialization.NoEncryption()).decode()
            pub_pem = self._rsa_public.public_bytes(
                serialization.Encoding.PEM,
                serialization.PublicFormat.SubjectPublicKeyInfo).decode()
            self._set_output(self.rsa_pub_text, pub_pem)
            self._set_output(self.rsa_priv_text, priv_pem)
            self.status_var.set(f"RSA {bits}bitキーペアを生成しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _rsa_encrypt(self):
        if not CRYPTO_AVAILABLE or not self._rsa_public:
            messagebox.showwarning("警告", "先にキーペアを生成してください")
            return
        text = self.rsa_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            cipher = self._rsa_public.encrypt(
                text.encode("utf-8"),
                padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                             algorithm=hashes.SHA256(), label=None))
            self._set_output(self.rsa_output, base64.b64encode(cipher).decode())
            self.status_var.set("RSA暗号化しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _rsa_decrypt(self):
        if not CRYPTO_AVAILABLE or not self._rsa_private:
            messagebox.showwarning("警告", "先にキーペアを生成してください")
            return
        text = self.rsa_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            cipher = base64.b64decode(text)
            plain = self._rsa_private.decrypt(
                cipher,
                padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                             algorithm=hashes.SHA256(), label=None))
            self._set_output(self.rsa_output, plain.decode("utf-8"))
            self.status_var.set("RSA復号しました")
        except Exception as e:
            messagebox.showerror("復号エラー", str(e))

    # ── ハッシュ タブ ──────────────────────────────────────────────

    def _build_hash_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}
        tk.Label(parent, text="テキスト:", **lbl_s).pack(anchor="w", padx=8, pady=4)
        self.hash_input = tk.Text(parent, bg="#0d1117", fg="#c9d1d9",
                                   font=("Courier New", 11), height=6,
                                   relief=tk.FLAT)
        self.hash_input.pack(fill=tk.X, padx=8)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(fill=tk.X, padx=8, pady=6)
        for algo in ["MD5", "SHA1", "SHA256", "SHA512", "SHA3-256"]:
            ttk.Button(btn_f, text=algo,
                       command=lambda a=algo: self._compute_hash(a)
                       ).pack(side=tk.LEFT, padx=4)

        # ファイルハッシュ
        file_f = ttk.LabelFrame(parent, text="ファイルハッシュ", padding=6)
        file_f.pack(fill=tk.X, padx=8, pady=4)
        file_row = tk.Frame(file_f, bg=file_f.cget("background"))
        file_row.pack(fill=tk.X)
        self.hash_file_var = tk.StringVar()
        ttk.Entry(file_row, textvariable=self.hash_file_var,
                  width=40).pack(side=tk.LEFT, fill=tk.X, expand=True)
        ttk.Button(file_row, text="参照",
                   command=self._browse_hash_file).pack(side=tk.LEFT, padx=4)
        ttk.Button(file_row, text="SHA256 計算",
                   command=self._file_hash).pack(side=tk.LEFT, padx=4)

        tk.Label(parent, text="ハッシュ結果:", **lbl_s).pack(anchor="w", padx=8, pady=4)
        self.hash_output = tk.Text(parent, bg="#0d1117", fg="#3fb950",
                                    font=("Courier New", 11), height=10,
                                    state=tk.DISABLED, relief=tk.FLAT)
        self.hash_output.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

    def _compute_hash(self, algo):
        text = self.hash_input.get("1.0", tk.END).strip()
        data = text.encode("utf-8")
        algo_map = {
            "MD5": hashlib.md5,
            "SHA1": hashlib.sha1,
            "SHA256": hashlib.sha256,
            "SHA512": hashlib.sha512,
            "SHA3-256": hashlib.sha3_256,
        }
        h = algo_map[algo](data).hexdigest()
        result = f"[{algo}]\n{h}\n\n"
        self.hash_output.config(state=tk.NORMAL)
        self.hash_output.insert(tk.END, result)
        self.hash_output.config(state=tk.DISABLED)
        self.status_var.set(f"{algo}: {h[:20]}...")

    def _browse_hash_file(self):
        path = filedialog.askopenfilename()
        if path:
            self.hash_file_var.set(path)

    def _file_hash(self):
        path = self.hash_file_var.get()
        if not path or not os.path.exists(path):
            messagebox.showwarning("警告", "ファイルを選択してください")
            return
        h = hashlib.sha256()
        with open(path, "rb") as f:
            for chunk in iter(lambda: f.read(65536), b""):
                h.update(chunk)
        result = f"[SHA256] {os.path.basename(path)}\n{h.hexdigest()}\n\n"
        self.hash_output.config(state=tk.NORMAL)
        self.hash_output.insert(tk.END, result)
        self.hash_output.config(state=tk.DISABLED)

    # ── Base64 タブ ──────────────────────────────────────────────

    def _build_b64_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}
        paned = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        paned.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)

        left = tk.Frame(paned, bg="#1a1a2e")
        paned.add(left, weight=1)
        tk.Label(left, text="入力", **lbl_s).pack(anchor="w")
        self.b64_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                  font=("Courier New", 11), relief=tk.FLAT)
        self.b64_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(paned, bg="#1a1a2e")
        paned.add(right, weight=1)
        tk.Label(right, text="出力", **lbl_s).pack(anchor="w")
        self.b64_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                   font=("Courier New", 11), relief=tk.FLAT,
                                   state=tk.DISABLED)
        self.b64_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="▶ Base64 エンコード",
                   command=self._b64_encode).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="◀ Base64 デコード",
                   command=self._b64_decode).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="📋 コピー",
                   command=lambda: self._copy_output(self.b64_output)
                   ).pack(side=tk.LEFT, padx=8)

    def _b64_encode(self):
        text = self.b64_input.get("1.0", tk.END).strip()
        encoded = base64.b64encode(text.encode("utf-8")).decode()
        self._set_output(self.b64_output, encoded)
        self.status_var.set("Base64エンコードしました")

    def _b64_decode(self):
        text = self.b64_input.get("1.0", tk.END).strip()
        try:
            decoded = base64.b64decode(text).decode("utf-8")
            self._set_output(self.b64_output, decoded)
            self.status_var.set("Base64デコードしました")
        except Exception as e:
            messagebox.showerror("エラー", f"デコード失敗: {e}")

    # ── ユーティリティ ────────────────────────────────────────────

    def _set_output(self, widget, text):
        widget.config(state=tk.NORMAL)
        widget.delete("1.0", tk.END)
        widget.insert("1.0", text)
        widget.config(state=tk.DISABLED)

    def _copy_output(self, widget):
        text = widget.get("1.0", tk.END).strip()
        self.root.clipboard_clear()
        self.root.clipboard_append(text)
        self.status_var.set("クリップボードにコピーしました")


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

LabelFrameによるセクション分け

ttk.LabelFrame を使うことで、入力エリアと結果エリアを視覚的に分けられます。padding引数でフレーム内の余白を設定し、見やすいレイアウトを実現しています。

import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import os
import base64
import hashlib
import secrets

try:
    from cryptography.fernet import Fernet
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import rsa, padding
    from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
    CRYPTO_AVAILABLE = True
except ImportError:
    CRYPTO_AVAILABLE = False


class App32:
    """暗号化・復号ツール"""

    def __init__(self, root):
        self.root = root
        self.root.title("暗号化・復号ツール")
        self.root.geometry("860x620")
        self.root.configure(bg="#1a1a2e")
        self._fernet = None
        self._rsa_private = None
        self._rsa_public = None
        self._build_ui()

    def _build_ui(self):
        # ヘッダー
        header = tk.Frame(self.root, bg="#16213e", pady=8)
        header.pack(fill=tk.X)
        tk.Label(header, text="🔐 暗号化・復号ツール",
                 font=("Noto Sans JP", 13, "bold"),
                 bg="#16213e", fg="#e94560").pack(side=tk.LEFT, padx=12)

        if not CRYPTO_AVAILABLE:
            tk.Label(self.root,
                     text="⚠ cryptographyライブラリが未インストールです "
                          "(pip install cryptography)。",
                     bg="#fff3cd", fg="#856404", font=("Arial", 9),
                     anchor="w", padx=8).pack(fill=tk.X)

        notebook = ttk.Notebook(self.root)
        notebook.pack(fill=tk.BOTH, expand=True, padx=6, pady=6)

        # ── タブ1: Fernet(AES-128 CBC)──────────────────────────
        fernet_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(fernet_tab, text="🔑 Fernet (AES)")
        self._build_fernet_tab(fernet_tab)

        # ── タブ2: RSA ──────────────────────────────────────────
        rsa_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(rsa_tab, text="🔒 RSA")
        self._build_rsa_tab(rsa_tab)

        # ── タブ3: ハッシュ ─────────────────────────────────────
        hash_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(hash_tab, text="🔎 ハッシュ")
        self._build_hash_tab(hash_tab)

        # ── タブ4: Base64 ───────────────────────────────────────
        b64_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(b64_tab, text="📄 Base64")
        self._build_b64_tab(b64_tab)

        self.status_var = tk.StringVar(value="アルゴリズムを選択してください")
        tk.Label(self.root, textvariable=self.status_var,
                 bg="#16213e", fg="#858585", font=("Arial", 9),
                 anchor="w", padx=8).pack(fill=tk.X, side=tk.BOTTOM)

    # ── Fernet タブ ──────────────────────────────────────────────

    def _build_fernet_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}

        # キー設定
        key_f = ttk.LabelFrame(parent, text="キー設定", padding=8)
        key_f.pack(fill=tk.X, padx=8, pady=6)
        row1 = tk.Frame(key_f, bg=key_f.cget("background"))
        row1.pack(fill=tk.X)
        tk.Label(row1, text="パスワード:", **lbl_s,
                 bg=row1.cget("bg")).pack(side=tk.LEFT)
        self.fernet_pw_var = tk.StringVar()
        ttk.Entry(row1, textvariable=self.fernet_pw_var, show="*",
                  width=30).pack(side=tk.LEFT, padx=6)
        ttk.Button(row1, text="🔑 キー生成",
                   command=self._gen_fernet_key).pack(side=tk.LEFT, padx=4)
        ttk.Button(row1, text="🎲 ランダム生成",
                   command=self._random_fernet_key).pack(side=tk.LEFT, padx=4)

        row2 = tk.Frame(key_f, bg=key_f.cget("background"))
        row2.pack(fill=tk.X, pady=4)
        tk.Label(row2, text="Fernetキー:", **lbl_s,
                 bg=row2.cget("bg")).pack(side=tk.LEFT)
        self.fernet_key_var = tk.StringVar()
        ttk.Entry(row2, textvariable=self.fernet_key_var,
                  width=60).pack(side=tk.LEFT, padx=6, fill=tk.X, expand=True)

        # テキスト入出力
        io_f = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        io_f.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

        left = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(left, weight=1)
        tk.Label(left, text="入力テキスト", **lbl_s).pack(anchor="w", pady=2)
        self.fernet_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                     font=("Courier New", 11), relief=tk.FLAT,
                                     height=12)
        self.fernet_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(right, weight=1)
        tk.Label(right, text="出力テキスト", **lbl_s).pack(anchor="w", pady=2)
        self.fernet_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                      font=("Courier New", 11), relief=tk.FLAT,
                                      height=12, state=tk.DISABLED)
        self.fernet_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="🔒 暗号化",
                   command=self._fernet_encrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="🔓 復号",
                   command=self._fernet_decrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="📋 出力をコピー",
                   command=lambda: self._copy_output(self.fernet_output)
                   ).pack(side=tk.LEFT, padx=8)

    def _gen_fernet_key(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        pw = self.fernet_pw_var.get()
        if not pw:
            messagebox.showwarning("警告", "パスワードを入力してください")
            return
        salt = b"pythonland_salt_"
        kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32,
                          salt=salt, iterations=100000)
        key = base64.urlsafe_b64encode(kdf.derive(pw.encode()))
        self.fernet_key_var.set(key.decode())
        self._fernet = Fernet(key)
        self.status_var.set("Fernetキーを生成しました")

    def _random_fernet_key(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        key = Fernet.generate_key()
        self.fernet_key_var.set(key.decode())
        self._fernet = Fernet(key)
        self.status_var.set("ランダムFernetキーを生成しました")

    def _get_fernet(self):
        key_str = self.fernet_key_var.get().strip()
        if not key_str:
            messagebox.showwarning("警告", "キーを生成してください")
            return None
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return None
        try:
            return Fernet(key_str.encode())
        except Exception as e:
            messagebox.showerror("エラー", f"無効なキー: {e}")
            return None

    def _fernet_encrypt(self):
        f = self._get_fernet()
        if not f:
            return
        text = self.fernet_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            token = f.encrypt(text.encode("utf-8"))
            self._set_output(self.fernet_output, token.decode())
            self.status_var.set("暗号化しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _fernet_decrypt(self):
        f = self._get_fernet()
        if not f:
            return
        text = self.fernet_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            plain = f.decrypt(text.encode())
            self._set_output(self.fernet_output, plain.decode("utf-8"))
            self.status_var.set("復号しました")
        except Exception as e:
            messagebox.showerror("復号エラー",
                                 "復号に失敗しました。キーまたはデータが不正です。\n" + str(e))

    # ── RSA タブ ──────────────────────────────────────────────────

    def _build_rsa_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}

        key_f = ttk.LabelFrame(parent, text="RSAキーペア生成", padding=8)
        key_f.pack(fill=tk.X, padx=8, pady=6)
        btn_row = tk.Frame(key_f, bg=key_f.cget("background"))
        btn_row.pack(fill=tk.X)
        tk.Label(btn_row, text="鍵長:", bg=btn_row.cget("bg"),
                 fg="#c9d1d9").pack(side=tk.LEFT)
        self.rsa_bits_var = tk.StringVar(value="2048")
        ttk.Combobox(btn_row, textvariable=self.rsa_bits_var,
                     values=["1024", "2048", "4096"],
                     state="readonly", width=8).pack(side=tk.LEFT, padx=6)
        ttk.Button(btn_row, text="🔑 キーペア生成",
                   command=self._gen_rsa_keys).pack(side=tk.LEFT, padx=8)

        paned = ttk.PanedWindow(key_f, orient=tk.HORIZONTAL)
        paned.pack(fill=tk.X, pady=4)
        for title, attr in [("公開鍵 (暗号化用)", "rsa_pub_text"),
                             ("秘密鍵 (復号用)", "rsa_priv_text")]:
            f = tk.Frame(paned, bg="#1a1a2e")
            paned.add(f, weight=1)
            tk.Label(f, text=title, **lbl_s).pack(anchor="w")
            txt = tk.Text(f, bg="#0d1117", fg="#c9d1d9",
                          font=("Courier New", 8), height=6, state=tk.DISABLED)
            txt.pack(fill=tk.X)
            setattr(self, attr, txt)

        io_f = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        io_f.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

        left = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(left, weight=1)
        tk.Label(left, text="入力テキスト", **lbl_s).pack(anchor="w")
        self.rsa_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                  font=("Courier New", 11), height=8)
        self.rsa_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(right, weight=1)
        tk.Label(right, text="出力テキスト", **lbl_s).pack(anchor="w")
        self.rsa_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                   font=("Courier New", 11), height=8,
                                   state=tk.DISABLED)
        self.rsa_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="🔒 公開鍵で暗号化",
                   command=self._rsa_encrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="🔓 秘密鍵で復号",
                   command=self._rsa_decrypt).pack(side=tk.LEFT, padx=8)

    def _gen_rsa_keys(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        bits = int(self.rsa_bits_var.get())
        self.status_var.set(f"RSA {bits}bitキーを生成中...")
        self.root.update()
        try:
            self._rsa_private = rsa.generate_private_key(
                public_exponent=65537, key_size=bits)
            self._rsa_public = self._rsa_private.public_key()
            priv_pem = self._rsa_private.private_bytes(
                serialization.Encoding.PEM,
                serialization.PrivateFormat.TraditionalOpenSSL,
                serialization.NoEncryption()).decode()
            pub_pem = self._rsa_public.public_bytes(
                serialization.Encoding.PEM,
                serialization.PublicFormat.SubjectPublicKeyInfo).decode()
            self._set_output(self.rsa_pub_text, pub_pem)
            self._set_output(self.rsa_priv_text, priv_pem)
            self.status_var.set(f"RSA {bits}bitキーペアを生成しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _rsa_encrypt(self):
        if not CRYPTO_AVAILABLE or not self._rsa_public:
            messagebox.showwarning("警告", "先にキーペアを生成してください")
            return
        text = self.rsa_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            cipher = self._rsa_public.encrypt(
                text.encode("utf-8"),
                padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                             algorithm=hashes.SHA256(), label=None))
            self._set_output(self.rsa_output, base64.b64encode(cipher).decode())
            self.status_var.set("RSA暗号化しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _rsa_decrypt(self):
        if not CRYPTO_AVAILABLE or not self._rsa_private:
            messagebox.showwarning("警告", "先にキーペアを生成してください")
            return
        text = self.rsa_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            cipher = base64.b64decode(text)
            plain = self._rsa_private.decrypt(
                cipher,
                padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                             algorithm=hashes.SHA256(), label=None))
            self._set_output(self.rsa_output, plain.decode("utf-8"))
            self.status_var.set("RSA復号しました")
        except Exception as e:
            messagebox.showerror("復号エラー", str(e))

    # ── ハッシュ タブ ──────────────────────────────────────────────

    def _build_hash_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}
        tk.Label(parent, text="テキスト:", **lbl_s).pack(anchor="w", padx=8, pady=4)
        self.hash_input = tk.Text(parent, bg="#0d1117", fg="#c9d1d9",
                                   font=("Courier New", 11), height=6,
                                   relief=tk.FLAT)
        self.hash_input.pack(fill=tk.X, padx=8)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(fill=tk.X, padx=8, pady=6)
        for algo in ["MD5", "SHA1", "SHA256", "SHA512", "SHA3-256"]:
            ttk.Button(btn_f, text=algo,
                       command=lambda a=algo: self._compute_hash(a)
                       ).pack(side=tk.LEFT, padx=4)

        # ファイルハッシュ
        file_f = ttk.LabelFrame(parent, text="ファイルハッシュ", padding=6)
        file_f.pack(fill=tk.X, padx=8, pady=4)
        file_row = tk.Frame(file_f, bg=file_f.cget("background"))
        file_row.pack(fill=tk.X)
        self.hash_file_var = tk.StringVar()
        ttk.Entry(file_row, textvariable=self.hash_file_var,
                  width=40).pack(side=tk.LEFT, fill=tk.X, expand=True)
        ttk.Button(file_row, text="参照",
                   command=self._browse_hash_file).pack(side=tk.LEFT, padx=4)
        ttk.Button(file_row, text="SHA256 計算",
                   command=self._file_hash).pack(side=tk.LEFT, padx=4)

        tk.Label(parent, text="ハッシュ結果:", **lbl_s).pack(anchor="w", padx=8, pady=4)
        self.hash_output = tk.Text(parent, bg="#0d1117", fg="#3fb950",
                                    font=("Courier New", 11), height=10,
                                    state=tk.DISABLED, relief=tk.FLAT)
        self.hash_output.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

    def _compute_hash(self, algo):
        text = self.hash_input.get("1.0", tk.END).strip()
        data = text.encode("utf-8")
        algo_map = {
            "MD5": hashlib.md5,
            "SHA1": hashlib.sha1,
            "SHA256": hashlib.sha256,
            "SHA512": hashlib.sha512,
            "SHA3-256": hashlib.sha3_256,
        }
        h = algo_map[algo](data).hexdigest()
        result = f"[{algo}]\n{h}\n\n"
        self.hash_output.config(state=tk.NORMAL)
        self.hash_output.insert(tk.END, result)
        self.hash_output.config(state=tk.DISABLED)
        self.status_var.set(f"{algo}: {h[:20]}...")

    def _browse_hash_file(self):
        path = filedialog.askopenfilename()
        if path:
            self.hash_file_var.set(path)

    def _file_hash(self):
        path = self.hash_file_var.get()
        if not path or not os.path.exists(path):
            messagebox.showwarning("警告", "ファイルを選択してください")
            return
        h = hashlib.sha256()
        with open(path, "rb") as f:
            for chunk in iter(lambda: f.read(65536), b""):
                h.update(chunk)
        result = f"[SHA256] {os.path.basename(path)}\n{h.hexdigest()}\n\n"
        self.hash_output.config(state=tk.NORMAL)
        self.hash_output.insert(tk.END, result)
        self.hash_output.config(state=tk.DISABLED)

    # ── Base64 タブ ──────────────────────────────────────────────

    def _build_b64_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}
        paned = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        paned.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)

        left = tk.Frame(paned, bg="#1a1a2e")
        paned.add(left, weight=1)
        tk.Label(left, text="入力", **lbl_s).pack(anchor="w")
        self.b64_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                  font=("Courier New", 11), relief=tk.FLAT)
        self.b64_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(paned, bg="#1a1a2e")
        paned.add(right, weight=1)
        tk.Label(right, text="出力", **lbl_s).pack(anchor="w")
        self.b64_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                   font=("Courier New", 11), relief=tk.FLAT,
                                   state=tk.DISABLED)
        self.b64_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="▶ Base64 エンコード",
                   command=self._b64_encode).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="◀ Base64 デコード",
                   command=self._b64_decode).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="📋 コピー",
                   command=lambda: self._copy_output(self.b64_output)
                   ).pack(side=tk.LEFT, padx=8)

    def _b64_encode(self):
        text = self.b64_input.get("1.0", tk.END).strip()
        encoded = base64.b64encode(text.encode("utf-8")).decode()
        self._set_output(self.b64_output, encoded)
        self.status_var.set("Base64エンコードしました")

    def _b64_decode(self):
        text = self.b64_input.get("1.0", tk.END).strip()
        try:
            decoded = base64.b64decode(text).decode("utf-8")
            self._set_output(self.b64_output, decoded)
            self.status_var.set("Base64デコードしました")
        except Exception as e:
            messagebox.showerror("エラー", f"デコード失敗: {e}")

    # ── ユーティリティ ────────────────────────────────────────────

    def _set_output(self, widget, text):
        widget.config(state=tk.NORMAL)
        widget.delete("1.0", tk.END)
        widget.insert("1.0", text)
        widget.config(state=tk.DISABLED)

    def _copy_output(self, widget):
        text = widget.get("1.0", tk.END).strip()
        self.root.clipboard_clear()
        self.root.clipboard_append(text)
        self.status_var.set("クリップボードにコピーしました")


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

Entryウィジェットとイベントバインド

ttk.Entryで入力フィールドを作成します。bind('', ...)でEnterキー押下時に処理を実行できます。これにより、マウスを使わずキーボードだけで操作できるUXが実現できます。

import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import os
import base64
import hashlib
import secrets

try:
    from cryptography.fernet import Fernet
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import rsa, padding
    from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
    CRYPTO_AVAILABLE = True
except ImportError:
    CRYPTO_AVAILABLE = False


class App32:
    """暗号化・復号ツール"""

    def __init__(self, root):
        self.root = root
        self.root.title("暗号化・復号ツール")
        self.root.geometry("860x620")
        self.root.configure(bg="#1a1a2e")
        self._fernet = None
        self._rsa_private = None
        self._rsa_public = None
        self._build_ui()

    def _build_ui(self):
        # ヘッダー
        header = tk.Frame(self.root, bg="#16213e", pady=8)
        header.pack(fill=tk.X)
        tk.Label(header, text="🔐 暗号化・復号ツール",
                 font=("Noto Sans JP", 13, "bold"),
                 bg="#16213e", fg="#e94560").pack(side=tk.LEFT, padx=12)

        if not CRYPTO_AVAILABLE:
            tk.Label(self.root,
                     text="⚠ cryptographyライブラリが未インストールです "
                          "(pip install cryptography)。",
                     bg="#fff3cd", fg="#856404", font=("Arial", 9),
                     anchor="w", padx=8).pack(fill=tk.X)

        notebook = ttk.Notebook(self.root)
        notebook.pack(fill=tk.BOTH, expand=True, padx=6, pady=6)

        # ── タブ1: Fernet(AES-128 CBC)──────────────────────────
        fernet_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(fernet_tab, text="🔑 Fernet (AES)")
        self._build_fernet_tab(fernet_tab)

        # ── タブ2: RSA ──────────────────────────────────────────
        rsa_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(rsa_tab, text="🔒 RSA")
        self._build_rsa_tab(rsa_tab)

        # ── タブ3: ハッシュ ─────────────────────────────────────
        hash_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(hash_tab, text="🔎 ハッシュ")
        self._build_hash_tab(hash_tab)

        # ── タブ4: Base64 ───────────────────────────────────────
        b64_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(b64_tab, text="📄 Base64")
        self._build_b64_tab(b64_tab)

        self.status_var = tk.StringVar(value="アルゴリズムを選択してください")
        tk.Label(self.root, textvariable=self.status_var,
                 bg="#16213e", fg="#858585", font=("Arial", 9),
                 anchor="w", padx=8).pack(fill=tk.X, side=tk.BOTTOM)

    # ── Fernet タブ ──────────────────────────────────────────────

    def _build_fernet_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}

        # キー設定
        key_f = ttk.LabelFrame(parent, text="キー設定", padding=8)
        key_f.pack(fill=tk.X, padx=8, pady=6)
        row1 = tk.Frame(key_f, bg=key_f.cget("background"))
        row1.pack(fill=tk.X)
        tk.Label(row1, text="パスワード:", **lbl_s,
                 bg=row1.cget("bg")).pack(side=tk.LEFT)
        self.fernet_pw_var = tk.StringVar()
        ttk.Entry(row1, textvariable=self.fernet_pw_var, show="*",
                  width=30).pack(side=tk.LEFT, padx=6)
        ttk.Button(row1, text="🔑 キー生成",
                   command=self._gen_fernet_key).pack(side=tk.LEFT, padx=4)
        ttk.Button(row1, text="🎲 ランダム生成",
                   command=self._random_fernet_key).pack(side=tk.LEFT, padx=4)

        row2 = tk.Frame(key_f, bg=key_f.cget("background"))
        row2.pack(fill=tk.X, pady=4)
        tk.Label(row2, text="Fernetキー:", **lbl_s,
                 bg=row2.cget("bg")).pack(side=tk.LEFT)
        self.fernet_key_var = tk.StringVar()
        ttk.Entry(row2, textvariable=self.fernet_key_var,
                  width=60).pack(side=tk.LEFT, padx=6, fill=tk.X, expand=True)

        # テキスト入出力
        io_f = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        io_f.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

        left = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(left, weight=1)
        tk.Label(left, text="入力テキスト", **lbl_s).pack(anchor="w", pady=2)
        self.fernet_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                     font=("Courier New", 11), relief=tk.FLAT,
                                     height=12)
        self.fernet_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(right, weight=1)
        tk.Label(right, text="出力テキスト", **lbl_s).pack(anchor="w", pady=2)
        self.fernet_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                      font=("Courier New", 11), relief=tk.FLAT,
                                      height=12, state=tk.DISABLED)
        self.fernet_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="🔒 暗号化",
                   command=self._fernet_encrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="🔓 復号",
                   command=self._fernet_decrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="📋 出力をコピー",
                   command=lambda: self._copy_output(self.fernet_output)
                   ).pack(side=tk.LEFT, padx=8)

    def _gen_fernet_key(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        pw = self.fernet_pw_var.get()
        if not pw:
            messagebox.showwarning("警告", "パスワードを入力してください")
            return
        salt = b"pythonland_salt_"
        kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32,
                          salt=salt, iterations=100000)
        key = base64.urlsafe_b64encode(kdf.derive(pw.encode()))
        self.fernet_key_var.set(key.decode())
        self._fernet = Fernet(key)
        self.status_var.set("Fernetキーを生成しました")

    def _random_fernet_key(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        key = Fernet.generate_key()
        self.fernet_key_var.set(key.decode())
        self._fernet = Fernet(key)
        self.status_var.set("ランダムFernetキーを生成しました")

    def _get_fernet(self):
        key_str = self.fernet_key_var.get().strip()
        if not key_str:
            messagebox.showwarning("警告", "キーを生成してください")
            return None
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return None
        try:
            return Fernet(key_str.encode())
        except Exception as e:
            messagebox.showerror("エラー", f"無効なキー: {e}")
            return None

    def _fernet_encrypt(self):
        f = self._get_fernet()
        if not f:
            return
        text = self.fernet_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            token = f.encrypt(text.encode("utf-8"))
            self._set_output(self.fernet_output, token.decode())
            self.status_var.set("暗号化しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _fernet_decrypt(self):
        f = self._get_fernet()
        if not f:
            return
        text = self.fernet_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            plain = f.decrypt(text.encode())
            self._set_output(self.fernet_output, plain.decode("utf-8"))
            self.status_var.set("復号しました")
        except Exception as e:
            messagebox.showerror("復号エラー",
                                 "復号に失敗しました。キーまたはデータが不正です。\n" + str(e))

    # ── RSA タブ ──────────────────────────────────────────────────

    def _build_rsa_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}

        key_f = ttk.LabelFrame(parent, text="RSAキーペア生成", padding=8)
        key_f.pack(fill=tk.X, padx=8, pady=6)
        btn_row = tk.Frame(key_f, bg=key_f.cget("background"))
        btn_row.pack(fill=tk.X)
        tk.Label(btn_row, text="鍵長:", bg=btn_row.cget("bg"),
                 fg="#c9d1d9").pack(side=tk.LEFT)
        self.rsa_bits_var = tk.StringVar(value="2048")
        ttk.Combobox(btn_row, textvariable=self.rsa_bits_var,
                     values=["1024", "2048", "4096"],
                     state="readonly", width=8).pack(side=tk.LEFT, padx=6)
        ttk.Button(btn_row, text="🔑 キーペア生成",
                   command=self._gen_rsa_keys).pack(side=tk.LEFT, padx=8)

        paned = ttk.PanedWindow(key_f, orient=tk.HORIZONTAL)
        paned.pack(fill=tk.X, pady=4)
        for title, attr in [("公開鍵 (暗号化用)", "rsa_pub_text"),
                             ("秘密鍵 (復号用)", "rsa_priv_text")]:
            f = tk.Frame(paned, bg="#1a1a2e")
            paned.add(f, weight=1)
            tk.Label(f, text=title, **lbl_s).pack(anchor="w")
            txt = tk.Text(f, bg="#0d1117", fg="#c9d1d9",
                          font=("Courier New", 8), height=6, state=tk.DISABLED)
            txt.pack(fill=tk.X)
            setattr(self, attr, txt)

        io_f = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        io_f.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

        left = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(left, weight=1)
        tk.Label(left, text="入力テキスト", **lbl_s).pack(anchor="w")
        self.rsa_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                  font=("Courier New", 11), height=8)
        self.rsa_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(right, weight=1)
        tk.Label(right, text="出力テキスト", **lbl_s).pack(anchor="w")
        self.rsa_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                   font=("Courier New", 11), height=8,
                                   state=tk.DISABLED)
        self.rsa_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="🔒 公開鍵で暗号化",
                   command=self._rsa_encrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="🔓 秘密鍵で復号",
                   command=self._rsa_decrypt).pack(side=tk.LEFT, padx=8)

    def _gen_rsa_keys(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        bits = int(self.rsa_bits_var.get())
        self.status_var.set(f"RSA {bits}bitキーを生成中...")
        self.root.update()
        try:
            self._rsa_private = rsa.generate_private_key(
                public_exponent=65537, key_size=bits)
            self._rsa_public = self._rsa_private.public_key()
            priv_pem = self._rsa_private.private_bytes(
                serialization.Encoding.PEM,
                serialization.PrivateFormat.TraditionalOpenSSL,
                serialization.NoEncryption()).decode()
            pub_pem = self._rsa_public.public_bytes(
                serialization.Encoding.PEM,
                serialization.PublicFormat.SubjectPublicKeyInfo).decode()
            self._set_output(self.rsa_pub_text, pub_pem)
            self._set_output(self.rsa_priv_text, priv_pem)
            self.status_var.set(f"RSA {bits}bitキーペアを生成しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _rsa_encrypt(self):
        if not CRYPTO_AVAILABLE or not self._rsa_public:
            messagebox.showwarning("警告", "先にキーペアを生成してください")
            return
        text = self.rsa_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            cipher = self._rsa_public.encrypt(
                text.encode("utf-8"),
                padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                             algorithm=hashes.SHA256(), label=None))
            self._set_output(self.rsa_output, base64.b64encode(cipher).decode())
            self.status_var.set("RSA暗号化しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _rsa_decrypt(self):
        if not CRYPTO_AVAILABLE or not self._rsa_private:
            messagebox.showwarning("警告", "先にキーペアを生成してください")
            return
        text = self.rsa_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            cipher = base64.b64decode(text)
            plain = self._rsa_private.decrypt(
                cipher,
                padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                             algorithm=hashes.SHA256(), label=None))
            self._set_output(self.rsa_output, plain.decode("utf-8"))
            self.status_var.set("RSA復号しました")
        except Exception as e:
            messagebox.showerror("復号エラー", str(e))

    # ── ハッシュ タブ ──────────────────────────────────────────────

    def _build_hash_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}
        tk.Label(parent, text="テキスト:", **lbl_s).pack(anchor="w", padx=8, pady=4)
        self.hash_input = tk.Text(parent, bg="#0d1117", fg="#c9d1d9",
                                   font=("Courier New", 11), height=6,
                                   relief=tk.FLAT)
        self.hash_input.pack(fill=tk.X, padx=8)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(fill=tk.X, padx=8, pady=6)
        for algo in ["MD5", "SHA1", "SHA256", "SHA512", "SHA3-256"]:
            ttk.Button(btn_f, text=algo,
                       command=lambda a=algo: self._compute_hash(a)
                       ).pack(side=tk.LEFT, padx=4)

        # ファイルハッシュ
        file_f = ttk.LabelFrame(parent, text="ファイルハッシュ", padding=6)
        file_f.pack(fill=tk.X, padx=8, pady=4)
        file_row = tk.Frame(file_f, bg=file_f.cget("background"))
        file_row.pack(fill=tk.X)
        self.hash_file_var = tk.StringVar()
        ttk.Entry(file_row, textvariable=self.hash_file_var,
                  width=40).pack(side=tk.LEFT, fill=tk.X, expand=True)
        ttk.Button(file_row, text="参照",
                   command=self._browse_hash_file).pack(side=tk.LEFT, padx=4)
        ttk.Button(file_row, text="SHA256 計算",
                   command=self._file_hash).pack(side=tk.LEFT, padx=4)

        tk.Label(parent, text="ハッシュ結果:", **lbl_s).pack(anchor="w", padx=8, pady=4)
        self.hash_output = tk.Text(parent, bg="#0d1117", fg="#3fb950",
                                    font=("Courier New", 11), height=10,
                                    state=tk.DISABLED, relief=tk.FLAT)
        self.hash_output.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

    def _compute_hash(self, algo):
        text = self.hash_input.get("1.0", tk.END).strip()
        data = text.encode("utf-8")
        algo_map = {
            "MD5": hashlib.md5,
            "SHA1": hashlib.sha1,
            "SHA256": hashlib.sha256,
            "SHA512": hashlib.sha512,
            "SHA3-256": hashlib.sha3_256,
        }
        h = algo_map[algo](data).hexdigest()
        result = f"[{algo}]\n{h}\n\n"
        self.hash_output.config(state=tk.NORMAL)
        self.hash_output.insert(tk.END, result)
        self.hash_output.config(state=tk.DISABLED)
        self.status_var.set(f"{algo}: {h[:20]}...")

    def _browse_hash_file(self):
        path = filedialog.askopenfilename()
        if path:
            self.hash_file_var.set(path)

    def _file_hash(self):
        path = self.hash_file_var.get()
        if not path or not os.path.exists(path):
            messagebox.showwarning("警告", "ファイルを選択してください")
            return
        h = hashlib.sha256()
        with open(path, "rb") as f:
            for chunk in iter(lambda: f.read(65536), b""):
                h.update(chunk)
        result = f"[SHA256] {os.path.basename(path)}\n{h.hexdigest()}\n\n"
        self.hash_output.config(state=tk.NORMAL)
        self.hash_output.insert(tk.END, result)
        self.hash_output.config(state=tk.DISABLED)

    # ── Base64 タブ ──────────────────────────────────────────────

    def _build_b64_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}
        paned = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        paned.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)

        left = tk.Frame(paned, bg="#1a1a2e")
        paned.add(left, weight=1)
        tk.Label(left, text="入力", **lbl_s).pack(anchor="w")
        self.b64_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                  font=("Courier New", 11), relief=tk.FLAT)
        self.b64_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(paned, bg="#1a1a2e")
        paned.add(right, weight=1)
        tk.Label(right, text="出力", **lbl_s).pack(anchor="w")
        self.b64_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                   font=("Courier New", 11), relief=tk.FLAT,
                                   state=tk.DISABLED)
        self.b64_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="▶ Base64 エンコード",
                   command=self._b64_encode).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="◀ Base64 デコード",
                   command=self._b64_decode).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="📋 コピー",
                   command=lambda: self._copy_output(self.b64_output)
                   ).pack(side=tk.LEFT, padx=8)

    def _b64_encode(self):
        text = self.b64_input.get("1.0", tk.END).strip()
        encoded = base64.b64encode(text.encode("utf-8")).decode()
        self._set_output(self.b64_output, encoded)
        self.status_var.set("Base64エンコードしました")

    def _b64_decode(self):
        text = self.b64_input.get("1.0", tk.END).strip()
        try:
            decoded = base64.b64decode(text).decode("utf-8")
            self._set_output(self.b64_output, decoded)
            self.status_var.set("Base64デコードしました")
        except Exception as e:
            messagebox.showerror("エラー", f"デコード失敗: {e}")

    # ── ユーティリティ ────────────────────────────────────────────

    def _set_output(self, widget, text):
        widget.config(state=tk.NORMAL)
        widget.delete("1.0", tk.END)
        widget.insert("1.0", text)
        widget.config(state=tk.DISABLED)

    def _copy_output(self, widget):
        text = widget.get("1.0", tk.END).strip()
        self.root.clipboard_clear()
        self.root.clipboard_append(text)
        self.status_var.set("クリップボードにコピーしました")


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

Textウィジェットでの結果表示

結果表示にはtk.Textウィジェットを使います。state=tk.DISABLEDでユーザーが直接編集できないようにし、表示前にNORMALに切り替えてからinsert()で内容を更新します。

import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import os
import base64
import hashlib
import secrets

try:
    from cryptography.fernet import Fernet
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import rsa, padding
    from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
    CRYPTO_AVAILABLE = True
except ImportError:
    CRYPTO_AVAILABLE = False


class App32:
    """暗号化・復号ツール"""

    def __init__(self, root):
        self.root = root
        self.root.title("暗号化・復号ツール")
        self.root.geometry("860x620")
        self.root.configure(bg="#1a1a2e")
        self._fernet = None
        self._rsa_private = None
        self._rsa_public = None
        self._build_ui()

    def _build_ui(self):
        # ヘッダー
        header = tk.Frame(self.root, bg="#16213e", pady=8)
        header.pack(fill=tk.X)
        tk.Label(header, text="🔐 暗号化・復号ツール",
                 font=("Noto Sans JP", 13, "bold"),
                 bg="#16213e", fg="#e94560").pack(side=tk.LEFT, padx=12)

        if not CRYPTO_AVAILABLE:
            tk.Label(self.root,
                     text="⚠ cryptographyライブラリが未インストールです "
                          "(pip install cryptography)。",
                     bg="#fff3cd", fg="#856404", font=("Arial", 9),
                     anchor="w", padx=8).pack(fill=tk.X)

        notebook = ttk.Notebook(self.root)
        notebook.pack(fill=tk.BOTH, expand=True, padx=6, pady=6)

        # ── タブ1: Fernet(AES-128 CBC)──────────────────────────
        fernet_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(fernet_tab, text="🔑 Fernet (AES)")
        self._build_fernet_tab(fernet_tab)

        # ── タブ2: RSA ──────────────────────────────────────────
        rsa_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(rsa_tab, text="🔒 RSA")
        self._build_rsa_tab(rsa_tab)

        # ── タブ3: ハッシュ ─────────────────────────────────────
        hash_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(hash_tab, text="🔎 ハッシュ")
        self._build_hash_tab(hash_tab)

        # ── タブ4: Base64 ───────────────────────────────────────
        b64_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(b64_tab, text="📄 Base64")
        self._build_b64_tab(b64_tab)

        self.status_var = tk.StringVar(value="アルゴリズムを選択してください")
        tk.Label(self.root, textvariable=self.status_var,
                 bg="#16213e", fg="#858585", font=("Arial", 9),
                 anchor="w", padx=8).pack(fill=tk.X, side=tk.BOTTOM)

    # ── Fernet タブ ──────────────────────────────────────────────

    def _build_fernet_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}

        # キー設定
        key_f = ttk.LabelFrame(parent, text="キー設定", padding=8)
        key_f.pack(fill=tk.X, padx=8, pady=6)
        row1 = tk.Frame(key_f, bg=key_f.cget("background"))
        row1.pack(fill=tk.X)
        tk.Label(row1, text="パスワード:", **lbl_s,
                 bg=row1.cget("bg")).pack(side=tk.LEFT)
        self.fernet_pw_var = tk.StringVar()
        ttk.Entry(row1, textvariable=self.fernet_pw_var, show="*",
                  width=30).pack(side=tk.LEFT, padx=6)
        ttk.Button(row1, text="🔑 キー生成",
                   command=self._gen_fernet_key).pack(side=tk.LEFT, padx=4)
        ttk.Button(row1, text="🎲 ランダム生成",
                   command=self._random_fernet_key).pack(side=tk.LEFT, padx=4)

        row2 = tk.Frame(key_f, bg=key_f.cget("background"))
        row2.pack(fill=tk.X, pady=4)
        tk.Label(row2, text="Fernetキー:", **lbl_s,
                 bg=row2.cget("bg")).pack(side=tk.LEFT)
        self.fernet_key_var = tk.StringVar()
        ttk.Entry(row2, textvariable=self.fernet_key_var,
                  width=60).pack(side=tk.LEFT, padx=6, fill=tk.X, expand=True)

        # テキスト入出力
        io_f = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        io_f.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

        left = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(left, weight=1)
        tk.Label(left, text="入力テキスト", **lbl_s).pack(anchor="w", pady=2)
        self.fernet_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                     font=("Courier New", 11), relief=tk.FLAT,
                                     height=12)
        self.fernet_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(right, weight=1)
        tk.Label(right, text="出力テキスト", **lbl_s).pack(anchor="w", pady=2)
        self.fernet_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                      font=("Courier New", 11), relief=tk.FLAT,
                                      height=12, state=tk.DISABLED)
        self.fernet_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="🔒 暗号化",
                   command=self._fernet_encrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="🔓 復号",
                   command=self._fernet_decrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="📋 出力をコピー",
                   command=lambda: self._copy_output(self.fernet_output)
                   ).pack(side=tk.LEFT, padx=8)

    def _gen_fernet_key(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        pw = self.fernet_pw_var.get()
        if not pw:
            messagebox.showwarning("警告", "パスワードを入力してください")
            return
        salt = b"pythonland_salt_"
        kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32,
                          salt=salt, iterations=100000)
        key = base64.urlsafe_b64encode(kdf.derive(pw.encode()))
        self.fernet_key_var.set(key.decode())
        self._fernet = Fernet(key)
        self.status_var.set("Fernetキーを生成しました")

    def _random_fernet_key(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        key = Fernet.generate_key()
        self.fernet_key_var.set(key.decode())
        self._fernet = Fernet(key)
        self.status_var.set("ランダムFernetキーを生成しました")

    def _get_fernet(self):
        key_str = self.fernet_key_var.get().strip()
        if not key_str:
            messagebox.showwarning("警告", "キーを生成してください")
            return None
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return None
        try:
            return Fernet(key_str.encode())
        except Exception as e:
            messagebox.showerror("エラー", f"無効なキー: {e}")
            return None

    def _fernet_encrypt(self):
        f = self._get_fernet()
        if not f:
            return
        text = self.fernet_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            token = f.encrypt(text.encode("utf-8"))
            self._set_output(self.fernet_output, token.decode())
            self.status_var.set("暗号化しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _fernet_decrypt(self):
        f = self._get_fernet()
        if not f:
            return
        text = self.fernet_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            plain = f.decrypt(text.encode())
            self._set_output(self.fernet_output, plain.decode("utf-8"))
            self.status_var.set("復号しました")
        except Exception as e:
            messagebox.showerror("復号エラー",
                                 "復号に失敗しました。キーまたはデータが不正です。\n" + str(e))

    # ── RSA タブ ──────────────────────────────────────────────────

    def _build_rsa_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}

        key_f = ttk.LabelFrame(parent, text="RSAキーペア生成", padding=8)
        key_f.pack(fill=tk.X, padx=8, pady=6)
        btn_row = tk.Frame(key_f, bg=key_f.cget("background"))
        btn_row.pack(fill=tk.X)
        tk.Label(btn_row, text="鍵長:", bg=btn_row.cget("bg"),
                 fg="#c9d1d9").pack(side=tk.LEFT)
        self.rsa_bits_var = tk.StringVar(value="2048")
        ttk.Combobox(btn_row, textvariable=self.rsa_bits_var,
                     values=["1024", "2048", "4096"],
                     state="readonly", width=8).pack(side=tk.LEFT, padx=6)
        ttk.Button(btn_row, text="🔑 キーペア生成",
                   command=self._gen_rsa_keys).pack(side=tk.LEFT, padx=8)

        paned = ttk.PanedWindow(key_f, orient=tk.HORIZONTAL)
        paned.pack(fill=tk.X, pady=4)
        for title, attr in [("公開鍵 (暗号化用)", "rsa_pub_text"),
                             ("秘密鍵 (復号用)", "rsa_priv_text")]:
            f = tk.Frame(paned, bg="#1a1a2e")
            paned.add(f, weight=1)
            tk.Label(f, text=title, **lbl_s).pack(anchor="w")
            txt = tk.Text(f, bg="#0d1117", fg="#c9d1d9",
                          font=("Courier New", 8), height=6, state=tk.DISABLED)
            txt.pack(fill=tk.X)
            setattr(self, attr, txt)

        io_f = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        io_f.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

        left = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(left, weight=1)
        tk.Label(left, text="入力テキスト", **lbl_s).pack(anchor="w")
        self.rsa_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                  font=("Courier New", 11), height=8)
        self.rsa_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(right, weight=1)
        tk.Label(right, text="出力テキスト", **lbl_s).pack(anchor="w")
        self.rsa_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                   font=("Courier New", 11), height=8,
                                   state=tk.DISABLED)
        self.rsa_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="🔒 公開鍵で暗号化",
                   command=self._rsa_encrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="🔓 秘密鍵で復号",
                   command=self._rsa_decrypt).pack(side=tk.LEFT, padx=8)

    def _gen_rsa_keys(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        bits = int(self.rsa_bits_var.get())
        self.status_var.set(f"RSA {bits}bitキーを生成中...")
        self.root.update()
        try:
            self._rsa_private = rsa.generate_private_key(
                public_exponent=65537, key_size=bits)
            self._rsa_public = self._rsa_private.public_key()
            priv_pem = self._rsa_private.private_bytes(
                serialization.Encoding.PEM,
                serialization.PrivateFormat.TraditionalOpenSSL,
                serialization.NoEncryption()).decode()
            pub_pem = self._rsa_public.public_bytes(
                serialization.Encoding.PEM,
                serialization.PublicFormat.SubjectPublicKeyInfo).decode()
            self._set_output(self.rsa_pub_text, pub_pem)
            self._set_output(self.rsa_priv_text, priv_pem)
            self.status_var.set(f"RSA {bits}bitキーペアを生成しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _rsa_encrypt(self):
        if not CRYPTO_AVAILABLE or not self._rsa_public:
            messagebox.showwarning("警告", "先にキーペアを生成してください")
            return
        text = self.rsa_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            cipher = self._rsa_public.encrypt(
                text.encode("utf-8"),
                padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                             algorithm=hashes.SHA256(), label=None))
            self._set_output(self.rsa_output, base64.b64encode(cipher).decode())
            self.status_var.set("RSA暗号化しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _rsa_decrypt(self):
        if not CRYPTO_AVAILABLE or not self._rsa_private:
            messagebox.showwarning("警告", "先にキーペアを生成してください")
            return
        text = self.rsa_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            cipher = base64.b64decode(text)
            plain = self._rsa_private.decrypt(
                cipher,
                padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                             algorithm=hashes.SHA256(), label=None))
            self._set_output(self.rsa_output, plain.decode("utf-8"))
            self.status_var.set("RSA復号しました")
        except Exception as e:
            messagebox.showerror("復号エラー", str(e))

    # ── ハッシュ タブ ──────────────────────────────────────────────

    def _build_hash_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}
        tk.Label(parent, text="テキスト:", **lbl_s).pack(anchor="w", padx=8, pady=4)
        self.hash_input = tk.Text(parent, bg="#0d1117", fg="#c9d1d9",
                                   font=("Courier New", 11), height=6,
                                   relief=tk.FLAT)
        self.hash_input.pack(fill=tk.X, padx=8)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(fill=tk.X, padx=8, pady=6)
        for algo in ["MD5", "SHA1", "SHA256", "SHA512", "SHA3-256"]:
            ttk.Button(btn_f, text=algo,
                       command=lambda a=algo: self._compute_hash(a)
                       ).pack(side=tk.LEFT, padx=4)

        # ファイルハッシュ
        file_f = ttk.LabelFrame(parent, text="ファイルハッシュ", padding=6)
        file_f.pack(fill=tk.X, padx=8, pady=4)
        file_row = tk.Frame(file_f, bg=file_f.cget("background"))
        file_row.pack(fill=tk.X)
        self.hash_file_var = tk.StringVar()
        ttk.Entry(file_row, textvariable=self.hash_file_var,
                  width=40).pack(side=tk.LEFT, fill=tk.X, expand=True)
        ttk.Button(file_row, text="参照",
                   command=self._browse_hash_file).pack(side=tk.LEFT, padx=4)
        ttk.Button(file_row, text="SHA256 計算",
                   command=self._file_hash).pack(side=tk.LEFT, padx=4)

        tk.Label(parent, text="ハッシュ結果:", **lbl_s).pack(anchor="w", padx=8, pady=4)
        self.hash_output = tk.Text(parent, bg="#0d1117", fg="#3fb950",
                                    font=("Courier New", 11), height=10,
                                    state=tk.DISABLED, relief=tk.FLAT)
        self.hash_output.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

    def _compute_hash(self, algo):
        text = self.hash_input.get("1.0", tk.END).strip()
        data = text.encode("utf-8")
        algo_map = {
            "MD5": hashlib.md5,
            "SHA1": hashlib.sha1,
            "SHA256": hashlib.sha256,
            "SHA512": hashlib.sha512,
            "SHA3-256": hashlib.sha3_256,
        }
        h = algo_map[algo](data).hexdigest()
        result = f"[{algo}]\n{h}\n\n"
        self.hash_output.config(state=tk.NORMAL)
        self.hash_output.insert(tk.END, result)
        self.hash_output.config(state=tk.DISABLED)
        self.status_var.set(f"{algo}: {h[:20]}...")

    def _browse_hash_file(self):
        path = filedialog.askopenfilename()
        if path:
            self.hash_file_var.set(path)

    def _file_hash(self):
        path = self.hash_file_var.get()
        if not path or not os.path.exists(path):
            messagebox.showwarning("警告", "ファイルを選択してください")
            return
        h = hashlib.sha256()
        with open(path, "rb") as f:
            for chunk in iter(lambda: f.read(65536), b""):
                h.update(chunk)
        result = f"[SHA256] {os.path.basename(path)}\n{h.hexdigest()}\n\n"
        self.hash_output.config(state=tk.NORMAL)
        self.hash_output.insert(tk.END, result)
        self.hash_output.config(state=tk.DISABLED)

    # ── Base64 タブ ──────────────────────────────────────────────

    def _build_b64_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}
        paned = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        paned.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)

        left = tk.Frame(paned, bg="#1a1a2e")
        paned.add(left, weight=1)
        tk.Label(left, text="入力", **lbl_s).pack(anchor="w")
        self.b64_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                  font=("Courier New", 11), relief=tk.FLAT)
        self.b64_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(paned, bg="#1a1a2e")
        paned.add(right, weight=1)
        tk.Label(right, text="出力", **lbl_s).pack(anchor="w")
        self.b64_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                   font=("Courier New", 11), relief=tk.FLAT,
                                   state=tk.DISABLED)
        self.b64_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="▶ Base64 エンコード",
                   command=self._b64_encode).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="◀ Base64 デコード",
                   command=self._b64_decode).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="📋 コピー",
                   command=lambda: self._copy_output(self.b64_output)
                   ).pack(side=tk.LEFT, padx=8)

    def _b64_encode(self):
        text = self.b64_input.get("1.0", tk.END).strip()
        encoded = base64.b64encode(text.encode("utf-8")).decode()
        self._set_output(self.b64_output, encoded)
        self.status_var.set("Base64エンコードしました")

    def _b64_decode(self):
        text = self.b64_input.get("1.0", tk.END).strip()
        try:
            decoded = base64.b64decode(text).decode("utf-8")
            self._set_output(self.b64_output, decoded)
            self.status_var.set("Base64デコードしました")
        except Exception as e:
            messagebox.showerror("エラー", f"デコード失敗: {e}")

    # ── ユーティリティ ────────────────────────────────────────────

    def _set_output(self, widget, text):
        widget.config(state=tk.NORMAL)
        widget.delete("1.0", tk.END)
        widget.insert("1.0", text)
        widget.config(state=tk.DISABLED)

    def _copy_output(self, widget):
        text = widget.get("1.0", tk.END).strip()
        self.root.clipboard_clear()
        self.root.clipboard_append(text)
        self.status_var.set("クリップボードにコピーしました")


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

例外処理とmessagebox

try-except で ValueError と Exception を捕捉し、messagebox.showerror() でユーザーにわかりやすいエラーメッセージを表示します。入力バリデーションは必ず実装しましょう。

import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import os
import base64
import hashlib
import secrets

try:
    from cryptography.fernet import Fernet
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import rsa, padding
    from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
    CRYPTO_AVAILABLE = True
except ImportError:
    CRYPTO_AVAILABLE = False


class App32:
    """暗号化・復号ツール"""

    def __init__(self, root):
        self.root = root
        self.root.title("暗号化・復号ツール")
        self.root.geometry("860x620")
        self.root.configure(bg="#1a1a2e")
        self._fernet = None
        self._rsa_private = None
        self._rsa_public = None
        self._build_ui()

    def _build_ui(self):
        # ヘッダー
        header = tk.Frame(self.root, bg="#16213e", pady=8)
        header.pack(fill=tk.X)
        tk.Label(header, text="🔐 暗号化・復号ツール",
                 font=("Noto Sans JP", 13, "bold"),
                 bg="#16213e", fg="#e94560").pack(side=tk.LEFT, padx=12)

        if not CRYPTO_AVAILABLE:
            tk.Label(self.root,
                     text="⚠ cryptographyライブラリが未インストールです "
                          "(pip install cryptography)。",
                     bg="#fff3cd", fg="#856404", font=("Arial", 9),
                     anchor="w", padx=8).pack(fill=tk.X)

        notebook = ttk.Notebook(self.root)
        notebook.pack(fill=tk.BOTH, expand=True, padx=6, pady=6)

        # ── タブ1: Fernet(AES-128 CBC)──────────────────────────
        fernet_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(fernet_tab, text="🔑 Fernet (AES)")
        self._build_fernet_tab(fernet_tab)

        # ── タブ2: RSA ──────────────────────────────────────────
        rsa_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(rsa_tab, text="🔒 RSA")
        self._build_rsa_tab(rsa_tab)

        # ── タブ3: ハッシュ ─────────────────────────────────────
        hash_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(hash_tab, text="🔎 ハッシュ")
        self._build_hash_tab(hash_tab)

        # ── タブ4: Base64 ───────────────────────────────────────
        b64_tab = tk.Frame(notebook, bg="#1a1a2e")
        notebook.add(b64_tab, text="📄 Base64")
        self._build_b64_tab(b64_tab)

        self.status_var = tk.StringVar(value="アルゴリズムを選択してください")
        tk.Label(self.root, textvariable=self.status_var,
                 bg="#16213e", fg="#858585", font=("Arial", 9),
                 anchor="w", padx=8).pack(fill=tk.X, side=tk.BOTTOM)

    # ── Fernet タブ ──────────────────────────────────────────────

    def _build_fernet_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}

        # キー設定
        key_f = ttk.LabelFrame(parent, text="キー設定", padding=8)
        key_f.pack(fill=tk.X, padx=8, pady=6)
        row1 = tk.Frame(key_f, bg=key_f.cget("background"))
        row1.pack(fill=tk.X)
        tk.Label(row1, text="パスワード:", **lbl_s,
                 bg=row1.cget("bg")).pack(side=tk.LEFT)
        self.fernet_pw_var = tk.StringVar()
        ttk.Entry(row1, textvariable=self.fernet_pw_var, show="*",
                  width=30).pack(side=tk.LEFT, padx=6)
        ttk.Button(row1, text="🔑 キー生成",
                   command=self._gen_fernet_key).pack(side=tk.LEFT, padx=4)
        ttk.Button(row1, text="🎲 ランダム生成",
                   command=self._random_fernet_key).pack(side=tk.LEFT, padx=4)

        row2 = tk.Frame(key_f, bg=key_f.cget("background"))
        row2.pack(fill=tk.X, pady=4)
        tk.Label(row2, text="Fernetキー:", **lbl_s,
                 bg=row2.cget("bg")).pack(side=tk.LEFT)
        self.fernet_key_var = tk.StringVar()
        ttk.Entry(row2, textvariable=self.fernet_key_var,
                  width=60).pack(side=tk.LEFT, padx=6, fill=tk.X, expand=True)

        # テキスト入出力
        io_f = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        io_f.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

        left = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(left, weight=1)
        tk.Label(left, text="入力テキスト", **lbl_s).pack(anchor="w", pady=2)
        self.fernet_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                     font=("Courier New", 11), relief=tk.FLAT,
                                     height=12)
        self.fernet_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(right, weight=1)
        tk.Label(right, text="出力テキスト", **lbl_s).pack(anchor="w", pady=2)
        self.fernet_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                      font=("Courier New", 11), relief=tk.FLAT,
                                      height=12, state=tk.DISABLED)
        self.fernet_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="🔒 暗号化",
                   command=self._fernet_encrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="🔓 復号",
                   command=self._fernet_decrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="📋 出力をコピー",
                   command=lambda: self._copy_output(self.fernet_output)
                   ).pack(side=tk.LEFT, padx=8)

    def _gen_fernet_key(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        pw = self.fernet_pw_var.get()
        if not pw:
            messagebox.showwarning("警告", "パスワードを入力してください")
            return
        salt = b"pythonland_salt_"
        kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32,
                          salt=salt, iterations=100000)
        key = base64.urlsafe_b64encode(kdf.derive(pw.encode()))
        self.fernet_key_var.set(key.decode())
        self._fernet = Fernet(key)
        self.status_var.set("Fernetキーを生成しました")

    def _random_fernet_key(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        key = Fernet.generate_key()
        self.fernet_key_var.set(key.decode())
        self._fernet = Fernet(key)
        self.status_var.set("ランダムFernetキーを生成しました")

    def _get_fernet(self):
        key_str = self.fernet_key_var.get().strip()
        if not key_str:
            messagebox.showwarning("警告", "キーを生成してください")
            return None
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return None
        try:
            return Fernet(key_str.encode())
        except Exception as e:
            messagebox.showerror("エラー", f"無効なキー: {e}")
            return None

    def _fernet_encrypt(self):
        f = self._get_fernet()
        if not f:
            return
        text = self.fernet_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            token = f.encrypt(text.encode("utf-8"))
            self._set_output(self.fernet_output, token.decode())
            self.status_var.set("暗号化しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _fernet_decrypt(self):
        f = self._get_fernet()
        if not f:
            return
        text = self.fernet_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            plain = f.decrypt(text.encode())
            self._set_output(self.fernet_output, plain.decode("utf-8"))
            self.status_var.set("復号しました")
        except Exception as e:
            messagebox.showerror("復号エラー",
                                 "復号に失敗しました。キーまたはデータが不正です。\n" + str(e))

    # ── RSA タブ ──────────────────────────────────────────────────

    def _build_rsa_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}

        key_f = ttk.LabelFrame(parent, text="RSAキーペア生成", padding=8)
        key_f.pack(fill=tk.X, padx=8, pady=6)
        btn_row = tk.Frame(key_f, bg=key_f.cget("background"))
        btn_row.pack(fill=tk.X)
        tk.Label(btn_row, text="鍵長:", bg=btn_row.cget("bg"),
                 fg="#c9d1d9").pack(side=tk.LEFT)
        self.rsa_bits_var = tk.StringVar(value="2048")
        ttk.Combobox(btn_row, textvariable=self.rsa_bits_var,
                     values=["1024", "2048", "4096"],
                     state="readonly", width=8).pack(side=tk.LEFT, padx=6)
        ttk.Button(btn_row, text="🔑 キーペア生成",
                   command=self._gen_rsa_keys).pack(side=tk.LEFT, padx=8)

        paned = ttk.PanedWindow(key_f, orient=tk.HORIZONTAL)
        paned.pack(fill=tk.X, pady=4)
        for title, attr in [("公開鍵 (暗号化用)", "rsa_pub_text"),
                             ("秘密鍵 (復号用)", "rsa_priv_text")]:
            f = tk.Frame(paned, bg="#1a1a2e")
            paned.add(f, weight=1)
            tk.Label(f, text=title, **lbl_s).pack(anchor="w")
            txt = tk.Text(f, bg="#0d1117", fg="#c9d1d9",
                          font=("Courier New", 8), height=6, state=tk.DISABLED)
            txt.pack(fill=tk.X)
            setattr(self, attr, txt)

        io_f = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        io_f.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

        left = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(left, weight=1)
        tk.Label(left, text="入力テキスト", **lbl_s).pack(anchor="w")
        self.rsa_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                  font=("Courier New", 11), height=8)
        self.rsa_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(io_f, bg="#1a1a2e")
        io_f.add(right, weight=1)
        tk.Label(right, text="出力テキスト", **lbl_s).pack(anchor="w")
        self.rsa_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                   font=("Courier New", 11), height=8,
                                   state=tk.DISABLED)
        self.rsa_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="🔒 公開鍵で暗号化",
                   command=self._rsa_encrypt).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="🔓 秘密鍵で復号",
                   command=self._rsa_decrypt).pack(side=tk.LEFT, padx=8)

    def _gen_rsa_keys(self):
        if not CRYPTO_AVAILABLE:
            messagebox.showerror("エラー", "cryptography が必要です")
            return
        bits = int(self.rsa_bits_var.get())
        self.status_var.set(f"RSA {bits}bitキーを生成中...")
        self.root.update()
        try:
            self._rsa_private = rsa.generate_private_key(
                public_exponent=65537, key_size=bits)
            self._rsa_public = self._rsa_private.public_key()
            priv_pem = self._rsa_private.private_bytes(
                serialization.Encoding.PEM,
                serialization.PrivateFormat.TraditionalOpenSSL,
                serialization.NoEncryption()).decode()
            pub_pem = self._rsa_public.public_bytes(
                serialization.Encoding.PEM,
                serialization.PublicFormat.SubjectPublicKeyInfo).decode()
            self._set_output(self.rsa_pub_text, pub_pem)
            self._set_output(self.rsa_priv_text, priv_pem)
            self.status_var.set(f"RSA {bits}bitキーペアを生成しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _rsa_encrypt(self):
        if not CRYPTO_AVAILABLE or not self._rsa_public:
            messagebox.showwarning("警告", "先にキーペアを生成してください")
            return
        text = self.rsa_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            cipher = self._rsa_public.encrypt(
                text.encode("utf-8"),
                padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                             algorithm=hashes.SHA256(), label=None))
            self._set_output(self.rsa_output, base64.b64encode(cipher).decode())
            self.status_var.set("RSA暗号化しました")
        except Exception as e:
            messagebox.showerror("エラー", str(e))

    def _rsa_decrypt(self):
        if not CRYPTO_AVAILABLE or not self._rsa_private:
            messagebox.showwarning("警告", "先にキーペアを生成してください")
            return
        text = self.rsa_input.get("1.0", tk.END).strip()
        if not text:
            return
        try:
            cipher = base64.b64decode(text)
            plain = self._rsa_private.decrypt(
                cipher,
                padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                             algorithm=hashes.SHA256(), label=None))
            self._set_output(self.rsa_output, plain.decode("utf-8"))
            self.status_var.set("RSA復号しました")
        except Exception as e:
            messagebox.showerror("復号エラー", str(e))

    # ── ハッシュ タブ ──────────────────────────────────────────────

    def _build_hash_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}
        tk.Label(parent, text="テキスト:", **lbl_s).pack(anchor="w", padx=8, pady=4)
        self.hash_input = tk.Text(parent, bg="#0d1117", fg="#c9d1d9",
                                   font=("Courier New", 11), height=6,
                                   relief=tk.FLAT)
        self.hash_input.pack(fill=tk.X, padx=8)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(fill=tk.X, padx=8, pady=6)
        for algo in ["MD5", "SHA1", "SHA256", "SHA512", "SHA3-256"]:
            ttk.Button(btn_f, text=algo,
                       command=lambda a=algo: self._compute_hash(a)
                       ).pack(side=tk.LEFT, padx=4)

        # ファイルハッシュ
        file_f = ttk.LabelFrame(parent, text="ファイルハッシュ", padding=6)
        file_f.pack(fill=tk.X, padx=8, pady=4)
        file_row = tk.Frame(file_f, bg=file_f.cget("background"))
        file_row.pack(fill=tk.X)
        self.hash_file_var = tk.StringVar()
        ttk.Entry(file_row, textvariable=self.hash_file_var,
                  width=40).pack(side=tk.LEFT, fill=tk.X, expand=True)
        ttk.Button(file_row, text="参照",
                   command=self._browse_hash_file).pack(side=tk.LEFT, padx=4)
        ttk.Button(file_row, text="SHA256 計算",
                   command=self._file_hash).pack(side=tk.LEFT, padx=4)

        tk.Label(parent, text="ハッシュ結果:", **lbl_s).pack(anchor="w", padx=8, pady=4)
        self.hash_output = tk.Text(parent, bg="#0d1117", fg="#3fb950",
                                    font=("Courier New", 11), height=10,
                                    state=tk.DISABLED, relief=tk.FLAT)
        self.hash_output.pack(fill=tk.BOTH, expand=True, padx=8, pady=4)

    def _compute_hash(self, algo):
        text = self.hash_input.get("1.0", tk.END).strip()
        data = text.encode("utf-8")
        algo_map = {
            "MD5": hashlib.md5,
            "SHA1": hashlib.sha1,
            "SHA256": hashlib.sha256,
            "SHA512": hashlib.sha512,
            "SHA3-256": hashlib.sha3_256,
        }
        h = algo_map[algo](data).hexdigest()
        result = f"[{algo}]\n{h}\n\n"
        self.hash_output.config(state=tk.NORMAL)
        self.hash_output.insert(tk.END, result)
        self.hash_output.config(state=tk.DISABLED)
        self.status_var.set(f"{algo}: {h[:20]}...")

    def _browse_hash_file(self):
        path = filedialog.askopenfilename()
        if path:
            self.hash_file_var.set(path)

    def _file_hash(self):
        path = self.hash_file_var.get()
        if not path or not os.path.exists(path):
            messagebox.showwarning("警告", "ファイルを選択してください")
            return
        h = hashlib.sha256()
        with open(path, "rb") as f:
            for chunk in iter(lambda: f.read(65536), b""):
                h.update(chunk)
        result = f"[SHA256] {os.path.basename(path)}\n{h.hexdigest()}\n\n"
        self.hash_output.config(state=tk.NORMAL)
        self.hash_output.insert(tk.END, result)
        self.hash_output.config(state=tk.DISABLED)

    # ── Base64 タブ ──────────────────────────────────────────────

    def _build_b64_tab(self, parent):
        lbl_s = {"bg": "#1a1a2e", "fg": "#c9d1d9", "font": ("Arial", 10)}
        paned = ttk.PanedWindow(parent, orient=tk.HORIZONTAL)
        paned.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)

        left = tk.Frame(paned, bg="#1a1a2e")
        paned.add(left, weight=1)
        tk.Label(left, text="入力", **lbl_s).pack(anchor="w")
        self.b64_input = tk.Text(left, bg="#0d1117", fg="#c9d1d9",
                                  font=("Courier New", 11), relief=tk.FLAT)
        self.b64_input.pack(fill=tk.BOTH, expand=True)

        right = tk.Frame(paned, bg="#1a1a2e")
        paned.add(right, weight=1)
        tk.Label(right, text="出力", **lbl_s).pack(anchor="w")
        self.b64_output = tk.Text(right, bg="#0d1117", fg="#3fb950",
                                   font=("Courier New", 11), relief=tk.FLAT,
                                   state=tk.DISABLED)
        self.b64_output.pack(fill=tk.BOTH, expand=True)

        btn_f = tk.Frame(parent, bg="#1a1a2e")
        btn_f.pack(pady=6)
        ttk.Button(btn_f, text="▶ Base64 エンコード",
                   command=self._b64_encode).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="◀ Base64 デコード",
                   command=self._b64_decode).pack(side=tk.LEFT, padx=8)
        ttk.Button(btn_f, text="📋 コピー",
                   command=lambda: self._copy_output(self.b64_output)
                   ).pack(side=tk.LEFT, padx=8)

    def _b64_encode(self):
        text = self.b64_input.get("1.0", tk.END).strip()
        encoded = base64.b64encode(text.encode("utf-8")).decode()
        self._set_output(self.b64_output, encoded)
        self.status_var.set("Base64エンコードしました")

    def _b64_decode(self):
        text = self.b64_input.get("1.0", tk.END).strip()
        try:
            decoded = base64.b64decode(text).decode("utf-8")
            self._set_output(self.b64_output, decoded)
            self.status_var.set("Base64デコードしました")
        except Exception as e:
            messagebox.showerror("エラー", f"デコード失敗: {e}")

    # ── ユーティリティ ────────────────────────────────────────────

    def _set_output(self, widget, text):
        widget.config(state=tk.NORMAL)
        widget.delete("1.0", tk.END)
        widget.insert("1.0", text)
        widget.config(state=tk.DISABLED)

    def _copy_output(self, widget):
        text = widget.get("1.0", tk.END).strip()
        self.root.clipboard_clear()
        self.root.clipboard_append(text)
        self.status_var.set("クリップボードにコピーしました")


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

6. ステップバイステップガイド

このアプリをゼロから自分で作る手順を解説します。コードをコピーするだけでなく、実際に手順を追って自分で書いてみましょう。

  1. 1
    ファイルを作成する

    新しいファイルを作成して app32.py と保存します。

  2. 2
    クラスの骨格を作る

    App32クラスを定義し、__init__とmainloop()の最小構成を作ります。

  3. 3
    タイトルバーを作る

    Frameを使ってカラーバー付きのタイトルエリアを作ります。

  4. 4
    入力フォームを実装する

    LabelFrameとEntryウィジェットで入力エリアを作ります。

  5. 5
    処理ロジックを実装する

    _calculate()メソッドに計算・処理ロジックを実装します。

  6. 6
    結果表示を実装する

    TextウィジェットかLabelに結果を表示する_show_result()を実装します。

  7. 7
    エラー処理を追加する

    try-exceptとmessageboxでエラーハンドリングを追加します。

7. カスタマイズアイデア

基本機能を習得したら、以下のカスタマイズに挑戦してみましょう。少しずつ機能を追加することで、Pythonのスキルが飛躍的に向上します。

💡 ダークモードを追加する

bg色・fg色を辞書で管理し、ボタン1つでダークモード・ライトモードを切り替えられるようにしましょう。

💡 データのエクスポート機能

計算結果をCSV・TXTファイルに保存するエクスポート機能を追加しましょう。filedialog.asksaveasfilename()でファイル保存ダイアログが使えます。

💡 入力履歴機能

以前の入力値を覚えておいてComboboxのドロップダウンで再選択できる履歴機能を追加しましょう。

8. よくある問題と解決法

❌ 日本語フォントが表示されない

原因:システムに日本語フォントが見つからない場合があります。

解決法:font引数を省略するかシステムに合ったフォントを指定してください。

❌ ウィンドウのサイズが変更できない

原因:resizable(False, False)が設定されています。

解決法:resizable(True, True)に変更してください。

9. 練習問題

アプリの理解を深めるための練習問題です。難易度順に挑戦してみてください。

  1. 課題1:機能拡張

    暗号化・復号ツールに新しい機能を1つ追加してみましょう。どんな機能があると便利か考えてから実装してください。

  2. 課題2:UIの改善

    色・フォント・レイアウトを変更して、より使いやすいUIにカスタマイズしてみましょう。

  3. 課題3:保存機能の追加

    入力値や計算結果をファイルに保存する機能を追加しましょう。jsonやcsvモジュールを使います。

🚀
次に挑戦するアプリ

このアプリをマスターしたら、次のNo.33に挑戦しましょう。