Walk the AcroForm fields, keep the ones whose field type is /Sig, and read the signature dictionary hanging off /V.
from datetime import datetime
from pypdf import PdfReader
def parse_pdf_date(raw):
# PDF dates look like D:20260415133012-07'00'
s = str(raw).removeprefix("D:").replace("'", "")
return datetime.strptime(s, "%Y%m%d%H%M%S%z")
reader = PdfReader("signed.pdf")
acroform = reader.trailer["/Root"].get("/AcroForm")
for field in acroform.get("/Fields", []):
field = field.get_object()
if field.get("/FT") != "/Sig":
continue
sig = field.get("/V")
if sig is None:
print(f"{field.get('/T')}: field present but unsigned")
continue
sig = sig.get_object()
print("field: ", field.get("/T"))
print("signer: ", sig.get("/Name"))
print("signed at:", parse_pdf_date(sig.get("/M")))
print("reason: ", sig.get("/Reason"))
print("location: ", sig.get("/Location"))
print("subfilter:", sig.get("/SubFilter"))
print("covers: ", sig.get("/ByteRange"))What the fields mean
A signature field is an ordinary form field with /FT set to /Sig. If it has never been signed, /V is absent, which is a cheap way to tell a prepared-but-unsigned document from a completed one. When it has been signed, /V points at the signature dictionary: /Name is the signer as recorded by the signing software, /M is the claimed signing time, /Reason and /Location are optional free text, /SubFilter names the signature flavor (for example an ETSI PAdES profile), and /ByteRange lists the byte offsets the signature actually covers.
Dates are the one annoyance. PDF stores them as a string like D:20260415133012-07'00', so strip the prefix and the apostrophes before handing it to strptime, as above.
Two caveats
This reads what the signature says. It does not validate it. Per ISO 32000-2, the byte range digest "should be the entire PDF file, including the signature dictionary but excluding the signature value itself." So print /ByteRange and check it: if the last segment ends well before the end of the file, bytes were appended after signing and the signature does not cover them. Actually verifying the cryptography in /Contents is a separate job and pypdf will not do it for you.
Second, do not treat /M as proof of when signing happened. The byte range digest does cover the signature dictionary, so /M cannot be edited afterward without breaking the signature, but it is still whatever the signing software wrote there at the time. A time you can independently verify comes from a document timestamp signature, which the spec defines as its own signature type with its own dictionary, not from /M. If your audit trail needs a defensible timestamp, look for that instead.
Back to All Questions