CLI tool
MatthiasValvekens/pyHanko avatar
MatthiasValvekens/pyHanko

pyHanko: signing and stamping PDF files from the command line

pyHanko: sign and stamp PDF files

791 stars113 forksPythonMIT

At a glance

What is it?
pyHanko is an MIT licensed Python toolkit for digitally signing and stamping PDF files, with PAdES baseline profiles, RFC 3161 timestamps, PKCS#11 signing through the CLI, validation against EU trusted lists and PDF 2.0 encryption, and it ships as three separate packages on PyPI. The judgement is that the coverage is unusually wide for a Python tool, while the project itself still declares itself beta and not yet production-ready.
Who is it for?
pyHanko fits a Python team that has to produce signed documents in a regulated European workflow, because it covers the parts that are tedious to assemble, PAdES profiles, timestamp servers, EU trusted lists, PKCS#11 tokens and encrypted output, and it exposes all of it from a click based command line tool as well as from the library.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A signing toolkit born out of a gap in open source tooling

The origin story in the README is short and sets the tone. The lack of open source command line tooling to handle digitally signing and stamping PDF files was bothering the author, so he rolled his own. That origin explains the shape of the project, which is a library with a command line tool on top rather than a graphical application.

It is also a beta. The README states that the project is currently in beta and not yet production-ready, which deserves weighing before it goes anywhere near a production document pipeline.

What is distributed is three packages rather than one. The badges show pyHanko on PyPI, pyhanko-certvalidator and pyhanko-cli as separate distributions, and the release history shows all three moving together: pyhanko 0.38.0, pyhanko-cli 0.6.0 and pyhanko-certvalidator 0.33.0, all published on 2026-10-17 as beta releases, with the last push to the repository on 2026-10-19. The split lets a deployment install the validator without the signing machinery, which is a sensible boundary for a tool that handles private keys.

The documentation is hosted on ReadTheDocs at docs.pyhanko.eu and covers CLI usage, library usage and an API reference generated from inline docstrings.

The pip install names the optional stacks you need

Installation is a single pip invocation, and the extras in it are the interesting part:

bash
pip install 'pyHanko[pkcs11,image-support,opentype,qr]' pyhanko-cli

The README notes that depending on the shell you might have to leave off the quotes, and gives the unquoted form as the alternative. The extras correspond to the four optional dependency groups the project documents: PKCS#11 for hardware tokens, image handling, OpenType and TrueType support, and QR code generation. The pyhanko-cli package is installed alongside because the command line tool is a separate distribution.

The Python requirement is 3.10 or later, and the manifest sets the same floor with requires-python.

Two details from the development configuration matter if you plan to work on the code rather than just use it. The project is set up as a uv workspace with package = false at the root, so the root project is a development shell rather than an installable distribution, and the real packages live in the pkgs directory. The mypy configuration lists the modules that are type checked, which are the cli, config, keys, pdf_utils, sign and stamp modules of pyhanko plus pyhanko_certvalidator, and it sets ignore_missing_imports for the third party libraries whose type stubs are missing, among them asn1crypto, pkcs11, fontTools, qrcode and oscrypto.

PAdES profiles, RFC 3161 timestamps and incremental updates

The signing features are the core of the library, and they are specified in the terms the PDF standards use.

Long term validation is supported, which means a signature can carry the revocation information needed to be checked years later. The PAdES baseline profiles B-B, B-T, B-LT and B-LTA are all supported, and Adobe style revocation information embedding is supported as well, so the output can satisfy both a European eIDAS workflow and a reader that expects Adobe's own conventions.

Timestamps come from an RFC 3161 timestamp server, which is the mechanism that proves a signature existed at a given moment rather than relying on the certificate's own validity dates.

Multiple signatures on one document are handled by using incremental updates, and the README explains why in one line: all modifications are executed using incremental updates to preserve cryptographic integrity. That is the mechanism PDF has for changing a signed file without breaking the signature, and using it is what makes a second signature possible at all.

Two more things round out the signing story. An async signing API is available, which matters for a service rather than a script. And there is an interrupted signing mode for ease of integration with remote and/or interactive signing processes, which is how you use a key that never leaves a smart card or a remote signing service while still letting pyHanko prepare the document.

Algorithm coverage reaches ML-DSA, with EdDSA limited to pure mode

The signature algorithms are listed with their modes, which is where the detail is.

RSA is supported with both PKCS#1 v1.5 and RSASSA-PSS padding. DSA is supported. ECDSA works with any curve the cryptography library supports, so the curve list is inherited from that dependency rather than frozen in pyHanko itself. ML-DSA, the post-quantum algorithm, is supported alongside the classical ones.

EdDSA is the entry with a condition attached. Both Ed25519 and Ed448 are supported, but in pure mode only, as the README says, with a reference to RFC 8419. The distinction is that the pure form is EdDSA without the extra pre-hashing variants, and a caller who needs the pre-hashed form has to look elsewhere.

Two PDF extension standards are built in as well, the ones defined in ISO/TS 32001 and ISO/TS 32002, which cover extensions to the PDF standard that the specification treats as separate documents.

PKCS#11 support is available both from the library and through the CLI, and there is an extra convenience wrapper for Belgian eID cards, which is a country specific convenience that shows where the project is used most.

Validation covers seed values, AdES and EU trusted lists

The validation side is the part that decides whether a signed document is accepted downstream, and pyHanko treats it as a first class feature rather than a helper function.

There is a cryptographic integrity check, which answers whether the bytes still hash to what was signed. There is authentication through X.509 chain of trust validation, which answers whether the signer's certificate leads somewhere trusted.

The less common checks are the interesting ones. Difference analysis on files with multiple signatures and/or incremental updates made after signing is supported, which means you can work out what changed in a document after it was signed rather than only whether the current bytes are intact. Signature seed value constraint validation checks the constraints attached to a signature field, so a field that was created to require a particular signer, a particular timestamp or a particular reason can be enforced rather than assumed.

AdES validation is supported, and so is validating against EU trusted lists, which is what a Belgian or Dutch public sector workflow usually requires. That last one is the feature with the sharpest edge, because a document can be cryptographically sound and still be rejected if its trust anchor is not on the list the other side uses.

For the encryption side, all encryption methods in PDF 2.0 are supported apart from some very outdated message encryption methods in the public-key security handler, and authenticated encryption is implemented as described in ISO/TS 32003 and 32004.

Stamping, document preparation and the YAML configuration

Not everything pyHanko does is cryptographic. The stamping tools decide what a signature looks like on the page: simple text based stamps, QR stamps, and a font that can be monospaced or embedded from a TTF or OTF file, the last of which needs the opentype extras.

Document preparation is the other half of the toolset, and it is the part that shows how a signature workflow is meant to be used in practice. You can add empty signature fields to existing PDFs, add seed values to those fields with or without constraints, and manage document metadata. Adding the fields in advance, and attaching the constraints to them, is what lets a later signing step be unattended and still rule out an unauthorised signature.

The command line tool is built on click, is available as pyhanko when installed and as python -m pyhanko when running from the source directory, and has built-in help through pyhanko --help. Configuration is YAML based and described as optional for most features, which is a sensible default given that most signing runs are a single command.

Encryption gets a few extra modes beyond the standard, and the README is candid that one of them is not widely supported: RSAES-OAEP, which does not appear to be widely supported in PDF tooling, and ephemeral-static ECDH with X9.63 key derivation, which is supported by Acrobat.

A monorepo with three packages, a PyPDF2 legacy and a known issues list

The repository is organised as a workspace rather than a single package. The root holds pyproject.toml with package = false, a uv.lock, and directories for dev, docs, external-schemata, internal and pkgs, which is where the three published distributions live. There is also a ReadTheDocs configuration, a codecov configuration, a security policy, a code of conduct, a contributing guide and a pinact configuration for pinning GitHub actions.

The documentation dependency groups show how the project is developed as well as used. The docs build pulls pyHanko with the opentype, qr, image-support, pkcs11, async-http and etsi extras, together with pyhanko-certvalidator and pyhanko-cli, so the published documentation exercises the optional stacks. The doctest group adds certomancer and freezegun, which is how the examples in the documentation are kept honest.

One historical dependency deserves flagging. The README acknowledges that the repository includes code from PyPDF2, with both minor and major modifications, and the original licence has been included alongside it in the pyhanko/pdf_utils directory.

Finally, the known limitations are not hidden. The README points at a known issues page in the documentation for the TODOs, which is the page to read before promising anyone a signed document in production.

Editorial conclusion

pyHanko fits a Python team that has to produce signed documents in a regulated European workflow, because it covers the parts that are tedious to assemble, PAdES profiles, timestamp servers, EU trusted lists, PKCS#11 tokens and encrypted output, and it exposes all of it from a click based command line tool as well as from the library. It is the wrong choice if you need a supported release, since the project states plainly that it is in beta and not yet production-ready, and it is the wrong choice if your PDFs have to be validated by a strict corporate trust list that the project does not cover. Before you deploy it, read the known issues page in the documentation, decide whether the [pkcs11], [opentype] and qr extras are needed, and test the validation path against the certificates your relying party actually trusts, because a signature that validates locally can still fail on a trust list.

Frequently asked questions

How do I install pyHanko?

Install it from PyPI with pip install 'pyHanko[pkcs11,image-support,opentype,qr]' pyhanko-cli, dropping the quotes if your shell needs it. The extras cover PKCS#11, image handling, OpenType support and QR code generation, and Python 3.10 or later is required.

Which signature standards does pyHanko support?

The PAdES baseline profiles B-B, B-T, B-LT and B-LTA are supported, together with long term validation, Adobe style revocation information embedding and RFC 3161 timestamps. Signatures can be made with RSA, DSA, ECDSA, EdDSA and ML-DSA.

Can pyHanko use a smart card or hardware token to sign?

Yes. PKCS#11 support is available both from the library and through the CLI, there is a convenience wrapper for Belgian eID cards, and an interrupted signing mode exists for remote and interactive signing setups.

Is pyHanko ready for production use?

The README states that the project is currently in beta and not yet production-ready, and it points at a known issues page in the documentation for the outstanding problems.

Official sources

  1. Issues
  2. License: MIT
  3. MatthiasValvekens/pyHanko on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/matthiasvalvekens-pyhanko.svg)](https://hysenlabs.com/projects/matthiasvalvekens-pyhanko)

Was this useful?