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
相关产品推荐
相关产品推荐

