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

如何在Python中使用带eMudhra DSC的Hypersecure USB令牌生成可验证的可见PDF数字签名?

如何在Python中使用带eMudhra DSC的Hypersecure USB令牌生成可验证的可见PDF数字签名?

我最近尝试用Python结合带eMudhra DSC的Hypersecure USB硬件令牌给PDF做数字签名,目标是生成一个在Adobe Acrobat这类标准PDF查看器里能被验证的可见签名。我用了两个核心库:

  • PyKCS11:负责和硬件令牌进行交互,包括令牌识别、登录、私钥获取和签名操作
  • pikepdf:负责PDF文件的读取、修改以及签名数据的嵌入

以下是我写的完整实现代码:

from csv import reader, writer
from pdfrw import PdfReader, PdfWriter
from pdfrw.objects import PdfDict, PdfName, PdfString, PdfArray
import hashlib
import PyKCS11

class TokenSigner:
    def __init__(self, token_library_path, pin):
        self.pkcs11 = PyKCS11.PyKCS11Lib()
        try:
            self.pkcs11.load(token_library_path)
            print("PKCS11 library loaded successfully.")
        except Exception as e:
            print(f"Error loading PKCS11 library: {e}")
            self.pkcs11 = None

        self.session = None
        self.token_slot = None
        if self.pkcs11:
            self.login(pin)

    def login(self, pin):
        try:
            slots = self.pkcs11.getSlotList(tokenPresent=True)
            if not slots:
                print("No tokens found.")
                return
            self.token_slot = slots[0]
            self.session = self.pkcs11.openSession(self.token_slot,  PyKCS11.CKF_SERIAL_SESSION)
            self.session.login(pin)
            print("Logged in to token.")
        except Exception as e:
            print(f"Error during login: {e}")

    def get_private_key(self):
        try:
            private_keys = self.session.findObjects([(PyKCS11.CKA_CLASS, PyKCS11.CKO_PRIVATE_KEY)])
            if not private_keys:
                print("No private keys found.")
                return None
            for private_key in private_keys:
                key_type = self.session.getAttributeValue(private_key, [PyKCS11.CKA_KEY_TYPE])[0]
                if key_type == PyKCS11.CKK_RSA:
                    print("Found valid RSA private key.")
                    return private_key
            print("No valid RSA private key found.")
        except Exception as e:
            print(f"Error retrieving private key: {e}")
        return None

    def sign(self, private_key, data):
        try:
            print(f"Signing data with private key: {private_key}")
            if private_key is None:
                print("Private key is None!")
                return None
        
            hash_data = hashlib.sha256(data).digest()
            print(f"Hash data: {hash_data.hex()}")

            mechanism = PyKCS11.Mechanism(PyKCS11.CKM_SHA256_RSA_PKCS, None)
            signed_data = self.session.sign(private_key, hash_data, mechanism)
            print("Data signed successfully.")
            return bytes(signed_data)
        except PyKCS11.PyKCS11Error as e:
            print(f"Signing error: {e}")
            return None

    def logout(self):
        try:
            if self.session:
                self.session.logout()
                self.session.closeSession()
                print("Logged out from token.")
        except Exception as e:
            print(f"Error logging out: {e}")

from pikepdf import Pdf, Name, Array, String, Dictionary

def sign_pdf(input_pdf_path, output_pdf_path, signature_text, signature_position, token_library_path, pin):
    try:
        # Initialize the signer
        signer = TokenSigner(token_library_path, pin)
        if signer is None:
           print("Signer initialization failed.")
           return

        private_key = signer.get_private_key()
        if private_key is None:
            print("Private key not found. Exiting.")
            return

        # Read the PDF and data for signing
        with open(input_pdf_path, "rb") as f:
            data_to_sign = f.read()

        # Sign the data
        signed_data = signer.sign(private_key, data_to_sign)
        if signed_data is None:
            print("Error signing data.")
            return

        # Open the input PDF
        with Pdf.open(input_pdf_path) as pdf:
            first_page = pdf.pages[0]

            # Define the signature field dictionary
            signature_dict = Dictionary(
                Type=Name("/Annot"),
                Subtype=Name("/Widget"),
                FT=Name("/Sig"),
                Rect=Array([
                    signature_position[0], signature_position[1],
                    signature_position[0] + signature_position[2], signature_position[1] + signature_position[3]
                ]),
                T=String(signature_text),
                DA=String("/Helv 0 Tf 0 g"),
                V=Dictionary(
                    Contents=String(signed_data.hex())
                )
            )

            print("\n signature data = ", signature_dict)

            # Add the signature field to the annotations
            if "/Annots" not in first_page:
                 first_page.Annots = Array()

            first_page.Annots.append(signature_dict)

            # Save the updated PDF
            pdf.save(output_pdf_path)

        print(f"PDF signed successfully and saved to: {output_pdf_path}")

    except Exception as e:
        print(f"An unexpected error occurred: {e}")

    finally:
        if signer:
            signer.logout()


token_library_path = "HYP2003-Linux-x86_64/redist/libcastle_v2.so.1.0.0"
pin = "12345678"
pdf_path = "sample.pdf"
output_path = "signed_sample_visible.pdf"
signature_text = "JAYESH MAHATO"
signature_position = (100, 50, 200, 50)

sign_pdf(pdf_path, output_path, signature_text, signature_position, token_library_path, pin)

运行代码后,控制台显示所有步骤都执行成功:

PKCS11 library loaded successfully. Logged in to token. Found valid 
        RSA private key. Signing data with private key:   
        CKA_ALWAYS_AUTHENTICATE: False CKA_ALWAYS_SENSITIVE: True CKA_CLASS:
        CKO_PRIVATE_KEY CKA_DECRYPT: True CKA_DERIVE: False CKA_END_DATE: ()
        CKA_EXTRACTABLE: False CKA_ID: (122, 171, 175, 175, 25, 237, 96, 58,
        207, 130, 173, 148, 193, 195, 74, 94, 121, 110, 10, 31)
        CKA_KEY_TYPE:    CKK_RSA CKA_LABEL:
        48055270-5655-4074-92f5-5f043e5b7908 CKA_LOCAL:    True
        CKA_MODIFIABLE: True CKA_MODULUS: (157, 159, 241, 25, 154, 190,   
        52, 253, 254, 145, 218, 252, 211, 2, 17, 85, 223, 229, 71, 192, 8,  
        62, 249, 36, 2, 41, 176, 194, 172, 106, 202, 61, 94, 96, 77, 48, 72,
        93, 167, 128, 128, 175, 108, 104, 225, 68, 26, 216, 171, 110, 230,  
        91, 243, 102, 158, 92, 93, 20, 42, 182, 199, 212, 53, 3, 248, 60,
        42,    242, 185, 150, 13, 97, 238, 226, 93, 109, 215, 61, 228, 56,
        16, 204,    101, 9, 126, 237, 88, 12, 158, 154, 234, 250, 23, 201,
        148, 183, 167,    93, 222, 247, 219, 232, 247, 196, 175, 34, 197,
        136, 181, 50, 235,    106, 139, 92, 88, 0, 176, 30, 137, 113, 174,
        66, 124, 15, 120, 21,    77, 248, 194, 49, 47, 76, 76, 174, 204,
        141, 94, 213, 161, 96, 240,    123, 70, 86, 109, 231, 242, 183, 10,
        108, 10, 148, 40, 125, 197, 187,    130, 57, 82, 65, 16, 100, 158,
        114, 102, 0, 232, 247, 8, 47, 58, 175,    155, 86, 255, 35, 96, 213,
        34, 37, 255, 189, 162, 143, 54, 92, 147,    57, 43, 167, 44, 157,
        128, 231, 196, 72, 13, 218, 71, 18, 153, 122,    39, 226, 86, 173,
        201, 142, 102, 63, 142, 238, 234, 27, 77, 186, 98,    143, 105, 218,
        57, 118, 37, 227, 72, 150, 192, 134, 139, 91, 72, 37,    26, 164,
        205, 78, 182, 130, 142, 177, 49, 189, 213, 130, 9, 219, 0,    87,
        23, 223, 80, 100, 233, 176, 69, 155) CKA_NEVER_EXTRACTABLE: True   
        CKA_PRIVATE: True CKA_PUBLIC_EXPONENT: (1, 0, 1) CKA_SENSITIVE: True
        CKA_SIGN: True CKA_SIGN_RECOVER: True CKA_START_DATE: ()
        CKA_SUBJECT:    () CKA_TOKEN: True CKA_UNWRAP: True
        CKA_WRAP_WITH_TRUSTED: False Hash    data:   
        d5ddf867002b9541fcc6ea8169a85ca2a741b6afa36448d2b389cdfc0e80bd5b
        Data    signed successfully.
           
            signature data =  pikepdf.Dictionary(Type="/Annot")({   "/DA":    "/Helv 0 Tf 0 g",   "/FT": "/Sig",   "/Rect": [ 100, 50, 300, 100 ],
        "/Subtype": "/Widget",   "/T": "JAYESH MAHATO",   "/Type": "/Annot",
        "/V": {
               "/Contents": "7d5653390f44bf06377b2ae5264538a3d0d4e9c524d33374ce8b6ad03033ea0354de6fde1d07fbded86679b4f2ebe29a0ffb139eb47ad5c7cd04b7486a671a783936c77fde54a871a00afb818e7acf7140c1570be07597c906d7d138645d3e011fcb9da1c788ba3cc79b31029fc7de1a53cadd50983ddcbb160c7fd78f5e968a53074217f56b85f2f23292f133a9086346c33fe68f13c8c41ab8c2fbbc45bb51648553ba68e9c76bd11e3e7616a67c5bde5f09f55639c8a5f38dcf3b96e5b02eb53c1af4bd3e4ff88db36d204e00cf78867967a7c577a910576e5bf68aec8966a1f18546016fc09c56c388a50ed9ad954608bf07c0c23ff549e10477138e413e"
        } }) PDF signed successfully and saved to: signed_sample_visible.pdf
        Logged out from token.

但实际生成的PDF存在两个关键问题:

  1. 签名完全不可见:我设置的签名文本JAYESH MAHATO和指定的位置没有在PDF中任何地方显示
  2. 签名无法被验证:使用Adobe Acrobat等标准PDF查看器打开时,系统提示无法验证该数字签名

我已经确认USB令牌能被正常识别,私钥也能正确获取,签名数据也生成了,但看起来签名数据要么不完整,要么嵌入的方式不符合PDF签名规范。

备注:内容来源于stack exchange,提问作者kishan Kumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.15 03:44:32