

已完成可运行的 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()
704

被折叠的 条评论
为什么被折叠?



