{"id":1865,"library":"pyhanko","title":"PyHanko","description":"PyHanko is a Python library designed for stamping and digitally signing PDF files, offering extensive functionality for handling digital signatures, including support for various PAdES profiles and cryptographic operations. It is actively maintained with frequent minor releases, currently at version 0.34.1, and aims to cover the digital signing features of the PDF standard comprehensively.","status":"active","version":"0.34.1","language":"en","source_language":"en","source_url":"https://github.com/MatthiasValvekens/pyHanko","tags":["pdf","signing","digital signature","cryptography","PKI","stamping"],"install":[{"cmd":"pip install 'pyHanko[pkcs11,image-support,opentype,qr]' pyhanko-cli","lang":"bash","label":"Full installation with common optional dependencies and CLI"},{"cmd":"pip install pyhanko","lang":"bash","label":"Minimal library installation"}],"dependencies":[{"reason":"Provides command-line interface (CLI) functionality; separated from the main library in v0.28.0.","package":"pyhanko-cli","optional":false},{"reason":"Requires Python 3.10 or later for compatibility.","package":"Python","optional":false},{"reason":"Optional dependency for PKCS#11 device support.","package":"pkcs11","optional":true},{"reason":"Optional dependency for image handling in stamps.","package":"image-support","optional":true},{"reason":"Optional dependency for OpenType/TrueType font support in stamps.","package":"opentype","optional":true},{"reason":"Optional dependency for QR code generation in stamps.","package":"qr","optional":true},{"reason":"Optional dependency for asynchronous HTTP operations.","package":"async-http","optional":true},{"reason":"Optional dependency for ETSI (European Telecommunications Standards Institute) related functionality.","package":"etsi","optional":true}],"imports":[{"symbol":"IncrementalPdfFileWriter","correct":"from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter"},{"symbol":"SimpleSigner","correct":"from pyhanko.sign.signers import SimpleSigner"},{"symbol":"PdfSignatureMetadata","correct":"from pyhanko.sign.signers import PdfSignatureMetadata"},{"symbol":"PdfSigner","correct":"from pyhanko.sign.signers import PdfSigner"},{"symbol":"sign_pdf","correct":"from pyhanko.sign.signers import sign_pdf"},{"symbol":"HTTPTimeStamper","correct":"from pyhanko.sign.timestamps import HTTPTimeStamper"},{"note":"The certificate validator package was renamed from 'certvalidator' to 'pyhanko_certvalidator' in v0.6.0 to avoid namespace conflicts.","wrong":"import certvalidator","symbol":"pyhanko_certvalidator","correct":"import pyhanko_certvalidator"}],"quickstart":{"code":"import io\nimport os\n\nfrom pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter\nfrom pyhanko.sign import signers\n\ndef sign_document_example(input_path, output_path, key_path, cert_path, ca_chain_path=None, key_passphrase=None):\n    # Create dummy key and cert files for runnable example\n    with open('dummy_key.pem', 'w') as f: f.write('-----BEGIN PRIVATE KEY-----\\n...\\n-----END PRIVATE KEY-----') # Placeholder\n    with open('dummy_cert.pem', 'w') as f: f.write('-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----') # Placeholder\n    if ca_chain_path: # Create dummy CA chain if path provided\n        with open('dummy_ca_chain.pem', 'w') as f: f.write('-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----') # Placeholder\n\n    # In a real scenario, replace 'dummy_key.pem' and 'dummy_cert.pem'\n    # with paths to your actual signer key and certificate.\n    # key_passphrase should be bytes, e.g., b'your_password'\n\n    # Load the signer key and certificate\n    cms_signer = signers.SimpleSigner.load(\n        key_path or 'dummy_key.pem',\n        cert_path or 'dummy_cert.pem',\n        ca_chain_files=(ca_chain_path or 'dummy_ca_chain.pem',) if ca_chain_path else None,\n        key_passphrase=key_passphrase\n    )\n\n    with open(input_path, 'rb') as doc_input:\n        w = IncrementalPdfFileWriter(doc_input)\n        out = signers.sign_pdf(\n            w,\n            signers.PdfSignatureMetadata(field_name='Signature1'), # Use an existing field or 'Signature1' will be created\n            signer=cms_signer,\n        )\n\n        with open(output_path, 'wb') as doc_output:\n            doc_output.write(out.read())\n\n    print(f\"Document signed: {output_path}\")\n\n# Example usage (requires a dummy PDF and actual key/cert files in a real scenario)\n# You can create a dummy PDF file like 'input.pdf' for testing.\n# Replace 'your_key.pem', 'your_cert.pem', 'your_ca_chain.pem' with actual paths.\n# To run this example, ensure you have a 'input.pdf' file.\n# And replace the '...'(s) with actual PEM contents from your test certificates if you want to run it end to end.\n# try:\n#     # Create a minimal dummy PDF for testing if it doesn't exist\n#     if not os.path.exists('input.pdf'):\n#         from PyPDF2 import PdfWriter\n#         writer = PdfWriter()\n#         writer.add_blank_page(width=72, height=72)\n#         with open('input.pdf', 'wb') as f: writer.write(f)\n#\n#     sign_document_example(\n#         input_path='input.pdf',\n#         output_path='signed_output.pdf',\n#         key_path=os.environ.get('PYHANKO_SIGNER_KEY_PATH', 'dummy_key.pem'),\n#         cert_path=os.environ.get('PYHANKO_SIGNER_CERT_PATH', 'dummy_cert.pem'),\n#         ca_chain_path=os.environ.get('PYHANKO_CA_CHAIN_PATH', 'dummy_ca_chain.pem'),\n#         key_passphrase=os.environ.get('PYHANKO_KEY_PASSPHRASE', '').encode('utf-8')\n#     )\n# finally:\n#     # Clean up dummy files\n#     for f in ['dummy_key.pem', 'dummy_cert.pem', 'dummy_ca_chain.pem']:\n#         if os.path.exists(f): os.remove(f)\n","lang":"python","description":"This quickstart demonstrates how to digitally sign a PDF document using `pyhanko` with a simple signer. It covers loading the signing key and certificate, applying a signature to a PDF, and saving the output. For a real application, you would replace the dummy key/cert paths with your actual cryptographic materials."},"warnings":[{"fix":"Install `pyhanko-cli` explicitly: `pip install pyhanko-cli`. If using optional dependencies, include it in the same command: `pip install 'pyHanko[pkcs11]' pyhanko-cli`.","message":"The `pyhanko-cli` package was separated from the main `pyhanko` library. Direct imports or reliance on the CLI being bundled with `pyhanko` will break.","severity":"breaking","affected_versions":">=0.28.0"},{"fix":"Update import statements from `import certvalidator` to `import pyhanko_certvalidator`.","message":"The `certvalidator` dependency, originally an internal fork, was renamed to `pyhanko_certvalidator` to prevent namespace conflicts.","severity":"breaking","affected_versions":">=0.6.0"},{"fix":"For API users, pass `strict=False` to `IncrementalPdfFileWriter` or `PdfFileReader` objects when handling hybrid files. For CLI users, use the `--no-strict-syntax` switch.","message":"PyHanko considers 'hybrid reference files' less secure and disables strict parsing for them by default to avoid accidental corruption. Attempting to process them in strict mode will result in errors.","severity":"gotcha","affected_versions":">=0.12.0"},{"fix":"Consult the latest documentation for updated LTV validation APIs. The documentation notes that LTV validation is still ad-hoc and may not fully adhere to PAdES specifications.","message":"The old LTV (Long-Term Validation) functionality provided by `async_validate_pdf_ltv_signature()` has been deprecated.","severity":"deprecated","affected_versions":">=0.31.0"},{"fix":"While `pyhanko` attempts to not unnecessarily break conformance, users should be aware that it does not enforce additional restrictions for PDF/A or PDF/UA. Manual validation or external tooling may be required to ensure compliance.","message":"PyHanko does not provide explicit support for signing or stamping PDF/A and PDF/UA files, meaning the output may not comply with these standards.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Avoid adding comments or annotations to PDFs that have already been signed if strict document integrity is paramount, as `pyhanko`'s validation will flag these as modifications.","message":"Comments and annotations added to a signed PDF are considered 'unsafe' changes by `pyhanko`, regardless of the signer's policy.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-04-09T00:00:00.000Z","next_check":"2026-07-08T00:00:00.000Z"}