Mécanisme de génération de clé dans MuPDF
MuPDF avant la version 1.28.5 utilise la famille drand48 comme source d’aléa. Le générateur est un LCG de 48 bits initialisé une fois avec le temps d’epoch en secondes. Le code d’initialisation se résume ainsi :
static void fz_srand48(uint64_t seed) {
state[0] = 0x330e;
state[1] = (seed >> 16) & 0xffff;
state[2] = seed & 0xffff;
}
Lors de la création du file key, MuPDF appelle fz_memrnd() qui extrait le byte le plus bas du nombre généré. Le LCG avance, puis le byte retourné provient uniquement des bits 17 à 24 de l’état interne. Les bits supérieurs (25‑47) n’influencent jamais les bits inférieurs, ce qui réduit l’espace exploitable à 25 bits. Les 16 bits de poids faible sont fixes à 0x330e, de sorte que seuls 9 bits du timestamp (bits 16‑24) participent réellement à la génération du file key. Deux timestamps séparés de moins de 512 secondes produisent exactement le même flux de bytes.
Faiblesse de la source d’aléa et exploitation
Un espace de 9 bits correspond à 512 combinaisons possibles. En parcourant ces 512 valeurs et en testant chaque clé de 32 bytes contre le document, les chercheurs ont pu décrypter un PDF protégé en environ 11 ms avec un script Python non optimisé. Le champ /ID du trailer, généré par le même LCG, sert de vérification supplémentaire : il suffit de comparer le /ID attendu avec celui présent dans le fichier pour éliminer les faux positifs.
Analyse de la dérivation de clé dans Foxit et autres bibliothèques
Foxit Reader 2026.1.3.36551 dérive le file key à partir d’un hash du timestamp actuel, de 32 bytes non initialisés (qui se révèlent être zéro) et d’une constante. Le fragment Python reproduisant ce comportement est :
def derive_key(timestamp):
data = struct.pack("<Q", timestamp) + b"\x00"*32 + b"constant"
return hashlib.sha256(data).digest()
Le bloc /Perms contient du texte en clair, ce qui permet de valider chaque hypothèse de timestamp avec une seule opération AES et un hash. En exploitant le parallélisme de Rayon sur un Ryzen 9 7950X (32 threads), les chercheurs ont atteint 370‑376 millions de timestamps par seconde. Ainsi, pour un document créé récemment, la recherche du bon timestamp se situe dans l’ordre de quelques millisecondes.
OpenPDF 3.0.5 et iText 9.7.1 utilisent un ARC4 stream seedé par le timestamp en millisecondes et la mémoire libre de la JVM. Bien que cet état soit plus large que les 9 bits de MuPDF, il reste non‑cryptographique : la même faiblesse de prédictibilité permet de restreindre l’espace de recherche et de récupérer la clé en confrontant les sels stockés en clair dans les entrées /U et /O.
Conséquences et mesures correctives
Dans chaque cas, la génération du file key repose sur une source d’aléa non sécurisée, ce qui annule l’avantage théorique de l’AES‑256 (256 bits de sécurité) au profit d’un espace de recherche de quelques centaines ou milliers de valeurs. Les vulnérabilités ont été signalées aux éditeurs ; MuPDF a corrigé le problème en remplaçant le LCG par un générateur cryptographique. Les utilisateurs de documents existants restent exposés, car les clés déjà dérivées ne peuvent être re‑chiffrées rétroactivement. La recommandation principale consiste à utiliser un générateur de nombres aléatoires cryptographiquement sécurisé (ex. arc4random, getrandom) pour toutes les étapes de dérivation de clé, et à vérifier que les sels et identifiants sont eux‑mêmes générés de façon imprévisible.