Brand logo
HomeCloud ComputingSecuritySoftware DevelopmentAI ApplicationsStartupsGeneral
  1. Home
  2. /
  3. security
  4. /
  5. pdf-aes-256-encryption-rests-on-a-9-bit-key

PDF AES-256 or Just a 9 bit AES key? ItInsightsWorld Security Breakdown and Hardening Checklist

By James Hartley — ItInsightsWorld Security desk

A quotation, an NDA or a board deck exported from a mainstream PDF toolkit can display an AES-256 badge and still be readable by anyone willing to spend a few seconds on a laptop. That is the headline finding of a disclosure write-up reviewed by this desk: multiple PDF tools advertise AES-256, yet the 256-bit file key behind that badge is produced either from a state space small enough to enumerate, or from a randomness source with no cryptographic strength. Nothing in the document properties warns you.

Worth stating plainly for defenders: AES is not the broken part. The failure sits one layer below, in how implementations generate and wrap the key — which is exactly the layer nobody audits, because it is invisible from outside.

The design PDF encryption is supposed to follow

PDF encryption protects strings and datastreams inside the file, including metadata unless that metadata is explicitly left unencrypted. The password mechanism has been revised repeatedly over the years, moving from RC4 to AES and hardening password hashing against brute-force attacks; the current version, revision 6, uses AES-256.

  • The password is not the content key. The implementation generates a random 32-byte file key that encrypts the content.
  • The password only wraps that key. A salt plus the password are hashed into a separate key, and that key encrypts the random 32-byte file key.
  • Only one revision slows the wrap step. Revision 6 makes this hashing deliberately slow; the older AES-256 revision 5 does not, so offline guessing against revision 5 is cheaper.
  • User and owner passwords unlock the same file key. One is for viewing, one is for changing permissions, enforcement is up to the reader, and with the viewing password you can also make an unrestricted copy.

Because that randomness is often generated very insecurely, protected PDFs can be decrypted without a password at all — and no amount of hashing hardening compensates for it.

Case one — MuPDF: a 48-bit generator that behaves like 9 bits

In MuPDF versions before 1.28.5, random numbers come from an implementation of the drand48 family, seeded exactly once using the current epoch in seconds. The internal machinery is a 48-bit linear congruential generator, but the seeding routine writes the state as three 16-bit words: the low word is the constant 0x330e, while the 32-bit timestamp occupies state bits 16 to 47.

When a key is needed, the generator is stepped repeatedly and only the lowest byte of each draw is kept. The step discards state bits 0 to 16, and the cast to an unsigned char discards everything above bit 24. Every output byte therefore comes from bits 17 to 24 of the LCG state.

Multiplication and addition carry bits upward but never downward, so the higher bits of the state can never affect the emitted bytes. That leaves 25 bits that can affect the output, and since the bottom 16 are always initialized to 0x330e, only nine bits from the timestamp matter. Timestamps that differ by 512 seconds produce exactly the same byte stream.

With a keyspace that small there is no attack to design, only enumeration: generate every candidate key and trial-decrypt. A very unoptimized python implementation walking all 512 streams in pure brute force takes about 11 milliseconds. MuPDF uses the same generator for the document /ID, stored in the trailer in plaintext; when writing a new document it first draws 32 bytes and samples the decryption key immediately after, so /ID can be used as an additional check.

time(NULL) mod 512 = 80
/ID[0] = 7628523958da31d6c21c90e39344b28e
/ID[1] = 8be910bddaa1f1a542e5a27ceb115346
key    = c3888ecc8ff554ac93acd689da2ecd6223cb760168622e8a0c44a533a037c212

The test-document result is blunt: that key decrypts the document without either password.

Case two — Foxit PDF Reader: hashing a clock value

Foxit PDF Reader 2026.1.3.36551 derives the file key as a SHA-256 digest of three inputs: the current timestamp as a little-endian 32-bit value, 32 uninitialized bytes, and the constant string there. In practice those uninitialized bytes are zero-filled and add no randomness, so the key is a pure function of one integer.

  • Guessing is cheap. The /Perms permissions block contains known plaintext, so validating one candidate needs a single hash and a single AES block decryption — no password hashing, no document decryption.
  • The check is a handful of byte comparisons. The digest material is a 41-byte buffer: timestamp in the first four bytes, the constant at offset 36, zeros in between. A hit means the permission bytes match, a run of 0xff padding is present, the metadata byte lines up, and the block ends with the adb marker.
  • It parallelises. The Rust implementation searches timestamps downward from the current time using Rayon for multithreading, and on a Ryzen 9 7950X with 32 threads it sustains 370 to 376 million timestamps per second.
Target timestampCandidates testedSearch time
Ten years ago315,573,5710.84 s
Unix epoch1,792,610,1104.85 s

The second row covers every timestamp from 1970 to the present, so a full-history sweep is a five-second job; for a relatively recently generated document, recovery runs in the order of milliseconds. In the published example, timestamp 1787583328 yields the key 41474b490265500f9727f21f1dde34be31bcdfc3830e2dc027daabe5b5984081, and the decrypted /Perms block reads fcfbffffffffffff54616462f67f0000.

Provenance: how that key derivation spread

Because this exact pattern popped up in a couple of different PDF editing suites, the researchers traced its origin. It was already present in PDFium's initial public commit from May 2014, and the file header credits Foxit for the original code, so this derivation appears to have come from Foxit — with other editors picking it up through PDFium.

The historical listing says the same thing: it hashes an int cast of the current time, then a 32-byte member buffer, then the literal string there, using a hashing context declared as uint8_t sha[128]. That middle input is m_EncryptKey — the uninitialized contents of the key buffer being hashed into itself. Assuming those bytes are zero-filled, this is exactly the key derivation Foxit still uses today.

Case three — OpenPDF and iText: ARC4 seeded by the JVM

OpenPDF 3.0.5 and iText 9.7.1 both route cryptographic randomness through an IVGenerator class: an ARC4 stream seeded with a millisecond timestamp and the JVM's free memory, concatenated into a string and used as the key material. The same stream generates the file key and the 32 bytes of salts stored in plaintext in /U and /O, so guesses for the timestamp and free memory can be checked against those salts, then the key recovered from the adjacent bytes.

Although there is significantly more variance here than in the MuPDF and Foxit examples, the outcome belongs to the same class of defect: the promise of 256 bit security is undermined by insecure randomness sources and a lower-than-advertised state space.

Checklist: is your PDF pipeline affected?

  1. Inventory every component that writes encrypted PDFs — embedded SDKs, converters, print drivers, export and e-signature paths — and flag MuPDF, Foxit, OpenPDF, iText and anything descended from PDFium.
  2. Pin and compare versions: MuPDF before 1.28.5 is the exposed range; OpenPDF 3.0.5 and iText 9.7.1 are the builds described above.
  3. Audit the key-generation path for time(NULL), currentTimeMillis and freeMemory used as seeding material instead of a cryptographically secure generator.
  4. Sample plaintext /ID values from files written in the same session and look for repeated byte streams inside a 512 seconds window.
  5. Run a no-password decryption test against a representative set of archived AES-256 PDFs, covering both revision 5 and revision 6 documents.
  6. Confirm what actually falls inside the protected envelope, including metadata that may or may not have been explicitly left unencrypted.
  7. Scope the sweep deliberately: which repositories, mail stores and sharing portals hold exports whose contents you would least want read.
  8. Measure real entropy in code review, not advertised width — a routine built on a 48-bit generator can still hand out only 9 bits of usable state.

Common misconceptions

  • "The properties dialog says AES-256, so this is 256-bit security." The label describes the cipher and the key length, not the entropy that produced the key.
  • "A long, strong password compensates." The password never encrypts the document directly; it only wraps the file key. If that file key is guessable, the hashing work factor is beside the point.
  • "Upgrading to revision 6 solves it." revision 6 makes password hashing deliberately slow where revision 5 does not; neither revision controls how random the file key is.
  • "Owner-password restrictions are access control." Either password unlocks the same file key and the restrictions are up to the reader — permission flags are a convention, not a boundary.
  • "Only the visible page content is exposed." Strings, datastreams and metadata (unless explicitly left unencrypted) all sit inside the same envelope.
  • "A vendor patch removes the exposure." In MuPDF's case the issue was fixed, but previously published copies cannot be retroactively protected.

FAQ

Is AES itself at risk here?

No. Every case above is a key-generation defect: the file key comes from a non-cryptographically secure source of randomness, from a state space that collapses to nine bits, or from a hash of the clock. The cipher is sound; the wrapper is where confidentiality fails.

Why does a 256-bit key end up equivalent to a 9 bit AES key in MuPDF?

Because the state is seeded as three 16-bit words with the low word pinned at 0x330e, and only bits 17 to 24 ever reach the output. Of the 25 bits that can affect the stream, the bottom 16 are constants, leaving nine bits of timestamp — 512 keys, enumerated in 11 milliseconds.

Does the plaintext /ID help attackers only?

Both sides. Since /ID is drawn from the same generator and stored in the trailer in plaintext, defenders can use it to detect predictable randomness, and it doubles as an additional candidate check during enumeration.

Can we re-encrypt old files after upgrading the library?

Treat that as re-issuance, not as patching. Already-distributed copies keep their original key, and published copies cannot be retroactively protected once they are in circulation.

Is viewer-side permission setting enough to stop data leakage?

No. With the viewing password you can produce an unrestricted copy, and a recovered file key bypasses both the user and owner passwords entirely.

Action recommendations

  1. Stop treating PDF encryption as a storage control. Anything whose confidentiality matters in a forensic review needs server-side access control as well.
  2. Upgrade and then verify rather than assume. Replace MuPDF builds before 1.28.5, and confirm which OpenPDF or iText build your toolchain actually ships before trusting the AES-256 label.
  3. Write the randomness requirement into your secure-coding baseline: key material from a cryptographically secure generator only, with time-based or memory-based seeding rejected in review.
  4. Use revision 6 where deliberately slow password hashing is genuinely useful, but never as compensation for weak file keys.
  5. Run the enumeration self-test against your own corpus before an auditor does — 512 streams in 11 milliseconds makes this a very cheap check.
  6. Re-issue high-sensitivity documents created by affected builds, and record which build produced them so the backlog is enumerable.

Disclosure worked as intended here: the vendors were notified, MuPDF shipped a fix, and the researchers published their decryptors for MuPDF and Foxit so security teams can test their own archives instead of speculating. The lasting lesson is procedural. A cipher label is a claim about an algorithm; a control is a claim about how the key was born. What was supposed to be a security upgrade over older file keys derived from the user's password instead led to weaker keys in practice — so until your review process verifies the second claim, the number 256 in a document's properties tells you what the reader was promised, not what an attacker has to break.

Brand logo

IT Insights

Cloud & Infrastructure Practice Media for Enterprise Technology Decision-Makers

Cloud ComputingSecuritySoftware DevelopmentAI ApplicationsStartupsGeneralAI Data News Global

© 2026 IT Insights.

Cookie Notice

We use cookies to improve your experience and analyze website traffic. Read our Privacy Policy to learn more.