Digital signatures · Python

Digitally sign a PDF in Python

pip install rustpdf and add a PKCS#7 or PAdES signature to a PDF from a Django view or Celery task. An incremental update preserves the original bytes; B-LT and B-LTA add LTV. rust-pdf signs through a non-destructive incremental update, so the original bytes are preserved and the signature stays verifiable in Adobe Reader, pdfsig and any PAdES validator.

Last updated: 2026-06-29

Why Python needs this

Signing needs a private key and an X.509 certificate, and rust-pdf takes them as raw DER, a PKCS#8 key plus the cert. If your key lives in a .pfx/PKCS#12 container or a PEM bundle (the usual output of a CA or openssl), convert it once with the cryptography package and hand the DER bytes straight to rustpdf.sign; no key material ever leaves the Python process or touches disk.

Each call appends a non-destructive incremental update: the original PDF stays byte-for-byte intact and only the signature dictionary, its /ByteRange and the detached CMS are added at the end. So you can sign a freshly rendered invoice inside a Django view, stream the bytes back in the HttpResponse, and re-sign the same file later without invalidating the first signature.

For long-term validation, follow the signature with add_dss (certificates and CRLs, PAdES B-LT) and timestamp (an RFC 3161 token, PAdES B-LTA), both offline and incremental. pdfsig and OpenSSL confirm the ByteRange digest and that /Contents decodes as CMS SignedData.

  • PKCS#7 detached and PAdES B-B, with B-LT and B-LTA for long-term validation.
  • Incremental update: the original file is preserved byte for byte, so earlier signatures stay valid.
  • Bring your own key and X.509 certificate (PKCS#8 DER), or chain to a TSA for timestamps.

Sign a PDF in Python with rust-pdf

Install with pip, then call the same idiomatic API every rust-pdf binding shares. The snippet below is real Python code from the reference docs.

pip install rustpdf

Python
import rustpdf

pdf_bytes = open("contract.pdf", "rb").read()
key_der   = open("signing-key.pkcs8.der", "rb").read()   # PKCS#8 private key (DER)
cert_der  = open("signing-cert.der", "rb").read()        # X.509 certificate (DER)

signed = rustpdf.sign(pdf_bytes, key_der, cert_der,
                      reason="Approved", location="New York",
                      name="Jane Doe", pades=True)
open("contract.signed.pdf", "wb").write(signed)
# Verify in a shell: pdfsig contract.signed.pdf  →  "Signature is Valid."
Validated by: pdfsigopensslqpdf

Everything is free.

Full Python reference in the documentation.

Signing in Python: FAQ

Is the signature legally valid?

rust-pdf produces standards-compliant PKCS#7 and PAdES signatures. Legal validity depends on the certificate you sign with (for example an eIDAS qualified certificate or an ICP-Brasil certificate). The library handles the cryptography and the PDF structure correctly, which is what validators such as pdfsig and Adobe Reader check.

Does it support long-term validation (LTV)?

Yes. After signing you can append a Document Security Store with certificates and CRLs (PAdES B-LT) and an RFC 3161 document timestamp (PAdES B-LTA), all offline. A trusted external TSA and live OCSP fetching are the only parts that need network infrastructure.

Is signing free in Python?

Signing is free, like every feature in rust-pdf.

Can I sign with a PKCS#12/.pfx key in Python?

rustpdf.sign expects a PKCS#8 private key and an X.509 certificate as DER bytes. A .pfx/.p12 file is a container, so load it once with cryptography.hazmat (pkcs12.load_key_and_certificates), then re-encode the key as DER (PKCS#8) and the certificate as DER and pass both to sign. The private key stays in memory and is never written to disk.