如何用Python开发桌面工具,通过USB令牌签名PDF文件?
Python USB令牌签名PDF工具实现方案
一、Tkinter 界面搭建
先实现基础交互UI,包含源PDF选择、导出路径选择、PIN输入和签名触发按钮:
import tkinter as tk from tkinter import filedialog, messagebox class PDFSignerApp: def __init__(self, root): self.root = root self.root.title("PDF USB令牌签名工具") # 源PDF路径选择 self.source_path = tk.StringVar() tk.Label(root, text="源PDF文件:").grid(row=0, column=0, padx=5, pady=5) tk.Entry(root, textvariable=self.source_path, width=50).grid(row=0, column=1, padx=5, pady=5) tk.Button(root, text="选择文件", command=self.select_source_pdf).grid(row=0, column=2, padx=5, pady=5) # 导出路径选择 self.output_path = tk.StringVar() tk.Label(root, text="导出路径:").grid(row=1, column=0, padx=5, pady=5) tk.Entry(root, textvariable=self.output_path, width=50).grid(row=1, column=1, padx=5, pady=5) tk.Button(root, text="选择路径", command=self.select_output_path).grid(row=1, column=2, padx=5, pady=5) # PIN码输入框 self.pin_var = tk.StringVar() tk.Label(root, text="PIN码:").grid(row=2, column=0, padx=5, pady=5) tk.Entry(root, textvariable=self.pin_var, show="*", width=20).grid(row=2, column=1, padx=5, pady=5) # 签名触发按钮 tk.Button(root, text="签名PDF", command=self.sign_pdf).grid(row=3, column=1, pady=10) def select_source_pdf(self): path = filedialog.askopenfilename(filetypes=[("PDF文件", "*.pdf")]) if path: self.source_path.set(path) def select_output_path(self): path = filedialog.asksaveasfilename(defaultextension=".pdf", filetypes=[("PDF文件", "*.pdf")]) if path: self.output_path.set(path) def sign_pdf(self): source = self.source_path.get() output = self.output_path.get() pin = self.pin_var.get() if not source or not output or not pin: messagebox.showerror("错误", "请填写完整信息") return try: # 调用令牌操作和PDF签名逻辑 signature, cert = get_token_signature_and_cert(pin, source) embed_signature_to_pdf(source, output, signature, cert) messagebox.showinfo("成功", "PDF签名完成") except Exception as e: messagebox.showerror("错误", f"签名失败: {str(e)}") if __name__ == "__main__": root = tk.Tk() app = PDFSignerApp(root) root.mainloop()
二、pyscard 操作USB令牌
pyscard通过APDU指令与USB令牌交互,以下是通用流程(注意:不同厂商的令牌APDU指令差异极大,必须替换为对应令牌的官方指令):
1. 安装依赖
pip install pyscard
2. 核心操作代码
from smartcard.System import readers from smartcard.util import toBytes import hashlib def get_token_signature_and_cert(pin, pdf_path): # 1. 检测并连接读卡器 r = readers() if not r: raise Exception("未检测到USB令牌读卡器") reader = r[0] connection = reader.createConnection() connection.connect() try: # 2. 验证PIN码(替换为你的令牌APDU指令) pin_bytes = toBytes(pin) # 示例指令:部分令牌的PIN验证指令为 00 20 00 00 + PIN长度 + PIN字节 verify_apdu = [0x00, 0x20, 0x00, 0x00, len(pin_bytes)] + pin_bytes _, sw1, sw2 = connection.transmit(verify_apdu) if sw1 != 0x90 or sw2 != 0x00: raise Exception(f"PIN验证失败,返回码: {hex(sw1)}{hex(sw2)}") # 3. 获取令牌内的证书(替换为你的令牌APDU指令) # 示例指令:部分令牌读取证书的指令为 00 B0 00 00 00(读取全部证书数据) get_cert_apdu = [0x00, 0xB0, 0x00, 0x00, 0x00] cert_data, sw1, sw2 = connection.transmit(get_cert_apdu) if sw1 != 0x90 or sw2 != 0x00: raise Exception(f"获取证书失败,返回码: {hex(sw1)}{hex(sw2)}") # 4. 计算PDF的SHA-256哈希值 with open(pdf_path, "rb") as f: pdf_hash = hashlib.sha256(f.read()).digest() # 5. 发送哈希值给令牌签名(替换为你的令牌APDU指令) # 示例指令:部分令牌的签名指令为 00 2A 9E 9A + 哈希长度 + 哈希字节 sign_apdu = [0x00, 0x2A, 0x9E, 0x9A, len(pdf_hash)] + list(pdf_hash) signature_data, sw1, sw2 = connection.transmit(sign_apdu) if sw1 != 0x90 or sw2 != 0x00: raise Exception(f"签名失败,返回码: {hex(sw1)}{hex(sw2)}") return bytes(signature_data), bytes(cert_data) finally: connection.disconnect()
关键提示:APDU指令需参考令牌厂商提供的《PKCS#11规范》或《令牌开发手册》,PIN验证、证书读取、签名的指令每个品牌都不一样,示例仅作流程参考。
三、PDF 嵌入签名
使用PyPDF2和cryptography库实现签名嵌入:
1. 安装依赖
pip install PyPDF2 cryptography
2. 核心代码
from PyPDF2 import PdfWriter, PdfReader from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding from cryptography.x509 import load_der_x509_certificate from cryptography.hazmat.backends import default_backend def embed_signature_to_pdf(source_path, output_path, signature, cert_der): reader = PdfReader(source_path) writer = PdfWriter() # 复制原PDF所有页面 for page in reader.pages: writer.add_page(page) # 加载DER格式的证书 cert = load_der_x509_certificate(cert_der, default_backend()) # 嵌入签名(简化版,完整规范需处理签名字段属性) writer.add_signature( cert, signature, hash_algorithm=hashes.SHA256(), padding=padding.PKCS1v15(), reason="文档合法性签名", location="本地设备" ) # 保存签名后的PDF with open(output_path, "wb") as f: writer.write(f)
注意:上述为基础签名实现,若需符合PDF/A等严格规范,建议使用
pyhanko等专业PDF签名库。
内容的提问来源于stack exchange,提问作者k_amit
相关产品推荐
相关产品推荐

