You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 23:22:09