pdf2john.pl parses classic cross-reference entries with a fixed-width regex:
$buff =~ /^\s*(\d{10}) (\d{5}) (f|n)/s or return -4;
Each entry is 20 bytes, but some encrypted PDFs space-pad the offset/generation instead of zero-padding them:
' 15 00000 n \n' instead of '0000000015 00000 n \n'
These files are malformed per spec, but pdf readers (like pikepdf) accept them, and they appear in the wild as an anti-analysis trick: the PDF still opens in real readers, but pdf2john rejects the table (Warning: Invalid xref table), never reads the /Encrypt dict, and outputs an empty hash (filename: with nothing after it). John then has nothing to crack.
Suggested fix: relax the fixed widths to \d+ - entries are still read in 20-byte units so alignment is unchanged, and output is byte-identical for well-formed PDFs:
$buff =~ /^\s*(\d+)\s+(\d+)\s+(f|n)/s or return -4;
Attaching a minimal reproducer PDF (password: test123) - opens fine in any reader, but yields an empty hash without the fix.
malformed_xref_test.pdf
Malformed encrypted PDF test:
perl run/pdf2john.pl /tmp/malformed_xref_test.pdf
/tmp/malformed_xref_test.pdf:
Intact encrypted PDF test:
perl run/pdf2john.pl /tmp/sample_encrypted_ok.pdf
/tmp/sample_encrypted_ok.pdf:$pdf$5*6*256*-4*1*16*392430db72b0e546a8f6e9c5a5be43ad*48*1326610df39c173efff346e7e38c6e1aff85904a3d603ca332ce83e97bacfc16f8995cc636b178c90455ffd08cc9ca99*48*df6da506e83b1ab003495cfceb3852b725e4d98fd7e8dae3ff350727acac54e720cc502886e2c0ec080b7049e502cb0c*32*c3773142e97f901119f3a7be35d200fb948ee29696ae06529522d85be4fb3116*32*a9908afcfb15c3bb060041bf4b0b4262726526594800d330dc3b817c7368d996
Intact not encrypted PDF test :
perl run/pdf2john.pl /tmp/sample.pdf
/tmp/sample.pdf: not encrypted!
Note: pdf2john.py also fails on this file (raises ValueError: too many values to unpack in xref.py), so it's not a workaround.
uv run --with pyhanko run/pdf2john.py /tmp/malformed_xref_test.pdf
Traceback (most recent call last):
File "/opt/john/run/pdf2john.py", line 137, in <module>
extractor = PdfHashExtractor(filename)
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/john/run/pdf2john.py", line 67, in __init__
self.pdf = PdfFileReader(doc, strict=strict)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.cache/uv/archive-v0/465ORgOMVx58ZP64IsgZL/lib/python3.12/site-packages/pyhanko/pdf_utils/reader.py", line 201, in __init__
self.xrefs, xref_builder = _read_xrefs_and_trailer(stream, self, strict)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.cache/uv/archive-v0/465ORgOMVx58ZP64IsgZL/lib/python3.12/site-packages/pyhanko/pdf_utils/reader.py", line 167, in _read_xrefs_and_trailer
xref_sections = xref_builder.read_xrefs()
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.cache/uv/archive-v0/465ORgOMVx58ZP64IsgZL/lib/python3.12/site-packages/pyhanko/pdf_utils/xref.py", line 778, in read_xrefs
startxref = self._read_xref_table(declared_startxref)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.cache/uv/archive-v0/465ORgOMVx58ZP64IsgZL/lib/python3.12/site-packages/pyhanko/pdf_utils/xref.py", line 686, in _read_xref_table
highest = xref_section_data.process_entries(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/root/.cache/uv/archive-v0/465ORgOMVx58ZP64IsgZL/lib/python3.12/site-packages/pyhanko/pdf_utils/xref.py", line 384, in process_entries
for xref_entry in entries:
File "/root/.cache/uv/archive-v0/465ORgOMVx58ZP64IsgZL/lib/python3.12/site-packages/pyhanko/pdf_utils/xref.py", line 156, in parse_xref_table
offset, generation, marker = line[:18].split(b" ")
^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: too many values to unpack (expected 3)
pdf2john.plparses classic cross-reference entries with a fixed-width regex:$buff =~ /^\s*(\d{10}) (\d{5}) (f|n)/s or return -4;Each entry is 20 bytes, but some encrypted PDFs space-pad the offset/generation instead of zero-padding them:
' 15 00000 n \n'instead of'0000000015 00000 n \n'These files are malformed per spec, but pdf readers (like pikepdf) accept them, and they appear in the wild as an anti-analysis trick: the PDF still opens in real readers, but
pdf2johnrejects the table (Warning: Invalid xref table), never reads the /Encrypt dict, and outputs an empty hash (filename: with nothing after it). John then has nothing to crack.Suggested fix: relax the fixed widths to \d+ - entries are still read in 20-byte units so alignment is unchanged, and output is byte-identical for well-formed PDFs:
$buff =~ /^\s*(\d+)\s+(\d+)\s+(f|n)/s or return -4;Attaching a minimal reproducer PDF (password: test123) - opens fine in any reader, but yields an empty hash without the fix.
malformed_xref_test.pdf
Malformed encrypted PDF test:
Intact encrypted PDF test:
Intact not encrypted PDF test :