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.

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.
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.
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.
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.
| Target timestamp | Candidates tested | Search time |
|---|---|---|
| Ten years ago | 315,573,571 | 0.84 s |
| Unix epoch | 1,792,610,110 | 4.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.
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.
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.
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.
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.
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.
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.
No. With the viewing password you can produce an unrestricted copy, and a recovered file key bypasses both the user and owner passwords entirely.
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.