Mark weak encryption with API changes (fixes #576)

This commit is contained in:
Jay Berkenbilt 2022-04-30 16:05:28 -04:00
parent 2213ed0c3d
commit 8ccd3a8a89
14 changed files with 288 additions and 70 deletions

View File

@ -1,8 +1,32 @@
2022-04-30 Jay Berkenbilt <ejb@ql.org>
* QPDFWriter: change encryption API calls
- Remove deprecated versions of setR*EncryptionParameters
methods from before qpdf 8.4.0
- Replace setR2EncryptionParameters with
setR2EncryptionParametersInsecure
- Replace setR3EncryptionParameters with
setR3EncryptionParametersInsecure
- Replace setR4EncryptionParameters with
setR4EncryptionParametersInsecure
* C API: change encryption API calls to match C++ interface
- Remove pre-8.4.0 functions:
- qpdf_set_r3_encryption_parameters
- qpdf_set_r4_encryption_parameters
- qpdf_set_r5_encryption_parameters
- qpdf_set_r6_encryption_parameters
- Add "_insecure" to insecure encryption triggers:
- Replace void qpdf_set_r2_encryption_parameters
with qpdf_set_r2_encryption_parameters_insecure
- Replace void qpdf_set_r3_encryption_parameters2
with qpdf_set_r3_encryption_parameters_insecure
- Replace void qpdf_set_r4_encryption_parameters2
with qpdf_set_r4_encryption_parameters_insecure
* Make attempting to write encrypted files that use RC4 (40-bit or
128-bit without AES) an error rather than a warning when
--allow-weak-crypto is not specified.
--allow-weak-crypto is not specified. Fixes #576.
2022-04-29 Jay Berkenbilt <ejb@ql.org>

12
TODO
View File

@ -475,18 +475,6 @@ Comments appear in the code prefixed by "ABI". Always Search for ABI
in source and header files to find items not listed here. Also search
for "[[deprecated" to find deprecated APIs that can be removed.
* Deal with weak cryptographic algorithms:
* Github issue #576
* Add something to QPDFWriter that you must call in order to allow
creation of files with insecure crypto. Maybe
QPDFWriter::allowWeakCrypto. Call this when --allow-weak-crypto is
passed and probably also when copying encryption by default from
an input file. There should be some API change so that, when
people recompile with qpdf 11, their code won't suddenly stop
working. Getting this right will take careful consideration of the
developer and user experience. We don't want to create a situation
where exactly the same code fails to work in 11 but worked on 10.
See #576 for latest notes.
Page splitting/merging
======================

View File

@ -107,7 +107,7 @@ FuzzHelper::testWrite()
w = getWriter(q);
w->setStaticID(true);
w->setObjectStreamMode(qpdf_o_disable);
w->setR3EncryptionParameters(
w->setR3EncryptionParametersInsecure(
"u", "o", true, true, true, true, true, true, qpdf_r3p_full);
doWrite(w);

View File

@ -366,10 +366,12 @@ class QPDFWriter
// functions that could be useful to you, most notably
// utf8_to_pdf_doc.
// R3 uses RC4, which is a weak cryptographic algorithm. Don't use
// it unless you have to.
// R2 uses RC4, which is a weak cryptographic algorithm. Don't use
// it unless you have to. See "Weak Cryptography" in the manual.
// This encryption format is deprecated in the PDF 2.0
// specification.
QPDF_DLL
void setR2EncryptionParameters(
void setR2EncryptionParametersInsecure(
char const* user_password,
char const* owner_password,
bool allow_print,
@ -377,9 +379,11 @@ class QPDFWriter
bool allow_extract,
bool allow_annotate);
// R3 uses RC4, which is a weak cryptographic algorithm. Don't use
// it unless you have to.
// it unless you have to. See "Weak Cryptography" in the manual.
// This encryption format is deprecated in the PDF 2.0
// specification.
QPDF_DLL
void setR3EncryptionParameters(
void setR3EncryptionParametersInsecure(
char const* user_password,
char const* owner_password,
bool allow_accessibility,
@ -389,10 +393,13 @@ class QPDFWriter
bool allow_form_filling,
bool allow_modify_other,
qpdf_r3_print_e print);
// R4 uses RC4, which is a weak cryptographic algorithm, when
// use_aes=false. Don't use it unless you have to.
// When use_aes=false, this call enables R4 with RC4, which is a
// weak cryptographic algorithm. Even with use_aes=true, the
// overall encryption scheme is weak. Don't use it unless you have
// to. See "Weak Cryptography" in the manual. This encryption
// format is deprecated in the PDF 2.0 specification.
QPDF_DLL
void setR4EncryptionParameters(
void setR4EncryptionParametersInsecure(
char const* user_password,
char const* owner_password,
bool allow_accessibility,
@ -419,6 +426,8 @@ class QPDFWriter
bool allow_modify_other,
qpdf_r3_print_e print,
bool encrypt_metadata);
// This is the only password-based encryption format supported by
// the PDF specification.
QPDF_DLL
void setR6EncryptionParameters(
char const* user_password,

View File

@ -458,8 +458,13 @@ extern "C" {
QPDF_DLL
void qpdf_set_preserve_encryption(qpdf_data qpdf, QPDF_BOOL value);
/* The *_insecure functions are identical to the old versions but
* have been renamed as a an alert to the caller that they are
* insecure. See "Weak Cryptographic" in the manual for
* details.
*/
QPDF_DLL
void qpdf_set_r2_encryption_parameters(
void qpdf_set_r2_encryption_parameters_insecure(
qpdf_data qpdf,
char const* user_password,
char const* owner_password,
@ -469,7 +474,7 @@ extern "C" {
QPDF_BOOL allow_annotate);
QPDF_DLL
void qpdf_set_r3_encryption_parameters2(
void qpdf_set_r3_encryption_parameters_insecure(
qpdf_data qpdf,
char const* user_password,
char const* owner_password,
@ -482,7 +487,7 @@ extern "C" {
enum qpdf_r3_print_e print);
QPDF_DLL
void qpdf_set_r4_encryption_parameters2(
void qpdf_set_r4_encryption_parameters_insecure(
qpdf_data qpdf,
char const* user_password,
char const* owner_password,

View File

@ -14,4 +14,4 @@ libqpdf/qpdf/auto_job_json_decl.hh 06caa46eaf71db8a50c046f91866baa8087745a947431
libqpdf/qpdf/auto_job_json_init.hh 06d51f11c117011256e175386eee9946441f3c22b49dd91fc591bbc1fa3bbeec
libqpdf/qpdf/auto_job_schema.hh 43273b9edfc48b1f4cccbff1d2b31916a9057c474ef97d2936b2f1f14170885b
manual/_ext/qpdf.py e9ac9d6c70642a3d29281ee5ad92ae2422dee8be9306fb8a0bc9dba0ed5e28f3
manual/cli.rst 6a2d99acedbd207370a8dc2807f6657323c42bccbe51ebdc6bc2d00f6851219c
manual/cli.rst 70258db13d89b0476248e9703bf5f50ffe28fce2a179dfeca241582dd28b455c

View File

@ -2815,19 +2815,22 @@ QPDFJob::setEncryptionOptions(QPDF& pdf, QPDFWriter& w)
QTC::TC("qpdf", "QPDFJob weak crypto error");
*(this->m->cerr)
<< this->m->message_prefix
<< ": refusing to write a file with RC4, a weak cryptographic algorithm"
<< ": refusing to write a file with RC4, a weak cryptographic "
"algorithm"
<< std::endl
<< "Please use 256-bit keys for better security." << std::endl
<< "Pass --allow-weak-crypto to enable writing insecure files."
<< std::endl
<< "See also https://qpdf.readthedocs.io/en/stable/weak-crypto.html"
<< "See also "
"https://qpdf.readthedocs.io/en/stable/weak-crypto.html"
<< std::endl;
throw std::runtime_error("refusing to write a file with weak crypto");
throw std::runtime_error(
"refusing to write a file with weak crypto");
}
}
switch (R) {
case 2:
w.setR2EncryptionParameters(
w.setR2EncryptionParametersInsecure(
m->user_password.c_str(),
m->owner_password.c_str(),
m->r2_print,
@ -2836,7 +2839,7 @@ QPDFJob::setEncryptionOptions(QPDF& pdf, QPDFWriter& w)
m->r2_annotate);
break;
case 3:
w.setR3EncryptionParameters(
w.setR3EncryptionParametersInsecure(
m->user_password.c_str(),
m->owner_password.c_str(),
m->r3_accessibility,
@ -2848,7 +2851,7 @@ QPDFJob::setEncryptionOptions(QPDF& pdf, QPDFWriter& w)
m->r3_print);
break;
case 4:
w.setR4EncryptionParameters(
w.setR4EncryptionParametersInsecure(
m->user_password.c_str(),
m->owner_password.c_str(),
m->r3_accessibility,

View File

@ -365,7 +365,7 @@ QPDFWriter::setPCLm(bool val)
}
void
QPDFWriter::setR2EncryptionParameters(
QPDFWriter::setR2EncryptionParametersInsecure(
char const* user_password,
char const* owner_password,
bool allow_print,
@ -391,7 +391,7 @@ QPDFWriter::setR2EncryptionParameters(
}
void
QPDFWriter::setR3EncryptionParameters(
QPDFWriter::setR3EncryptionParametersInsecure(
char const* user_password,
char const* owner_password,
bool allow_accessibility,
@ -419,7 +419,7 @@ QPDFWriter::setR3EncryptionParameters(
}
void
QPDFWriter::setR4EncryptionParameters(
QPDFWriter::setR4EncryptionParametersInsecure(
char const* user_password,
char const* owner_password,
bool allow_accessibility,

View File

@ -674,7 +674,7 @@ qpdf_set_preserve_encryption(qpdf_data qpdf, QPDF_BOOL value)
}
void
qpdf_set_r2_encryption_parameters(
qpdf_set_r2_encryption_parameters_insecure(
qpdf_data qpdf,
char const* user_password,
char const* owner_password,
@ -683,8 +683,8 @@ qpdf_set_r2_encryption_parameters(
QPDF_BOOL allow_extract,
QPDF_BOOL allow_annotate)
{
QTC::TC("qpdf", "qpdf-c called qpdf_set_r2_encryption_parameters");
qpdf->qpdf_writer->setR2EncryptionParameters(
QTC::TC("qpdf", "qpdf-c called qpdf_set_r2_encryption_parameters_insecure");
qpdf->qpdf_writer->setR2EncryptionParametersInsecure(
user_password,
owner_password,
allow_print != QPDF_FALSE,
@ -694,7 +694,7 @@ qpdf_set_r2_encryption_parameters(
}
void
qpdf_set_r3_encryption_parameters2(
qpdf_set_r3_encryption_parameters_insecure(
qpdf_data qpdf,
char const* user_password,
char const* owner_password,
@ -706,8 +706,8 @@ qpdf_set_r3_encryption_parameters2(
QPDF_BOOL allow_modify_other,
enum qpdf_r3_print_e print)
{
QTC::TC("qpdf", "qpdf-c called qpdf_set_r3_encryption_parameters");
qpdf->qpdf_writer->setR3EncryptionParameters(
QTC::TC("qpdf", "qpdf-c called qpdf_set_r3_encryption_parameters_insecure");
qpdf->qpdf_writer->setR3EncryptionParametersInsecure(
user_password,
owner_password,
allow_accessibility != QPDF_FALSE,
@ -720,7 +720,7 @@ qpdf_set_r3_encryption_parameters2(
}
void
qpdf_set_r4_encryption_parameters2(
qpdf_set_r4_encryption_parameters_insecure(
qpdf_data qpdf,
char const* user_password,
char const* owner_password,
@ -734,8 +734,8 @@ qpdf_set_r4_encryption_parameters2(
QPDF_BOOL encrypt_metadata,
QPDF_BOOL use_aes)
{
QTC::TC("qpdf", "qpdf-c called qpdf_set_r4_encryption_parameters");
qpdf->qpdf_writer->setR4EncryptionParameters(
QTC::TC("qpdf", "qpdf-c called qpdf_set_r4_encryption_parameters_insecure");
qpdf->qpdf_writer->setR4EncryptionParametersInsecure(
user_password,
owner_password,
allow_accessibility != QPDF_FALSE,

View File

@ -470,12 +470,18 @@ Related Options
option is necessary to create 40-bit files or 128-bit files that
use RC4 encryption.
Starting with version 10.4, qpdf issues warnings when requested to
create files using RC4 encryption. This option suppresses those
warnings. In future versions of qpdf, qpdf will refuse to create
files with weak cryptography when this flag is not given. See
Encrypted PDF files using 40-bit keys or 128-bit keys without AES
use the insecure *RC4* encryption algorithm. Starting with version
11.0, qpdf's default behavior is to refuse to write files using RC4
encryption. Use this option to allow creation of such files. In
versions 10.4 through 10.6, attempting to create weak encrypted
files was a warning, rather than an error, without this flag. See
:ref:`weak-crypto` for additional details.
No check is performed for weak crypto when preserving encryption
parameters from or copying encryption parameters from other files.
The rationale for this is discussed in :ref:`weak-crypto`.
.. qpdf:option:: --keep-files-open=[y|n]
.. help: manage keeping multiple files open
@ -741,6 +747,9 @@ Related Options
file without having to manual specify all the individual settings.
See also :qpdf:ref:`--decrypt`.
Checks for weak cryptographic algorithms are intentionally not made
by this operation. See :ref:`weak-crypto` for the rationale.
.. qpdf:option:: --encryption-file-password=password
.. help: supply password for --copy-encryption

View File

@ -62,6 +62,10 @@ For a detailed list of changes, please see the file
- The default json output version when :qpdf:ref:`--json` is
specified has been changed from ``1`` to ``latest``.
- The :qpdf:ref:`--allow-weak-crypto` flag is now mandatory when
explicitly creating files with weak cryptographic algorithms.
See :ref:`weak-crypto` for a discussion.
- API: breaking changes
- Remove
@ -73,6 +77,19 @@ For a detailed list of changes, please see the file
``QPDFNumberTreeObjectHelper`` constructors that don't take a
``QPDF&`` argument.
- Intentionally break API to call attention to operations that
write files with insecure encryption:
- Remove pre qpdf-8.4.0 encryption API methods from ``QPDFWriter``
and their corresponding C API functions
- Add ``Insecure`` to the names of some ``QPDFWriter`` methods
and ``_insecure`` to the names of some C API functions without
otherwise changing their behavior
- See :ref:`breaking-crypto-api` for specific details, and see
:ref:`weak-crypto` for a general discussion.
- Library Enhancements
- Support for more fluent programming with ``QPDFObjectHandle``.

View File

@ -3,24 +3,55 @@
Weak Cryptography
=================
Start with version 10.4, qpdf is taking steps to reduce the likelihood
of a user *accidentally* creating PDF files with insecure cryptography
but will continue to allow creation of such files indefinitely with
explicit acknowledgment.
For help with compiler errors in qpdf 11.0 or newer, see
:ref:`breaking-crypto-api`.
The PDF file format makes use of RC4, which is known to be a weak
cryptography algorithm, and MD5, which is a weak hashing algorithm. In
version 10.4, qpdf generates warnings for some (but not all) cases of
writing files with weak cryptography when invoked from the command-line.
These warnings can be suppressed using the
:qpdf:ref:`--allow-weak-crypto` option.
Since 2006, the PDF specification has offered ways to create encrypted
PDF files without using weak cryptography, though it took a few years
for many PDF readers and writers to catch up. It is still necessary to
support weak encryption algorithms to read encrypted PDF files that
were created using weak encryption algorithms, including all PDF files
created before the modern formats were introduced or widely supported.
It is planned for qpdf version 11 to be stricter, making it an error to
write files with insecure cryptography from the command-line tool in
most cases without specifying the
:qpdf:ref:`--allow-weak-crypto` flag and also to require
explicit steps when using the C++ library to enable use of insecure
cryptography.
Starting with version 10.4, qpdf began taking steps to reduce the
likelihood of a user *accidentally* creating PDF files with insecure
cryptography but will continue to allow creation of such files
indefinitely with explicit acknowledgment. The restrictions on use of
weak cryptography were made stricter with qpdf 11.
Definition of Weak Cryptographic Algorithm
------------------------------------------
We divide weak cryptographic algorithms into two categories: weak
encryption and weak hashing. Encryption is encoding data such that a
key of some sort is required to decode it. Hashing is creating a short
value from data in such a way that it is extremely improbable to find
two documents with the same hash (known has a hash collision) and
extremely difficult to intentionally create a document with a specific
hash or two documents with the same hash.
When we say that an encryption algorithm is weak, we either mean that
a mathematical flaw has been discovered that makes it inherently
crackable or that it is sufficiently simple that modern computer
technology makes it possible to use "brute force" to crack. For
example, when 40-bit keys were originally introduced, it wasn't
practical to consider trying all possible keys, but today such a thing
is possible.
When we say that a hashing algorithm is weak, we mean that, either
because of mathematical flaw or insufficient complexity, it is
computationally feasible to intentionally construct a hash collision.
While weak encryption should always be avoided, there are cases in
which it is safe to use a weak hashing algorithm when security is not
a factor. For example, a weak hashing algorithm should not be used as
the only mechanism to test whether a file has been tampered with. In
other words, you can't use a weak hash as a digital signature. There
is no harm, however, in using a weak hash as a way to sort or index
documents as long as hash collisions are tolerated. It is also common
to use weak hashes as checksums, which are often used a check that a
file wasn't damanged in transit or storage, though for true integrity,
a strong hash would be better.
Note that qpdf must always retain support for weak cryptographic
algorithms since this is required for reading older PDF files that use
@ -31,3 +62,135 @@ since these are sometimes needed to test or work with older versions of
software. Even if other cryptography libraries drop support for RC4 or
MD5, qpdf can always fall back to its internal implementations of those
algorithms, so they are not going to disappear from qpdf.
Uses of Weak Encryption in qpdf
---------------------------------
When PDF files are encrypted using 40-bit encryption or 128-bit
encryption without AES, then the weak *RC4* algorithm is used. You can
avoid using weak encryption in qpdf by always using 256-bit
encryption. Unless you are trying to create files that need to be
opened with PDF readers from before about 2010 (by which time most
readers had added support for the stronger encryption algorithms) or
are creating insecure files explicitly for testing or some similar
purpose, there is no reason to use anything other than 256-bit
encryption.
By default, qpdf refuses to write a file that uses weak encryption.
You can explicitly allow this by specifying the
:qpdf:ref:`--allow-weak-crypto` option.
In qpdf 11, all library methods that could potentially cause files to
be written with weak encryption were deprecated, and methods to enable
weak encryption were either given explicit names indicating this or
take required arguments to enable the insecure behavior.
There is one exception: when encryption parameters are copied from the
input file or another file to the output file, there is no prohibition
or even warning against using insecure encryption. The reason is that
many qpdf operations simply preserve whatever encryption is there, and
requiring confirmation to *preserve* insecure encryption would cause
qpdf to break when non-encryption-related operations were performed on
files that happened to be encrypted. Failing or generating warnings in
this case would likely have the effect of making people use the
:qpdf:ref:`--allow-weak-crypto` option blindly, which would be worse
than just letting those files go so that explicit, conscious selection
of weak crypto would be more likely to be noticed. Why, you might ask,
does this apply to :qpdf:ref:`--copy-encryption` as well as to the
default behavior preserving encryption? The answer is that
:qpdf:ref:`--copy-encryption` works with an unencrypted file as input,
which enables workflows where one may start with a file, decrypt it
*just in case*, perform a series of operations, and then reapply the
original encryption, *if any*. Also, one may have a template used for
encryption that one may apply to a variety of output files, and it
would be annoying to be warned about it for every output file.
Uses of Weak Hashing In QPDF
----------------------------
The PDF specification makes use the weak *MD5* hashing algorithm in
several places. While it is used in the encryption algorithms,
breaking MD5 would not be adequate to crack an encrypted file when
256-bit encryption is in use, so using 256-bit encryption is adequate
for avoiding the use of MD5 for anything security-sensitive.
MD5 is used in the following non-security-sensitive ways:
- Generation of the document ID. The document ID is an input parameter
to the document encryption but is not itself considered to be
secure. They are supposed to be unique, but they are not
tamper-resistent in non-encrypted PDF files, and hash collisions
must be tolerated.
The PDF specification recommends but does not require the use of MD5
in generation of document IDs. Usually there is also a random
component to document ID generation. There is a qpdf-specific
feature of generating a *deterministic ID* (see
:qpdf:ref:`--deterministic-id`) which also uses MD5. While it would
certainly be possible to change the deterministic ID algorithm to
not use MD5, doing so would break all previous deterministic IDs
(which would render the feature useless for many cases) and would
offer very little benefit since even a securely generated document
ID is not itself a security-sensitive value.
- Checksums in embedded file streams -- the PDF specification
specifies the use of MD5.
It is therefore not possible completely avoid the use of MD5 with
qpdf, but as long as you are using 256-bit encryption, it is not used
in a securty-sensitive fashion.
.. _breaking-crypto-api:
API-Breaking Changes in qpdf 11.0
---------------------------------
In qpdf 11, several deprecated functions and methods were removed.
These methods provided an incomplete API. Alternatives were added in
qpdf 8.4.0. The removed functions are
- C API: ``qpdf_set_r3_encryption_parameters``,
``qpdf_set_r4_encryption_parameters``,
``qpdf_set_r5_encryption_parameters``,
``qpdf_set_r6_encryption_parameters``
- ``QPDFWriter``: overloaded versions of these methods with fewer
arguments: ``setR3EncryptionParameters``,
``setR4EncryptionParameters``, ``setR5EncryptionParameters``, and
``setR6EncryptionParameters``
Additionally, remaining functions/methods had their names changed to
signal that they are insecure and to force developers to make a
decision. If you intentionally want to continue to use insecure
cryptographic algorithms and create insecure files, you can change
your code just add ``_insecure`` or ``Insecure`` to the end of the
function as needed. (Note the disappearance of ``2`` in some of the C
functions as well.) Better, you should migrate your code to use more
secure encryption as documented in :file:`QPDFWriter.hh`. Use the
``R6`` methods (or their corresponding C functions) to create files
with 256-bit encryption.
.. list-table:: Renamed Functions
:widths: 50 50
:header-rows: 1
- - Old Name
- New Name
- - qpdf_set_r2_encryption_parameters
- qpdf_set_r2_encryption_parameters_insecure
- - qpdf_set_r3_encryption_parameters2
- qpdf_set_r3_encryption_parameters_insecure
- - qpdf_set_r4_encryption_parameters2
- qpdf_set_r2_encryption_parameters_insecure
- - QPDFWriter::setR2EncryptionParameters
- QPDFWriter::setR2EncryptionParametersInsecure
- - QPDFWriter::setR3EncryptionParameters
- QPDFWriter::setR3EncryptionParametersInsecure
- - QPDFWriter::setR4EncryptionParameters
- QPDFWriter::setR4EncryptionParametersInsecure

View File

@ -320,7 +320,7 @@ test11(
qpdf_read(qpdf, infile, password);
qpdf_init_write(qpdf, outfile);
qpdf_set_static_ID(qpdf, QPDF_TRUE);
qpdf_set_r2_encryption_parameters(
qpdf_set_r2_encryption_parameters_insecure(
qpdf, "user1", "owner1", QPDF_FALSE, QPDF_TRUE, QPDF_TRUE, QPDF_TRUE);
qpdf_write(qpdf);
report_errors();
@ -336,7 +336,7 @@ test12(
qpdf_read(qpdf, infile, password);
qpdf_init_write(qpdf, outfile);
qpdf_set_static_ID(qpdf, QPDF_TRUE);
qpdf_set_r3_encryption_parameters2(
qpdf_set_r3_encryption_parameters_insecure(
qpdf,
"user2",
"owner2",
@ -397,7 +397,7 @@ test15(
qpdf_init_write(qpdf, outfile);
qpdf_set_static_ID(qpdf, QPDF_TRUE);
qpdf_set_static_aes_IV(qpdf, QPDF_TRUE);
qpdf_set_r4_encryption_parameters2(
qpdf_set_r4_encryption_parameters_insecure(
qpdf,
"user2",
"owner2",

View File

@ -130,8 +130,8 @@ qpdf-c called qpdf_set_qdf_mode 0
qpdf-c called qpdf_set_static_ID 0
qpdf-c called qpdf_set_suppress_original_object_IDs 0
qpdf-c called qpdf_set_preserve_encryption 0
qpdf-c called qpdf_set_r2_encryption_parameters 0
qpdf-c called qpdf_set_r3_encryption_parameters 0
qpdf-c called qpdf_set_r2_encryption_parameters_insecure 0
qpdf-c called qpdf_set_r3_encryption_parameters_insecure 0
qpdf-c called qpdf_set_linearization 0
qpdf-c called qpdf_write 1
qpdf-c called qpdf_allow_accessibility 0
@ -158,7 +158,7 @@ QPDF_encryption cleartext metadata 0
QPDF_encryption aes decode stream 0
QPDFWriter forcing object stream disable 0
QPDFWriter forced version disabled encryption 0
qpdf-c called qpdf_set_r4_encryption_parameters 0
qpdf-c called qpdf_set_r4_encryption_parameters_insecure 0
qpdf-c called qpdf_set_static_aes_IV 0
QPDF_encryption stream crypt filter 0
QPDF ERR object stream with wrong type 0