В MuPDF и Foxit PDF Reader нашли слабые ключи AES-256 в PDF
Авторы блога v12.sh обнаружили, что несколько PDF-реализаций заявляют шифрование AES-256, но генерируют 256-битный ключ из очень малого пространства состояний или из небезопасных источников случайности. В результате защищённые PDF можно расшифровать без пароля. Под удар попали MuPDF, Foxit PDF Reader, а также Java-библиотеки OpenPDF и iText.
Как устроено шифрование. При шифровании AES-256 пароль не шифрует документ напрямую: реализация генерирует случайный 32-байтный файловый ключ, который шифрует содержимое. Затем из пароля и соли хешированием получают отдельный ключ, и им шифруется этот случайный файловый ключ. В ревизии 6 это хеширование намеренно медленное, в более старой ревизии 5 AES-256, нет. Пароль пользователя (для просмотра) и пароль владельца (для смены прав) открывают один и тот же файловый ключ, поэтому ограничения остаются на совести программы-читателя: с паролем для просмотра можно сделать и копию без ограничений. Если сам файловый ключ угадывается, пароль не нужен вовсе.
MuPDF. Версии до 1.28.5 используют реализацию семейства drand48, 48-битный линейный конгруэнтный генератор, который один раз инициализируется текущим временем в секундах (epoch). Младшее слово состояния, константа 0x330e, а 32-битная метка времени занимает биты с 16-го по 47-й. Функция fz_memrnd() берёт случайное число, сдвигает генератор и использует младший байт числа как случайный байт; сдвиг отбрасывает биты 0-16, приведение к unsigned char, всё выше 24-го бита. Значит, каждый байт берётся из битов 17-24 состояния. Старшие биты состояния никогда не влияют на младшие (умножение и сложение переносят биты только вверх), поэтому всё выше 24-го бита можно игнорировать. Остаётся 25 бит, из них нижние 16 всегда равны 0x330e, так что значение имеют лишь девять бит метки времени. Метки, отличающиеся на 512 секунд, дают абсолютно одинаковый поток байтов. Ключевое пространство крошечное, поэтому достаточно перебрать все возможные 32-байтные ключи и попробовать расшифровать. Тот же генератор MuPDF использует для /ID документа, который хранится в трейлере открытым текстом: при записи документа сначала берутся 32 байта из генератора, а затем сразу ключ, так что /ID служит дополнительной проверкой. Перебор всех 512 потоков в неоптимизированной реализации на чистом Python занимает около 11 миллисекунд, и найденный ключ расшифровывает документ без обоих паролей.
Foxit. Foxit PDF Reader 2026.1.3.36551 выводит файловый ключ как хеш от текущей метки времени, 32 неинициализированных байтов и константы. На практике эти байты заполнены нулями и случайности не добавляют. Блок /Perms содержит известный открытый текст, поэтому проверка кандидата, это один хеш и расшифровка одного блока AES, без хеширования пароля и без расшифровки документа. Реализация авторов на Rust (многопоточность через Rayon) перебирает метки времени вниз от текущего момента и на Ryzen 9 7950X с 32 потоками держит 370-376 миллионов меток в секунду. Полный перебор всех меток с 1970 года до сегодняшнего дня тоже возможен, а для недавно созданного документа поиск занимает порядка миллисекунд.
Откуда это взялось. Тот же шаблон встретился в нескольких редакторах PDF, и авторы проследили его до первого публичного коммита PDFium в мае 2014 года: заголовок файла указывает Foxit как автора исходного кода, так что код, по-видимому, пришёл от Foxit. Другие редакторы могли перенять его через PDFium. 32-байтное среднее значение, это m_EncryptKey, то есть неинициализированное содержимое буфера ключа, хешируемое само в себя; при допущении, что оно заполнено нулями, получается ровно та схема, которую Foxit использует до сих пор.
OpenPDF и iText. OpenPDF 3.0.5 и iText 9.7.1 направляют криптографическую случайность через класс IVGenerator, поток ARC4, инициализированный меткой времени в миллисекундах и объёмом свободной памяти JVM. Тот же поток порождает файловый ключ и 32 байта солей, лежащих открытым текстом в /U и /O. Догадки о метке времени и свободной памяти можно проверять по этим солям, а затем восстанавливать ключ из соседних байтов. Разброс здесь заметно больше, чем у MuPDF и Foxit, но обещание 256-битной стойкости всё равно подорвано небезопасной случайностью и заниженным пространством состояний.
Итог. Во всех случаях файловый ключ создаётся из некриптостойкого источника случайности. То, что задумывалось как усиление защиты по сравнению со старыми ключами, выводимыми из пароля пользователя, на практике дало более слабые ключи. Об уязвимостях сообщили вендорам, в случае MuPDF они исправлены. Уже опубликованные копии задним числом защитить нельзя, поэтому проблемы могут иметь последствия и дальше. Авторы выложили свои дешифровщики для MuPDF и Foxit.
Ключевые факты
- В MuPDF до версии 1.28.5 на реальный ключ AES-256 влияют только 9 бит состояния генератора drand48, инициализированного временем в секундах; метки времени с разницей 512 секунд дают одинаковый поток байтов.
- Перебор всех 512 вариантов для MuPDF занимает около 11 миллисекунд даже в неоптимизированном коде на Python; ключ открывает документ без пароля.
- Foxit PDF Reader 2026.1.3.36551 выводит ключ из хеша метки времени, нулевых неинициализированных байтов и константы; решение авторов на Rust проверяет 370-376 млн меток в секунду на Ryzen 9 7950X (32 потока).
- Тот же шаблон найден в первом публичном коммите PDFium (май 2014), заголовок которого указывает Foxit как автора кода; OpenPDF 3.0.5 и iText 9.7.1 используют ARC4-генератор с меткой времени и свободной памятью JVM.
- О проблемах сообщено вендорам, в MuPDF они исправлены; авторы опубликовали дешифровщики для MuPDF и Foxit.
Почему это важно
Заявленное шифрование AES-256 должно давать 256 бит стойкости, но здесь она сводится к перебору, который у MuPDF занимает миллисекунды. Сам алгоритм не сломан: слабое место, генерация случайного файлового ключа. Шаблон из PDFium, по словам авторов, существует с мая 2014 года, а значит, в PDF, защищённых паролем, ключ мог быть угадываемым годами. Усиление ревизии 6 (медленное хеширование пароля) тут не помогает, потому что пароль для расшифровки не нужен.
Кому это важно
Тем, кто защищает документы паролем и рассылает их или хранит, особенно если файлы создавались в MuPDF (до версии 1.28.5), Foxit PDF Reader, OpenPDF или iText. Также разработчикам и командам, которые используют эти библиотеки для генерации зашифрованных PDF, и специалистам по безопасности, оценивающим риск утечки защищённых документов.
Как это применить
Источник не даёт пошаговых рекомендаций. Из текста следует: у MuPDF проблема исправлена в версии 1.28.5, так что имеет смысл проверить версию библиотеки или приложения, которым создаются защищённые PDF. Для остальных реализаций в источнике не сказано, исправлены ли проблемы, поэтому защиту паролем PDF, созданных в Foxit, OpenPDF и iText, разумно не считать надёжной, пока вендор не подтвердит исправление. Авторы опубликовали дешифровщики для MuPDF и Foxit, которые можно использовать, например, для проверки собственных документов.
Можно ли доверять
Материал, технический разбор самих авторов исследования, с описанием механики генератора, числами и проверкой на тестовом документе; логику рассуждения о 9 битах можно проверить по описанию состояния генератора. Имена авторов и организации в тексте не указаны, идентификаторов CVE нет, дат раскрытия нет. Утверждение, что код Foxit, источник шаблона, сформулировано осторожно («по-видимому»), а о распространении через PDFium сказано лишь «могли перенять». Для OpenPDF и iText конкретных оценок времени поиска не приведено.
Риски и подводные камни
Уже распространённые PDF задним числом не защитить: по словам авторов, ранее опубликованные копии остаются уязвимыми. В тексте не сказано, исправили ли проблемы Foxit, OpenPDF и iText: подтверждено исправление только в MuPDF. Не указано и масштабов: сколько пользователей или документов затронуто, неизвестно. Публичные дешифровщики для MuPDF и Foxit снижают порог для злоупотреблений. Для недавно созданных документов поиск ключа занимает порядка миллисекунд.
«То, что должно было стать усилением защиты по сравнению со старыми ключами, выводимыми из пароля пользователя, на практике привело к более слабым ключам.»
— авторы исследования, блог v12.sh