MuPDF and Foxit generate weak AES-256 PDF keys, open without a password
A blog post on v12.sh argues that several PDF implementations advertise AES-256 but generate the 256-bit key from a very small state space or from insecure random sources. The authors say the resulting keys are small enough to enumerate, which allows passwordless decryption of protected PDFs.
Background. PDF encryption protects the strings and data streams in a document. With AES-256 the password does not encrypt the document directly. The implementation generates a random 32-byte file key that encrypts the content; the password and a salt are hashed to derive a separate key, and that key encrypts the file key. Revision 6, the current one, makes this hashing deliberately slow; the older AES-256 revision 5 does not. A PDF can have a user password for viewing and an owner password for changing permissions, and either unlocks the same file key, so the restrictions are up to the reader: with the viewing password you can also make an unrestricted copy. The whole scheme rests on the file key being truly random.
MuPDF. Versions before 1.28.5 use an implementation of the drand48 family for random numbers, a 48-bit linear congruential generator seeded once with the current epoch in seconds. The 32-bit timestamp occupies state bits 16 to 47, and the low word is the constant 0x330e. To make a key, MuPDF's fz_memrnd() repeatedly steps the generator and takes one byte from each output. Because of the shift and the cast to unsigned char, every output byte comes from bits 17 to 24 of the state. Higher bits never affect lower ones (carries only travel upward), so everything above bit 24 can be ignored. That leaves 25 bits that can affect the output; the bottom 16 are always 0x330e, so only nine bits of the timestamp matter. Timestamps that differ by 512 seconds produce exactly the same byte stream, so there are only 512 candidate keys. The authors brute-force them by generating all possible 32-byte keys and trial-decrypting. MuPDF also draws the document /ID, stored in plaintext in the trailer, from the same generator: it takes 32 bytes for the /ID first and then samples the key, so /ID serves as an extra check. Searching all 512 streams in a very unoptimized pure brute-force Python implementation takes about 11 milliseconds, and the recovered key opens a test document without either password.
Foxit. Foxit PDF Reader 2026.1.3.36551 derives the file key as a hash of the current timestamp, 32 uninitialized bytes and a constant. In practice those bytes are zero filled and add no randomness. The /Perms permissions block contains known plaintext, so checking a candidate takes one hash and one AES block decryption, with no password hashing and no document decryption. The authors' Rust implementation searches timestamps downward from the current time using Rayon for multithreading. On a Ryzen 9 7950X with 32 threads it sustains 370 to 376 million timestamps per second, enough to cover every timestamp from 1970 to the present; for a recently generated document, they say, the search takes on the order of milliseconds.
Where the pattern came from. The same derivation turned up in several PDF editing suites. It was already present in PDFium's initial public commit from May 2014, and the file header credits Foxit for the original code, so it appears to have come from Foxit. Other editors may have picked it up through PDFium. The 32-byte middle value is m_EncryptKey, the uninitialized contents of the key buffer being hashed into itself; assuming zero fill, this is exactly the derivation Foxit still uses.
OpenPDF and iText. 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. The same stream produces 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 and the key recovered from the adjacent bytes. The authors say there is significantly more variance here than in the MuPDF and Foxit cases, but the promise of 256-bit security is still undermined.
Conclusion. In every case the file key comes from a non-cryptographically secure source of randomness. What was meant as a security upgrade over older password-derived file keys led to weaker keys in practice. The issues were disclosed to the vendors and, in MuPDF's case, fixed. Previously published copies cannot be retroactively protected, so the bugs may still have an impact. The authors published their decryptors for MuPDF and Foxit.
Key facts
- In MuPDF versions before 1.28.5, only 9 bits of random state affect the actual AES-256 key: timestamps 512 seconds apart give the same byte stream, so a Python brute force over all 512 candidates takes about 11 milliseconds.
- Foxit PDF Reader 2026.1.3.36551 derives the file key from a hash of the current timestamp, 32 bytes that are in practice zero, and a constant; the authors' Rust decryptor tests 370 to 376 million timestamps per second on a Ryzen 9 7950X with 32 threads.
- OpenPDF 3.0.5 and iText 9.7.1 seed an ARC4 IVGenerator with a millisecond timestamp and the JVM's free memory; the authors say variance is higher than for MuPDF and Foxit, but the 256-bit promise is still undermined.
- The Foxit-credited key derivation was already in PDFium's initial public commit from May 2014, and other editors may have picked it up through PDFium.
- The issues were disclosed to the vendors and fixed in MuPDF's case; the authors published decryptors for MuPDF and Foxit.
Why it matters
AES-256 is sold as strong, but its strength depends on the 32-byte file key being random. Here the key effectively has 9 bits of entropy in MuPDF, and in Foxit it is a hash of a timestamp, so the password's strength becomes irrelevant: the key can be found without ever touching the password. The authors note that revision 6's deliberately slow password hashing is bypassed, because the attack goes straight at the file key. They also trace the Foxit pattern back to PDFium's May 2014 initial public commit, which suggests the weakness is old and may have spread to other editors.
Who it affects
Anyone who protected a PDF with a password using MuPDF before version 1.28.5, Foxit PDF Reader (the version analysed was 2026.1.3.36551), or software built on OpenPDF 3.0.5 or iText 9.7.1. The source says other editors may have picked up the Foxit code through PDFium. It does not say how many users or documents are affected.
How to use it
The authors published decryptors for MuPDF and Foxit, which show how the keys are recovered; they are research tools and also illustrate what an attacker could do. MuPDF's fix is in version 1.28.5 and later. For Foxit, OpenPDF and iText the text does not say whether fixes exist. The authors stress that previously published copies cannot be retroactively protected, so a PDF already circulating with a weak key stays weak.
How solid is it
The post gives the full reasoning for MuPDF: the generator's structure, the bit arithmetic that leaves 25 relevant bits and 9 timestamp bits, and a working brute force with a test document. Foxit is backed by a described derivation and a measured search rate. The OpenPDF and iText section is less quantified: no concrete search time or state-space size is given. The findings are the authors' own, and the post gives no author names or institutions, CVE identifiers or disclosure dates. The vendors were reportedly notified, and MuPDF's fix is stated.
Risks and caveats
The text does not say whether Foxit, OpenPDF or iText have fixed their issues; only MuPDF is stated as fixed. It does not say Foxit or PDFium deliberately weakened the keys, and the idea that other editors got the code via PDFium is hedged as a possibility. The Foxit analysis assumes the uninitialized bytes are zero filled, which the authors observed in practice. The Foxit search is fast mainly for recently generated documents; the figures given are for the authors' own hardware and implementation. Old protected files remain exposed even after a vendor fix.
“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.”
— v12.sh blog post, "The case of the 9 bit AES key"