mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 02:49:00 +00:00
Allow raw encryption key to be specified
Add options to enable the raw encryption key to be directly shown or specified. Thanks to Didier Stevens <didier.stevens@gmail.com> for the idea and contribution of one implementation of this idea.
This commit is contained in:
parent
3e306ae64c
commit
569d74d36b
13
ChangeLog
13
ChangeLog
@ -1,3 +1,16 @@
|
||||
2018-01-14 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* Allow raw encryption key to be specified in libary and command
|
||||
line with the QPDF::setPasswordIsHexKey method and
|
||||
--password-is-hex-key option. Allow encryption key to be displayed
|
||||
with --show-encryption-key option. Thanks to Didier Stevens
|
||||
<didier.stevens@gmail.com> for the idea and contribution of one
|
||||
implementation of this idea. See his blog post at
|
||||
https://blog.didierstevens.com/2017/12/28/cracking-encrypted-pdfs-part-3/
|
||||
for a discussion of using this for cracking encrypted PDFs. I hope
|
||||
that a future release of qpdf will include some additional
|
||||
recovery options that may also make use of this capability.
|
||||
|
||||
2018-01-13 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* Fix lexical error: the PDF specification allows floating point
|
||||
|
4
TODO
4
TODO
@ -1,10 +1,6 @@
|
||||
Soon
|
||||
====
|
||||
|
||||
* Take changes on encryption-keys branch and make them usable.
|
||||
Replace the hex encoding and decoding piece, and come up with a
|
||||
more robust way of specifying the key.
|
||||
|
||||
* Consider whether there should be a mode in which QPDFObjectHandle
|
||||
returns nulls for operations on the wrong type instead of asserting
|
||||
the type. The way things are wired up now, this would have to be a
|
||||
|
@ -64,7 +64,11 @@ class QPDF
|
||||
// those that set parameters. If the input file is not
|
||||
// encrypted,either a null password or an empty password can be
|
||||
// used. If the file is encrypted, either the user password or
|
||||
// the owner password may be supplied.
|
||||
// the owner password may be supplied. The method
|
||||
// setPasswordIsHexKey may be called prior to calling this method
|
||||
// or any of the other process methods to force the password to be
|
||||
// interpreted as a raw encryption key. See comments on
|
||||
// setPasswordIsHexKey for more information.
|
||||
QPDF_DLL
|
||||
void processFile(char const* filename, char const* password = 0);
|
||||
|
||||
@ -94,6 +98,18 @@ class QPDF
|
||||
void processInputSource(PointerHolder<InputSource>,
|
||||
char const* password = 0);
|
||||
|
||||
// For certain forensic or investigatory purposes, it may
|
||||
// sometimes be useful to specify the encryption key directly,
|
||||
// even though regular PDF applications do not provide a way to do
|
||||
// this. calling setPasswordIsHexKey(true) before calling any of
|
||||
// the process methods will bypass the normal encryption key
|
||||
// computation or recovery mechanisms and interpret the bytes in
|
||||
// the password as a hex-encoded encryption key. Note that we
|
||||
// hex-encode the key because it may contain null bytes and
|
||||
// therefore can't be represented in a char const*.
|
||||
QPDF_DLL
|
||||
void setPasswordIsHexKey(bool);
|
||||
|
||||
// Create a QPDF object for an empty PDF. This PDF has no pages
|
||||
// or objects other than a minimal trailer, a document catalog,
|
||||
// and a /Pages tree containing zero pages. Pages and other
|
||||
@ -1145,6 +1161,7 @@ class QPDF
|
||||
QPDFTokenizer tokenizer;
|
||||
PointerHolder<InputSource> file;
|
||||
std::string last_object_description;
|
||||
bool provided_password_is_hex_key;
|
||||
bool encrypted;
|
||||
bool encryption_initialized;
|
||||
bool ignore_xref_streams;
|
||||
|
@ -75,6 +75,7 @@ QPDF::QPDFVersion()
|
||||
}
|
||||
|
||||
QPDF::Members::Members() :
|
||||
provided_password_is_hex_key(false),
|
||||
encrypted(false),
|
||||
encryption_initialized(false),
|
||||
ignore_xref_streams(false),
|
||||
@ -171,6 +172,12 @@ QPDF::processInputSource(PointerHolder<InputSource> source,
|
||||
parse(password);
|
||||
}
|
||||
|
||||
void
|
||||
QPDF::setPasswordIsHexKey(bool val)
|
||||
{
|
||||
this->m->provided_password_is_hex_key = val;
|
||||
}
|
||||
|
||||
void
|
||||
QPDF::emptyPDF()
|
||||
{
|
||||
|
@ -1007,8 +1007,12 @@ QPDF::initializeEncryption()
|
||||
|
||||
EncryptionData data(V, R, Length / 8, P, O, U, OE, UE, Perms,
|
||||
id1, this->m->encrypt_metadata);
|
||||
if (check_owner_password(
|
||||
this->m->user_password, this->m->provided_password, data))
|
||||
if (this->m->provided_password_is_hex_key)
|
||||
{
|
||||
// ignore passwords in file
|
||||
}
|
||||
else if (check_owner_password(
|
||||
this->m->user_password, this->m->provided_password, data))
|
||||
{
|
||||
// password supplied was owner password; user_password has
|
||||
// been initialized for V < 5
|
||||
@ -1023,7 +1027,11 @@ QPDF::initializeEncryption()
|
||||
"", 0, "invalid password");
|
||||
}
|
||||
|
||||
if (V < 5)
|
||||
if (this->m->provided_password_is_hex_key)
|
||||
{
|
||||
this->m->encryption_key = QUtil::hex_decode(this->m->provided_password);
|
||||
}
|
||||
else if (V < 5)
|
||||
{
|
||||
// For V < 5, the user password is encrypted with the owner
|
||||
// password, and the user password is always used for
|
||||
|
39
qpdf/qpdf.cc
39
qpdf/qpdf.cc
@ -61,6 +61,7 @@ struct Options
|
||||
encryption_file(0),
|
||||
encryption_file_password(0),
|
||||
encrypt(false),
|
||||
password_is_hex_key(false),
|
||||
keylen(0),
|
||||
r2_print(true),
|
||||
r2_modify(true),
|
||||
@ -95,6 +96,7 @@ struct Options
|
||||
static_aes_iv(false),
|
||||
suppress_original_object_id(false),
|
||||
show_encryption(false),
|
||||
show_encryption_key(false),
|
||||
check_linearization(false),
|
||||
show_linearization(false),
|
||||
show_xref(false),
|
||||
@ -120,6 +122,7 @@ struct Options
|
||||
char const* encryption_file;
|
||||
char const* encryption_file_password;
|
||||
bool encrypt;
|
||||
bool password_is_hex_key;
|
||||
std::string user_password;
|
||||
std::string owner_password;
|
||||
int keylen;
|
||||
@ -158,6 +161,7 @@ struct Options
|
||||
bool static_aes_iv;
|
||||
bool suppress_original_object_id;
|
||||
bool show_encryption;
|
||||
bool show_encryption_key;
|
||||
bool check_linearization;
|
||||
bool show_linearization;
|
||||
bool show_xref;
|
||||
@ -227,6 +231,7 @@ Basic Options\n\
|
||||
parameters are being copied\n\
|
||||
--encrypt options -- generate an encrypted file\n\
|
||||
--decrypt remove any encryption on the file\n\
|
||||
--password-is-hex-key treat primary password option as a hex-encoded key\n\
|
||||
--pages options -- select specific pages from one or more files\n\
|
||||
--rotate=[+|-]angle:page-range\n\
|
||||
rotate each specified page 90, 180, or 270 degrees\n\
|
||||
@ -240,6 +245,11 @@ parameters will be copied, including both user and owner passwords, even\n\
|
||||
if the user password is used to open the other file. This works even if\n\
|
||||
the owner password is not known.\n\
|
||||
\n\
|
||||
The --password-is-hex-key option overrides the normal computation of\n\
|
||||
encryption keys. It only applies to the password used to open the main\n\
|
||||
file. This option is not ordinarily useful but can be helpful for forensic\n\
|
||||
or investigatory purposes. See manual for further discussion.\n\
|
||||
\n\
|
||||
The --rotate flag can be used to specify pages to rotate pages either\n\
|
||||
90, 180, or 270 degrees. The page range is specified in the same\n\
|
||||
format as with the --pages option, described below. Repeat the option\n\
|
||||
@ -434,6 +444,7 @@ automated test suites for software that uses the qpdf library.\n\
|
||||
This is option is not secure! FOR TESTING ONLY!\n\
|
||||
--no-original-object-ids suppress original object ID comments in qdf mode\n\
|
||||
--show-encryption quickly show encryption parameters\n\
|
||||
--show-encryption-key when showing encryption, reveal the actual key\n\
|
||||
--check-linearization check file integrity and linearization status\n\
|
||||
--show-linearization check and show all linearization data\n\
|
||||
--show-xref show the contents of the cross-reference table\n\
|
||||
@ -501,7 +512,7 @@ static std::string show_encryption_method(QPDF::encryption_method_e method)
|
||||
return result;
|
||||
}
|
||||
|
||||
static void show_encryption(QPDF& pdf)
|
||||
static void show_encryption(QPDF& pdf, Options& o)
|
||||
{
|
||||
// Extract /P from /Encrypt
|
||||
int R = 0;
|
||||
@ -520,8 +531,14 @@ static void show_encryption(QPDF& pdf)
|
||||
std::cout << "R = " << R << std::endl;
|
||||
std::cout << "P = " << P << std::endl;
|
||||
std::string user_password = pdf.getTrimmedUserPassword();
|
||||
std::cout << "User password = " << user_password << std::endl
|
||||
<< "extract for accessibility: "
|
||||
std::string encryption_key = pdf.getEncryptionKey();
|
||||
std::cout << "User password = " << user_password << std::endl;
|
||||
if (o.show_encryption_key)
|
||||
{
|
||||
std::cout << "Encryption key = "
|
||||
<< QUtil::hex_encode(encryption_key) << std::endl;
|
||||
}
|
||||
std::cout << "extract for accessibility: "
|
||||
<< show_bool(pdf.allowAccessibility()) << std::endl
|
||||
<< "extract for any purpose: "
|
||||
<< show_bool(pdf.allowExtractAll()) << std::endl
|
||||
@ -1339,6 +1356,10 @@ static void parse_options(int argc, char* argv[], Options& o)
|
||||
o.encrypt = false;
|
||||
o.copy_encryption = false;
|
||||
}
|
||||
else if (strcmp(arg, "password-is-hex-key") == 0)
|
||||
{
|
||||
o.password_is_hex_key = true;
|
||||
}
|
||||
else if (strcmp(arg, "copy-encryption") == 0)
|
||||
{
|
||||
if (parameter == 0)
|
||||
@ -1559,6 +1580,10 @@ static void parse_options(int argc, char* argv[], Options& o)
|
||||
o.show_encryption = true;
|
||||
o.require_outfile = false;
|
||||
}
|
||||
else if (strcmp(arg, "show-encryption-key") == 0)
|
||||
{
|
||||
o.show_encryption_key = true;
|
||||
}
|
||||
else if (strcmp(arg, "check-linearization") == 0)
|
||||
{
|
||||
o.check_linearization = true;
|
||||
@ -1673,6 +1698,10 @@ static void set_qpdf_options(QPDF& pdf, Options& o)
|
||||
{
|
||||
pdf.setAttemptRecovery(false);
|
||||
}
|
||||
if (o.password_is_hex_key)
|
||||
{
|
||||
pdf.setPasswordIsHexKey(true);
|
||||
}
|
||||
}
|
||||
|
||||
static void do_check(QPDF& pdf, Options& o, int& exit_code)
|
||||
@ -1693,7 +1722,7 @@ static void do_check(QPDF& pdf, Options& o, int& exit_code)
|
||||
<< pdf.getExtensionLevel();
|
||||
}
|
||||
std::cout << std::endl;
|
||||
show_encryption(pdf);
|
||||
show_encryption(pdf, o);
|
||||
if (pdf.isLinearized())
|
||||
{
|
||||
std::cout << "File is linearized\n";
|
||||
@ -1877,7 +1906,7 @@ static void do_inspection(QPDF& pdf, Options& o)
|
||||
}
|
||||
if (o.show_encryption)
|
||||
{
|
||||
show_encryption(pdf);
|
||||
show_encryption(pdf, o);
|
||||
}
|
||||
if (o.check_linearization)
|
||||
{
|
||||
|
@ -314,7 +314,7 @@ foreach my $file (qw(short-id long-id))
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
|
||||
$td->runtest("check $file.pdf",
|
||||
{$td->COMMAND => "qpdf --check a.pdf"},
|
||||
{$td->COMMAND => "qpdf --check --show-encryption-key a.pdf"},
|
||||
{$td->FILE => "$file-check.out",
|
||||
$td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
@ -2244,6 +2244,21 @@ $td->runtest("copy of unfilterable with crypt",
|
||||
$td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
|
||||
# Raw encryption key
|
||||
my @enc_key = (['user', '--password=user3'],
|
||||
['owner', '--password=owner3'],
|
||||
['hex', '--password-is-hex-key --password=35ea16a48b6a3045133b69ac0906c2e8fb0a2cc97903ae17b51a5786ebdba020']);
|
||||
$n_tests += scalar(@enc_key);
|
||||
foreach my $d (@enc_key)
|
||||
{
|
||||
my ($description, $pass) = @$d;
|
||||
$td->runtest("use/show encryption key ($description)",
|
||||
{$td->COMMAND =>
|
||||
"qpdf --check --show-encryption-key c-r5-in.pdf $pass"},
|
||||
{$td->FILE => "c-r5-key-$description.out", $td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
}
|
||||
|
||||
show_ntests();
|
||||
# ----------
|
||||
$td->notify("--- Content Preservation Tests ---");
|
||||
|
21
qpdf/qtest/qpdf/c-r5-key-hex.out
Normal file
21
qpdf/qtest/qpdf/c-r5-key-hex.out
Normal file
@ -0,0 +1,21 @@
|
||||
checking c-r5-in.pdf
|
||||
PDF Version: 1.7 extension level 3
|
||||
R = 5
|
||||
P = -2052
|
||||
User password =
|
||||
Encryption key = 35ea16a48b6a3045133b69ac0906c2e8fb0a2cc97903ae17b51a5786ebdba020
|
||||
extract for accessibility: allowed
|
||||
extract for any purpose: allowed
|
||||
print low resolution: allowed
|
||||
print high resolution: not allowed
|
||||
modify document assembly: allowed
|
||||
modify forms: allowed
|
||||
modify annotations: allowed
|
||||
modify other: allowed
|
||||
modify anything: allowed
|
||||
stream encryption method: AESv3
|
||||
string encryption method: AESv3
|
||||
file encryption method: AESv3
|
||||
File is not linearized
|
||||
No syntax or stream encoding errors found; the file may still contain
|
||||
errors that qpdf cannot detect
|
21
qpdf/qtest/qpdf/c-r5-key-owner.out
Normal file
21
qpdf/qtest/qpdf/c-r5-key-owner.out
Normal file
@ -0,0 +1,21 @@
|
||||
checking c-r5-in.pdf
|
||||
PDF Version: 1.7 extension level 3
|
||||
R = 5
|
||||
P = -2052
|
||||
User password =
|
||||
Encryption key = 35ea16a48b6a3045133b69ac0906c2e8fb0a2cc97903ae17b51a5786ebdba020
|
||||
extract for accessibility: allowed
|
||||
extract for any purpose: allowed
|
||||
print low resolution: allowed
|
||||
print high resolution: not allowed
|
||||
modify document assembly: allowed
|
||||
modify forms: allowed
|
||||
modify annotations: allowed
|
||||
modify other: allowed
|
||||
modify anything: allowed
|
||||
stream encryption method: AESv3
|
||||
string encryption method: AESv3
|
||||
file encryption method: AESv3
|
||||
File is not linearized
|
||||
No syntax or stream encoding errors found; the file may still contain
|
||||
errors that qpdf cannot detect
|
21
qpdf/qtest/qpdf/c-r5-key-user.out
Normal file
21
qpdf/qtest/qpdf/c-r5-key-user.out
Normal file
@ -0,0 +1,21 @@
|
||||
checking c-r5-in.pdf
|
||||
PDF Version: 1.7 extension level 3
|
||||
R = 5
|
||||
P = -2052
|
||||
User password = user3
|
||||
Encryption key = 35ea16a48b6a3045133b69ac0906c2e8fb0a2cc97903ae17b51a5786ebdba020
|
||||
extract for accessibility: allowed
|
||||
extract for any purpose: allowed
|
||||
print low resolution: allowed
|
||||
print high resolution: not allowed
|
||||
modify document assembly: allowed
|
||||
modify forms: allowed
|
||||
modify annotations: allowed
|
||||
modify other: allowed
|
||||
modify anything: allowed
|
||||
stream encryption method: AESv3
|
||||
string encryption method: AESv3
|
||||
file encryption method: AESv3
|
||||
File is not linearized
|
||||
No syntax or stream encoding errors found; the file may still contain
|
||||
errors that qpdf cannot detect
|
@ -3,6 +3,7 @@ PDF Version: 1.3
|
||||
R = 2
|
||||
P = -4
|
||||
User password =
|
||||
Encryption key = 2f382cf6e1
|
||||
extract for accessibility: allowed
|
||||
extract for any purpose: allowed
|
||||
print low resolution: allowed
|
||||
|
@ -3,6 +3,7 @@ PDF Version: 1.3
|
||||
R = 2
|
||||
P = -4
|
||||
User password =
|
||||
Encryption key = 897d768fbd
|
||||
extract for accessibility: allowed
|
||||
extract for any purpose: allowed
|
||||
print low resolution: allowed
|
||||
|
Loading…
Reference in New Issue
Block a user