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

Python实现:通过PIN提取PKCS11 USB令牌证书私钥并签名PDF

解决方案

1. 补全会话登录与证书关联信息

首先需要通过PIN登录令牌会话,同时保存每个证书对应的CKA_ID(PKCS11标准中,证书与对应私钥通过CKA_ID关联)。修改你的初始代码:

from PyKCS11 import *
from PyKCS11.LowLevel import *
from asn1crypto import x509
# 导入PDF签名依赖库
from pyhanko.sign import signers
from pyhanko_certvalidator import ValidationContext
from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter
import io

pkcs11 = PyKCS11Lib()
pkcs11.load('c:\\windows\\system32\\eTPKCS11.dll')

# 获取存在令牌的槽位(修正tokenPresent参数为True)
slots = pkcs11.getSlotList(tokenPresent=True)
session = pkcs11.openSession(slots[0], CKF_SERIAL_SESSION | CKF_RW_SESSION)

# 输入PIN并登录会话
pin = input("Enter your token PIN: ")
session.login(pin)

certs = session.findObjects([(CKA_CLASS, CKO_CERTIFICATE)])
certificate_entries = []

for cert in certs:
    cka_value, cka_id = session.getAttributeValue(cert, [CKA_VALUE, CKA_ID])
    cert_der = bytes(cka_value)
    certificate = x509.Certificate.load(cert_der)
    # 保存证书及对应CKA_ID,用于匹配私钥
    certificate_entries.append({
        'cert': certificate,
        'cka_id': bytes(cka_id)
    })

print("Available certificates:")
for i, entry in enumerate(certificate_entries):
    print(f"{i + 1}. {entry['cert'].subject.human_friendly}")

selected_index = int(input("Please enter the number of certificate you want to use for signing: ")) - 1
selected_entry = certificate_entries[selected_index]

2. 匹配选中证书的私钥

利用证书的CKA_ID查找令牌上对应的可签名私钥:

# 构造私钥查询模板
private_key_template = [
    (CKA_CLASS, CKO_PRIVATE_KEY),
    (CKA_ID, selected_entry['cka_id']),
    (CKA_SIGN, CK_TRUE)  # 筛选支持签名操作的私钥
]
private_keys = session.findObjects(private_key_template)
if not private_keys:
    raise ValueError("No corresponding signable private key found for the selected certificate")
private_key = private_keys[0]

3. 封装PKCS11签名器

封装PyKCS11的私钥操作,适配PDF签名库的接口:

class PKCS11Signer:
    def __init__(self, session, private_key, cert):
        self.session = session
        self.private_key = private_key
        self.cert = cert

    def sign(self, data, algorithm):
        # 选择签名机制,这里以SHA256-RSA为例,若为EC证书请改用CKM_ECDSA_SHA256
        mechanism = PyKCS11.Mechanism(CKM_SHA256_RSA_PKCS, None)
        signature = self.session.sign(self.private_key, data, mechanism)
        return bytes(signature)

    @property
    def signing_cert(self):
        return self.cert

4. 执行PDF签名

使用pyhanko完成PDF的数字签名:

# 配置输入输出路径
input_pdf = "input.pdf"
output_pdf = "signed_output.pdf"

# 读取待签名PDF
with open(input_pdf, 'rb') as f:
    writer = IncrementalPdfFileWriter(f)

# 创建签名器实例
signer = PKCS11Signer(session, private_key, selected_entry['cert'])

# 执行签名
signers.sign_pdf(
    writer,
    signers.PdfSignatureMetadata(field_name="SignatureField"),
    signer=signer,
    validation_context=ValidationContext(allow_self_signed=True)
)

# 保存签名后的PDF
with open(output_pdf, 'wb') as f:
    writer.write(f)

# 清理会话
session.logout()
session.closeSession()

print(f"PDF signed successfully. Output file: {output_pdf}")

注意事项

  • 签名机制适配:如果你的证书是椭圆曲线(EC)类型,需将签名机制改为CKM_ECDSA_SHA256。
  • 依赖安装:执行前需安装所需依赖:
    pip install pyhanko pyhanko-certvalidator asn1crypto cryptography
    
  • 槽位选择:如果系统中有多个PKCS11令牌,需调整slots[0]为对应槽位索引。

内容的提问来源于stack exchange,提问作者xenax

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 15:59:51