Install & Compatibility
Where this runs
tested against v0.36.2 · pip install
no network on importno background threads
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
muslpy 3.10–3.910 runs
installs and imports cleanly · install 0.0s · import 1.172s · 57.8MB
glibcpy 3.10–3.910 runs
installs and imports cleanly · install 5.6s · import 1.091s · 58MB
57MB installed
● package 57MB
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
IncrementalPdfFileWriter
✓ from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter
SimpleSigner
✓ from pyhanko.sign.signers import SimpleSigner
PdfSignatureMetadata
✓ from pyhanko.sign.signers import PdfSignatureMetadata
PdfSigner
✓ from pyhanko.sign.signers import PdfSigner
sign_pdf
✓ from pyhanko.sign.signers import sign_pdf
HTTPTimeStamper
✓ from pyhanko.sign.timestamps import HTTPTimeStamper
pyhanko_certvalidator
✓ import pyhanko_certvalidator
✗ import certvalidator
The certificate validator package was renamed from 'certvalidator' to 'pyhanko_certvalidator' in v0.6.0 to avoid namespace conflicts.
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.
import io
import os
from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter
from pyhanko.sign import signers
def sign_document_example(input_path, output_path, key_path, cert_path, ca_chain_path=None, key_passphrase=None):
# Create dummy key and cert files for runnable example
with open('dummy_key.pem', 'w') as f: f.write('-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----') # Placeholder
with open('dummy_cert.pem', 'w') as f: f.write('-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----') # Placeholder
if ca_chain_path: # Create dummy CA chain if path provided
with open('dummy_ca_chain.pem', 'w') as f: f.write('-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----') # Placeholder
# In a real scenario, replace 'dummy_key.pem' and 'dummy_cert.pem'
# with paths to your actual signer key and certificate.
# key_passphrase should be bytes, e.g., b'your_password'
# Load the signer key and certificate
cms_signer = signers.SimpleSigner.load(
key_path or 'dummy_key.pem',
cert_path or 'dummy_cert.pem',
ca_chain_files=(ca_chain_path or 'dummy_ca_chain.pem',) if ca_chain_path else None,
key_passphrase=key_passphrase
)
with open(input_path, 'rb') as doc_input:
w = IncrementalPdfFileWriter(doc_input)
out = signers.sign_pdf(
w,
signers.PdfSignatureMetadata(field_name='Signature1'), # Use an existing field or 'Signature1' will be created
signer=cms_signer,
)
with open(output_path, 'wb') as doc_output:
doc_output.write(out.read())
print(f"Document signed: {output_path}")
# Example usage (requires a dummy PDF and actual key/cert files in a real scenario)
# You can create a dummy PDF file like 'input.pdf' for testing.
# Replace 'your_key.pem', 'your_cert.pem', 'your_ca_chain.pem' with actual paths.
# To run this example, ensure you have a 'input.pdf' file.
# And replace the '...'(s) with actual PEM contents from your test certificates if you want to run it end to end.
# try:
# # Create a minimal dummy PDF for testing if it doesn't exist
# if not os.path.exists('input.pdf'):
# from PyPDF2 import PdfWriter
# writer = PdfWriter()
# writer.add_blank_page(width=72, height=72)
# with open('input.pdf', 'wb') as f: writer.write(f)
#
# sign_document_example(
# input_path='input.pdf',
# output_path='signed_output.pdf',
# key_path=os.environ.get('PYHANKO_SIGNER_KEY_PATH', 'dummy_key.pem'),
# cert_path=os.environ.get('PYHANKO_SIGNER_CERT_PATH', 'dummy_cert.pem'),
# ca_chain_path=os.environ.get('PYHANKO_CA_CHAIN_PATH', 'dummy_ca_chain.pem'),
# key_passphrase=os.environ.get('PYHANKO_KEY_PASSPHRASE', '').encode('utf-8')
# )
# finally:
# # Clean up dummy files
# for f in ['dummy_key.pem', 'dummy_cert.pem', 'dummy_ca_chain.pem']:
# if os.path.exists(f): os.remove(f)
pyhanko --version
Upgrade
Version history
0.36.2latest on PyPI · released Jul 26, 2026
Audit
Dependencies
pyhanko-clirequiredProvides command-line interface (CLI) functionality; separated from the main library in v0.28.0.
PythonrequiredRequires Python 3.10 or later for compatibility.
pkcs11optionalOptional dependency for PKCS#11 device support.
image-supportoptionalOptional dependency for image handling in stamps.
opentypeoptionalOptional dependency for OpenType/TrueType font support in stamps.
qroptionalOptional dependency for QR code generation in stamps.
async-httpoptionalOptional dependency for asynchronous HTTP operations.
etsioptionalOptional dependency for ETSI (European Telecommunications Standards Institute) related functionality.