Computers & IT

qpdf Encrypts With AES-256 by Default. pdftk Can’t Open It.

You encrypt a PDF with qpdf, hand it to pdftk further down the pipeline, and pdftk refuses to open it. Both tools are current. The password is right. Nothing is corrupt.

The two tools simply do not agree on what “encrypted PDF” means, and their defaults land on opposite sides of the disagreement. I measured all three of the common command-line tools against the same document, and the interesting part is not that one of them is wrong — it is that two of the three report success while producing something useless.

The one-line version

qpdf refuses to write RC4 by default. pdftk cannot read AES-256 at all. So qpdf’s default output cannot be piped into pdftk, and the format pdftk does read is the one qpdf will not produce without an override.

First trap: qpdf leaves a zero-byte file behind

Ask qpdf for 40-bit or 128-bit encryption and it declines, because both use RC4:

qpdf --encrypt userpw ownerpw 128 -- src.pdf out.pdf
qpdf: refusing to write a file with RC4, a weak cryptographic algorithm

That is a reasonable default. The problem is what it leaves on disk:

Command Exit code Output file
--encrypt … 40 -- 2 0 bytes, created
--encrypt … 128 -- 2 0 bytes, created
--encrypt … 256 -- 0 5,660 bytes
--allow-weak-crypto --encrypt … 40 -- 0 5,057 bytes
--allow-weak-crypto --encrypt … 128 -- 0 5,058 bytes

The exit code is correct. The file existence check is not. A batch script that verifies its work with [ -f "out.pdf" ] — and plenty do — records a success and moves on, leaving an empty file where a document should be. Test for -s (non-empty) rather than -f, or better, check the exit code.

Second trap: the tools disagree about the format

With the correct password supplied, feeding each encrypted file to each reader:

Encrypted with qpdf --password pdftk input_pw
RC4-128 exit 0 — 6 pages, 9,234 chars exit 0 — 6 pages, 9,234 chars
AES-256 exit 0 — 6 pages, 9,234 chars exit 1 — no output

This is the break. AES-256 is what qpdf gives you when you ask for encryption without arguing about it, and pdftk-java 3.3.3 cannot open it. RC4-128 works everywhere and is exactly what qpdf declines to write.

So a pipeline of qpdf --encrypt → pdftk fails on defaults, and the fix is a choice between two bad options: pass --allow-weak-crypto and ship RC4, or drop pdftk from that stage. If the encryption is protecting anything real, drop pdftk — RC4 has been broken for years, which is why qpdf refuses it.

Worth saying plainly: pdftk is not “wrong” here in a way you can patch around. It is an interoperability gap you have to design around.

Third trap: Ghostscript’s exit code tells you nothing

Run Ghostscript against an AES-256 file without the password:

gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -sOutputFile=out.pdf encrypted.pdf

It prints **** This file requires a password for access. and No pages will be processed, writes an output file, and exits 0.

Ghostscript run Exit Output
with -sPDFPassword=u 0 5,740 b · 6 pages · 9,234 chars
without password 0 2,409 b · 1 page · 0 chars

A one-page empty document, reported as success. In a loop over a folder where some files are protected, every protected file silently becomes a blank page and the script never notices. qpdf and pdftk both fail correctly here — qpdf exits 2, pdftk exits 1, and neither writes a file.

This is the same lesson as PDF repair, where a recovered file can report the right page count and contain nothing: with Ghostscript, the exit status is not a result. Check the content.

Fourth trap: Ghostscript silently removes the protection

Give Ghostscript the correct password and it processes the file perfectly — 6 pages, all 9,234 characters intact. Then check what came out:

File pdfinfo Encrypted
input (AES-256) yes
Ghostscript output no

Because pdfwrite regenerates the document rather than restructuring it, the encryption is not carried over. That is not a bug — it is what re-rendering means — but if you are running a compression or normalisation pass over a folder of protected documents, Ghostscript hands back unprotected copies and says nothing. qpdf preserves the encryption unless you explicitly ask it to --decrypt.

How to check any of this yourself

pdfinfo -upw yourpassword file.pdf | grep Encrypted
pdftotext -upw yourpassword file.pdf - | wc -c
pdfinfo file.pdf | grep Pages

Three things worth asserting after any encryption-related step, because none of them are implied by the exit code: the output is non-empty, the page count matches the input, and the text still extracts. On a scanned document there is no text to extract, so check pdfimages -list for image rows instead.

[ -s out.pdf ] || { echo "FAILED (empty): $f" >&2; continue; }

One line, and it catches both the qpdf zero-byte case and the Ghostscript blank-page case.

What to use, by task

Task Use Note
Encrypt a PDF qpdf --encrypt, 256 AES-256, the only strong option here
Encrypt for a pdftk-based pipeline qpdf --allow-weak-crypto … 128 RC4, weak — only if the encryption is nominal
Read/modify an encrypted PDF qpdf --password=… Keeps encryption; fails loudly when wrong
Remove encryption deliberately qpdf --password=… --decrypt Explicit, unlike Ghostscript’s side effect
Compress a protected PDF qpdf, not Ghostscript Ghostscript drops the protection
Anything in a batch loop — Verify content; exit codes are unreliable

If you are building the batch loop itself, the routing and error-handling patterns are measured in batch PDF compression with a routing script — including the reason set -e is the wrong default when qpdf is involved.


Test setup. Measured 28 July 2026 on Linux with qpdf 12.2.0, Ghostscript 10.05.1 and pdftk-java 3.3.3. Source document: 6 pages, 5,710 bytes, 9,234 extractable characters (whitespace stripped, via pdftotext), generated from PostScript. Encryption applied with qpdf --encrypt userpw ownerpw <bits> --. Character counts after each operation are compared against the source to confirm content survived rather than assuming the exit code means anything. pdftk-java is the actively maintained port; behaviour of the original pdftk may differ, and AES-256 support in particular is a known gap rather than a version quirk.

References: qpdf — PDF encryption (supported key lengths and why RC4 is refused without --allow-weak-crypto) · qpdf manual — exit status (the exit codes the batch guard relies on)