python写的ECC算法支持 sect283r1曲线助手工具,带UI界面的

在这里插入图片描述
在这里插入图片描述
已完成可运行的 sect283r1 ECC 桌面助手:
生成、导入、导出 PEM 密钥
ECDSA-SHA256 签名与验签
ECIES 风格加解密(ECDH + HKDF + AES-256-GCM)
Tkinter 图形界面
已实际验证 sect283r1 的签名/验签和加解密流程通过。

py -m pip install -r requirements.txt
py ecc_sect283r1_tool.py

"""sect283r1 ECC desktop assistant.

Signature: ECDSA with SHA-256.
Encryption: ECIES-style ECDH + HKDF-SHA256 + AES-256-GCM.
"""

import base64
import json
import os
import subprocess
import tempfile
import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext, ttk

from cryptography.exceptions import InvalidTag
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.hkdf import HKDF


AAD = b"ECC-sect283r1-ECIES-v1"


def b64(data: bytes) -> str:
    return base64.b64encode(data).decode("ascii")


def unb64(value: str) -> bytes:
    return base64.b64decode(value.encode("ascii"), validate=True)


def openssl(args, data=None) -> bytes:
    """Run local OpenSSL, surfacing a concise error in the UI."""
    try:
        result = subprocess.run(["openssl", *args], input=data, capture_output=True, check=True)
        return result.stdout
    except FileNotFoundError as exc:
        raise RuntimeError("未找到 OpenSSL。请安装 OpenSSL 并加入系统 PATH。") from exc
    except subprocess.CalledProcessError as exc:
        raise ValueError(exc.stderr.decode("utf-8", "replace").strip() or "OpenSSL 操作失败") from exc


def temp_pem(content: str):
    fd, path = tempfile.mkstemp(suffix=".pem")
    with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as file:
        file.write(content)
    return path


def assert_curve(pem: str, private=False):
    path = temp_pem(pem)
    try:
        args = ["pkey", "-in", path, "-text", "-noout"]
        if not private: args.insert(1, "-pubin")
        details = openssl(args).decode("utf-8", "replace")
        if "sect283r1" not in details:
            raise ValueError("密钥不是 sect283r1 格式")
    finally:
        os.remove(path)


def derive_key(private_pem: str, public_pem: str, salt: bytes) -> bytes:
    private_path, public_path = temp_pem(private_pem), temp_pem(public_pem)
    try:
        shared = openssl(["pkeyutl", "-derive", "-inkey", private_path, "-peerkey", public_path])
    finally:
        os.remove(private_path); os.remove(public_path)
    return HKDF(algorithm=hashes.SHA256(), length=32, salt=salt, info=AAD).derive(shared)


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("ECC sect283r1 算法助手")
        self.geometry("930x760")
        self.minsize(760, 600)
        self.style = ttk.Style(self)
        self.style.configure("TButton", padding=6)
        self._build()

    def _text(self, parent, height=8):
        box = scrolledtext.ScrolledText(parent, height=height, wrap=tk.WORD, font=("Consolas", 10))
        return box

    @staticmethod
    def get(box):
        return box.get("1.0", "end-1c").strip()

    @staticmethod
    def put(box, value):
        box.delete("1.0", tk.END)
        box.insert("1.0", value)

    def _build(self):
        note = ttk.Label(self, text="曲线:sect283r1  ·  签名:ECDSA-SHA256  ·  加密:ECDH + HKDF + AES-256-GCM", foreground="#245a8d")
        note.pack(fill="x", padx=12, pady=(12, 4))
        tabs = ttk.Notebook(self)
        tabs.pack(expand=True, fill="both", padx=12, pady=8)
        self.keys_tab(tabs)
        self.sign_tab(tabs)
        self.encrypt_tab(tabs)

    def keys_tab(self, tabs):
        frame = ttk.Frame(tabs, padding=10)
        tabs.add(frame, text="密钥管理")
        ttk.Button(frame, text="生成 sect283r1 密钥对", command=self.generate).pack(anchor="w", pady=(0, 8))
        ttk.Label(frame, text="私钥 PEM(请妥善保管,不要分享)").pack(anchor="w")
        self.private_box = self._text(frame, 12); self.private_box.pack(fill="both", expand=True, pady=(2, 8))
        ttk.Label(frame, text="公钥 PEM(可公开)").pack(anchor="w")
        self.public_box = self._text(frame, 10); self.public_box.pack(fill="both", expand=True, pady=(2, 8))
        row = ttk.Frame(frame); row.pack(fill="x")
        ttk.Button(row, text="导入私钥文件", command=lambda: self.import_file(self.private_box)).pack(side="left", padx=(0, 6))
        ttk.Button(row, text="导入公钥文件", command=lambda: self.import_file(self.public_box)).pack(side="left", padx=6)
        ttk.Button(row, text="导出私钥", command=lambda: self.export_file(self.private_box, "private_key.pem")).pack(side="left", padx=6)
        ttk.Button(row, text="导出公钥", command=lambda: self.export_file(self.public_box, "public_key.pem")).pack(side="left", padx=6)

    def sign_tab(self, tabs):
        frame = ttk.Frame(tabs, padding=10); tabs.add(frame, text="签名与验签")
        ttk.Label(frame, text="原文").pack(anchor="w"); self.sign_message = self._text(frame, 6); self.sign_message.pack(fill="x")
        ttk.Label(frame, text="签名(Base64 / DER)").pack(anchor="w", pady=(8, 0)); self.signature = self._text(frame, 5); self.signature.pack(fill="x")
        row = ttk.Frame(frame); row.pack(fill="x", pady=8)
        ttk.Button(row, text="使用当前私钥签名", command=self.sign).pack(side="left", padx=(0, 8))
        ttk.Button(row, text="使用当前公钥验签", command=self.verify).pack(side="left")
        self.sign_result = ttk.Label(frame, text="", font=("Microsoft YaHei UI", 10)); self.sign_result.pack(anchor="w")

    def encrypt_tab(self, tabs):
        frame = ttk.Frame(tabs, padding=10); tabs.add(frame, text="加密与解密")
        ttk.Label(frame, text="明文(UTF-8)").pack(anchor="w"); self.plaintext = self._text(frame, 6); self.plaintext.pack(fill="x")
        ttk.Label(frame, text="密文包(Base64 JSON,可复制传递)").pack(anchor="w", pady=(8, 0)); self.ciphertext = self._text(frame, 8); self.ciphertext.pack(fill="both", expand=True)
        row = ttk.Frame(frame); row.pack(fill="x", pady=8)
        ttk.Button(row, text="使用当前公钥加密", command=self.encrypt).pack(side="left", padx=(0, 8))
        ttk.Button(row, text="使用当前私钥解密", command=self.decrypt).pack(side="left")
        self.crypt_result = ttk.Label(frame, text="", font=("Microsoft YaHei UI", 10)); self.crypt_result.pack(anchor="w")

    def generate(self):
        private = openssl(["genpkey", "-algorithm", "EC", "-pkeyopt", "ec_paramgen_curve:sect283r1"]).decode()
        private_path = temp_pem(private)
        try:
            public = openssl(["pkey", "-in", private_path, "-pubout"]).decode()
        finally:
            os.remove(private_path)
        self.put(self.private_box, private); self.put(self.public_box, public)
        messagebox.showinfo("完成", "已生成 sect283r1 密钥对。")

    def import_file(self, box):
        path = filedialog.askopenfilename(filetypes=[("PEM files", "*.pem"), ("All files", "*.*")])
        if path:
            try:
                with open(path, "r", encoding="utf-8") as f: self.put(box, f.read())
            except OSError as exc: messagebox.showerror("读取失败", str(exc))

    def export_file(self, box, default):
        value = self.get(box)
        if not value: return messagebox.showwarning("没有内容", "请先生成或导入密钥。")
        path = filedialog.asksaveasfilename(defaultextension=".pem", initialfile=default, filetypes=[("PEM files", "*.pem")])
        if path:
            with open(path, "w", encoding="utf-8", newline="\n") as f: f.write(value + "\n")

    def sign(self):
        try:
            private = self.get(self.private_box); assert_curve(private, private=True)
            private_path = temp_pem(private)
            try:
                signature = openssl(["dgst", "-sha256", "-sign", private_path], self.get(self.sign_message).encode())
            finally:
                os.remove(private_path)
            self.put(self.signature, b64(signature)); self.sign_result.config(text="签名完成。", foreground="#19733a")
        except Exception as exc: self.sign_result.config(text=f"失败:{exc}", foreground="#b3261e")

    def verify(self):
        try:
            public = self.get(self.public_box); assert_curve(public)
            public_path = temp_pem(public)
            signature_path = None
            try:
                fd, signature_path = tempfile.mkstemp(suffix=".sig")
                with os.fdopen(fd, "wb") as file: file.write(unb64(self.get(self.signature)))
                result = subprocess.run(["openssl", "dgst", "-sha256", "-verify", public_path, "-signature", signature_path], input=self.get(self.sign_message).encode(), capture_output=True)
                if result.returncode != 0: raise ValueError("验签未通过:签名无效或原文已被修改。")
            finally:
                os.remove(public_path)
                if signature_path: os.remove(signature_path)
            self.sign_result.config(text="验签通过:签名有效。", foreground="#19733a")
        except Exception as exc: self.sign_result.config(text=f"失败:{exc}", foreground="#b3261e")

    def encrypt(self):
        try:
            recipient = self.get(self.public_box); assert_curve(recipient)
            ephemeral = openssl(["genpkey", "-algorithm", "EC", "-pkeyopt", "ec_paramgen_curve:sect283r1"]).decode()
            ephemeral_path = temp_pem(ephemeral)
            try:
                ephemeral_public = openssl(["pkey", "-in", ephemeral_path, "-pubout"]).decode()
            finally:
                os.remove(ephemeral_path)
            salt, nonce = __import__("os").urandom(16), __import__("os").urandom(12)
            payload = AESGCM(derive_key(ephemeral, recipient, salt)).encrypt(nonce, self.get(self.plaintext).encode(), AAD)
            envelope = {"v": 1, "curve": "sect283r1", "ephemeral_public_key": b64(ephemeral_public.encode()), "salt": b64(salt), "nonce": b64(nonce), "ciphertext": b64(payload)}
            self.put(self.ciphertext, b64(json.dumps(envelope, separators=(",", ":")).encode()))
            self.crypt_result.config(text="加密完成。", foreground="#19733a")
        except Exception as exc: self.crypt_result.config(text=f"失败:{exc}", foreground="#b3261e")

    def decrypt(self):
        try:
            env = json.loads(unb64(self.get(self.ciphertext)).decode())
            if env.get("v") != 1 or env.get("curve") != "sect283r1": raise ValueError("不支持的密文格式")
            ephemeral = unb64(env["ephemeral_public_key"]).decode("utf-8")
            assert_curve(ephemeral)
            private = self.get(self.private_box); assert_curve(private, private=True)
            plain = AESGCM(derive_key(private, ephemeral, unb64(env["salt"]))).decrypt(unb64(env["nonce"]), unb64(env["ciphertext"]), AAD)
            self.put(self.plaintext, plain.decode("utf-8")); self.crypt_result.config(text="解密完成,认证通过。", foreground="#19733a")
        except InvalidTag: self.crypt_result.config(text="解密失败:密文已被篡改或密钥不匹配。", foreground="#b3261e")
        except Exception as exc: self.crypt_result.config(text=f"失败:{exc}", foreground="#b3261e")


if __name__ == "__main__":
    App().mainloop()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小黄人软件

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值