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:
Jay Berkenbilt 2018-01-14 10:17:17 -05:00
parent 3e306ae64c
commit 569d74d36b
12 changed files with 164 additions and 14 deletions

View File

@ -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
View File

@ -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

View File

@ -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;

View File

@ -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()
{

View File

@ -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

View File

@ -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)
{

View File

@ -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 ---");

View 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

View 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

View 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

View File

@ -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

View File

@ -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