Commit 569d74d36ba287b6951687ee1bdea45ae19091f8

Authored by Jay Berkenbilt
1 parent 3e306ae6

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.
ChangeLog
  1 +2018-01-14 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * Allow raw encryption key to be specified in libary and command
  4 + line with the QPDF::setPasswordIsHexKey method and
  5 + --password-is-hex-key option. Allow encryption key to be displayed
  6 + with --show-encryption-key option. Thanks to Didier Stevens
  7 + <didier.stevens@gmail.com> for the idea and contribution of one
  8 + implementation of this idea. See his blog post at
  9 + https://blog.didierstevens.com/2017/12/28/cracking-encrypted-pdfs-part-3/
  10 + for a discussion of using this for cracking encrypted PDFs. I hope
  11 + that a future release of qpdf will include some additional
  12 + recovery options that may also make use of this capability.
  13 +
1 2018-01-13 Jay Berkenbilt <ejb@ql.org> 14 2018-01-13 Jay Berkenbilt <ejb@ql.org>
2 15
3 * Fix lexical error: the PDF specification allows floating point 16 * Fix lexical error: the PDF specification allows floating point
1 Soon 1 Soon
2 ==== 2 ====
3 3
4 - * Take changes on encryption-keys branch and make them usable.  
5 - Replace the hex encoding and decoding piece, and come up with a  
6 - more robust way of specifying the key.  
7 -  
8 * Consider whether there should be a mode in which QPDFObjectHandle 4 * Consider whether there should be a mode in which QPDFObjectHandle
9 returns nulls for operations on the wrong type instead of asserting 5 returns nulls for operations on the wrong type instead of asserting
10 the type. The way things are wired up now, this would have to be a 6 the type. The way things are wired up now, this would have to be a
include/qpdf/QPDF.hh
@@ -64,7 +64,11 @@ class QPDF @@ -64,7 +64,11 @@ class QPDF
64 // those that set parameters. If the input file is not 64 // those that set parameters. If the input file is not
65 // encrypted,either a null password or an empty password can be 65 // encrypted,either a null password or an empty password can be
66 // used. If the file is encrypted, either the user password or 66 // used. If the file is encrypted, either the user password or
67 - // the owner password may be supplied. 67 + // the owner password may be supplied. The method
  68 + // setPasswordIsHexKey may be called prior to calling this method
  69 + // or any of the other process methods to force the password to be
  70 + // interpreted as a raw encryption key. See comments on
  71 + // setPasswordIsHexKey for more information.
68 QPDF_DLL 72 QPDF_DLL
69 void processFile(char const* filename, char const* password = 0); 73 void processFile(char const* filename, char const* password = 0);
70 74
@@ -94,6 +98,18 @@ class QPDF @@ -94,6 +98,18 @@ class QPDF
94 void processInputSource(PointerHolder<InputSource>, 98 void processInputSource(PointerHolder<InputSource>,
95 char const* password = 0); 99 char const* password = 0);
96 100
  101 + // For certain forensic or investigatory purposes, it may
  102 + // sometimes be useful to specify the encryption key directly,
  103 + // even though regular PDF applications do not provide a way to do
  104 + // this. calling setPasswordIsHexKey(true) before calling any of
  105 + // the process methods will bypass the normal encryption key
  106 + // computation or recovery mechanisms and interpret the bytes in
  107 + // the password as a hex-encoded encryption key. Note that we
  108 + // hex-encode the key because it may contain null bytes and
  109 + // therefore can't be represented in a char const*.
  110 + QPDF_DLL
  111 + void setPasswordIsHexKey(bool);
  112 +
97 // Create a QPDF object for an empty PDF. This PDF has no pages 113 // Create a QPDF object for an empty PDF. This PDF has no pages
98 // or objects other than a minimal trailer, a document catalog, 114 // or objects other than a minimal trailer, a document catalog,
99 // and a /Pages tree containing zero pages. Pages and other 115 // and a /Pages tree containing zero pages. Pages and other
@@ -1145,6 +1161,7 @@ class QPDF @@ -1145,6 +1161,7 @@ class QPDF
1145 QPDFTokenizer tokenizer; 1161 QPDFTokenizer tokenizer;
1146 PointerHolder<InputSource> file; 1162 PointerHolder<InputSource> file;
1147 std::string last_object_description; 1163 std::string last_object_description;
  1164 + bool provided_password_is_hex_key;
1148 bool encrypted; 1165 bool encrypted;
1149 bool encryption_initialized; 1166 bool encryption_initialized;
1150 bool ignore_xref_streams; 1167 bool ignore_xref_streams;
libqpdf/QPDF.cc
@@ -75,6 +75,7 @@ QPDF::QPDFVersion() @@ -75,6 +75,7 @@ QPDF::QPDFVersion()
75 } 75 }
76 76
77 QPDF::Members::Members() : 77 QPDF::Members::Members() :
  78 + provided_password_is_hex_key(false),
78 encrypted(false), 79 encrypted(false),
79 encryption_initialized(false), 80 encryption_initialized(false),
80 ignore_xref_streams(false), 81 ignore_xref_streams(false),
@@ -172,6 +173,12 @@ QPDF::processInputSource(PointerHolder&lt;InputSource&gt; source, @@ -172,6 +173,12 @@ QPDF::processInputSource(PointerHolder&lt;InputSource&gt; source,
172 } 173 }
173 174
174 void 175 void
  176 +QPDF::setPasswordIsHexKey(bool val)
  177 +{
  178 + this->m->provided_password_is_hex_key = val;
  179 +}
  180 +
  181 +void
175 QPDF::emptyPDF() 182 QPDF::emptyPDF()
176 { 183 {
177 processMemoryFile("empty PDF", EMPTY_PDF, strlen(EMPTY_PDF)); 184 processMemoryFile("empty PDF", EMPTY_PDF, strlen(EMPTY_PDF));
libqpdf/QPDF_encryption.cc
@@ -1007,8 +1007,12 @@ QPDF::initializeEncryption() @@ -1007,8 +1007,12 @@ QPDF::initializeEncryption()
1007 1007
1008 EncryptionData data(V, R, Length / 8, P, O, U, OE, UE, Perms, 1008 EncryptionData data(V, R, Length / 8, P, O, U, OE, UE, Perms,
1009 id1, this->m->encrypt_metadata); 1009 id1, this->m->encrypt_metadata);
1010 - if (check_owner_password(  
1011 - this->m->user_password, this->m->provided_password, data)) 1010 + if (this->m->provided_password_is_hex_key)
  1011 + {
  1012 + // ignore passwords in file
  1013 + }
  1014 + else if (check_owner_password(
  1015 + this->m->user_password, this->m->provided_password, data))
1012 { 1016 {
1013 // password supplied was owner password; user_password has 1017 // password supplied was owner password; user_password has
1014 // been initialized for V < 5 1018 // been initialized for V < 5
@@ -1023,7 +1027,11 @@ QPDF::initializeEncryption() @@ -1023,7 +1027,11 @@ QPDF::initializeEncryption()
1023 "", 0, "invalid password"); 1027 "", 0, "invalid password");
1024 } 1028 }
1025 1029
1026 - if (V < 5) 1030 + if (this->m->provided_password_is_hex_key)
  1031 + {
  1032 + this->m->encryption_key = QUtil::hex_decode(this->m->provided_password);
  1033 + }
  1034 + else if (V < 5)
1027 { 1035 {
1028 // For V < 5, the user password is encrypted with the owner 1036 // For V < 5, the user password is encrypted with the owner
1029 // password, and the user password is always used for 1037 // password, and the user password is always used for
qpdf/qpdf.cc
@@ -61,6 +61,7 @@ struct Options @@ -61,6 +61,7 @@ struct Options
61 encryption_file(0), 61 encryption_file(0),
62 encryption_file_password(0), 62 encryption_file_password(0),
63 encrypt(false), 63 encrypt(false),
  64 + password_is_hex_key(false),
64 keylen(0), 65 keylen(0),
65 r2_print(true), 66 r2_print(true),
66 r2_modify(true), 67 r2_modify(true),
@@ -95,6 +96,7 @@ struct Options @@ -95,6 +96,7 @@ struct Options
95 static_aes_iv(false), 96 static_aes_iv(false),
96 suppress_original_object_id(false), 97 suppress_original_object_id(false),
97 show_encryption(false), 98 show_encryption(false),
  99 + show_encryption_key(false),
98 check_linearization(false), 100 check_linearization(false),
99 show_linearization(false), 101 show_linearization(false),
100 show_xref(false), 102 show_xref(false),
@@ -120,6 +122,7 @@ struct Options @@ -120,6 +122,7 @@ struct Options
120 char const* encryption_file; 122 char const* encryption_file;
121 char const* encryption_file_password; 123 char const* encryption_file_password;
122 bool encrypt; 124 bool encrypt;
  125 + bool password_is_hex_key;
123 std::string user_password; 126 std::string user_password;
124 std::string owner_password; 127 std::string owner_password;
125 int keylen; 128 int keylen;
@@ -158,6 +161,7 @@ struct Options @@ -158,6 +161,7 @@ struct Options
158 bool static_aes_iv; 161 bool static_aes_iv;
159 bool suppress_original_object_id; 162 bool suppress_original_object_id;
160 bool show_encryption; 163 bool show_encryption;
  164 + bool show_encryption_key;
161 bool check_linearization; 165 bool check_linearization;
162 bool show_linearization; 166 bool show_linearization;
163 bool show_xref; 167 bool show_xref;
@@ -227,6 +231,7 @@ Basic Options\n\ @@ -227,6 +231,7 @@ Basic Options\n\
227 parameters are being copied\n\ 231 parameters are being copied\n\
228 --encrypt options -- generate an encrypted file\n\ 232 --encrypt options -- generate an encrypted file\n\
229 --decrypt remove any encryption on the file\n\ 233 --decrypt remove any encryption on the file\n\
  234 +--password-is-hex-key treat primary password option as a hex-encoded key\n\
230 --pages options -- select specific pages from one or more files\n\ 235 --pages options -- select specific pages from one or more files\n\
231 --rotate=[+|-]angle:page-range\n\ 236 --rotate=[+|-]angle:page-range\n\
232 rotate each specified page 90, 180, or 270 degrees\n\ 237 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\ @@ -240,6 +245,11 @@ parameters will be copied, including both user and owner passwords, even\n\
240 if the user password is used to open the other file. This works even if\n\ 245 if the user password is used to open the other file. This works even if\n\
241 the owner password is not known.\n\ 246 the owner password is not known.\n\
242 \n\ 247 \n\
  248 +The --password-is-hex-key option overrides the normal computation of\n\
  249 +encryption keys. It only applies to the password used to open the main\n\
  250 +file. This option is not ordinarily useful but can be helpful for forensic\n\
  251 +or investigatory purposes. See manual for further discussion.\n\
  252 +\n\
243 The --rotate flag can be used to specify pages to rotate pages either\n\ 253 The --rotate flag can be used to specify pages to rotate pages either\n\
244 90, 180, or 270 degrees. The page range is specified in the same\n\ 254 90, 180, or 270 degrees. The page range is specified in the same\n\
245 format as with the --pages option, described below. Repeat the option\n\ 255 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\ @@ -434,6 +444,7 @@ automated test suites for software that uses the qpdf library.\n\
434 This is option is not secure! FOR TESTING ONLY!\n\ 444 This is option is not secure! FOR TESTING ONLY!\n\
435 --no-original-object-ids suppress original object ID comments in qdf mode\n\ 445 --no-original-object-ids suppress original object ID comments in qdf mode\n\
436 --show-encryption quickly show encryption parameters\n\ 446 --show-encryption quickly show encryption parameters\n\
  447 +--show-encryption-key when showing encryption, reveal the actual key\n\
437 --check-linearization check file integrity and linearization status\n\ 448 --check-linearization check file integrity and linearization status\n\
438 --show-linearization check and show all linearization data\n\ 449 --show-linearization check and show all linearization data\n\
439 --show-xref show the contents of the cross-reference table\n\ 450 --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) @@ -501,7 +512,7 @@ static std::string show_encryption_method(QPDF::encryption_method_e method)
501 return result; 512 return result;
502 } 513 }
503 514
504 -static void show_encryption(QPDF& pdf) 515 +static void show_encryption(QPDF& pdf, Options& o)
505 { 516 {
506 // Extract /P from /Encrypt 517 // Extract /P from /Encrypt
507 int R = 0; 518 int R = 0;
@@ -520,8 +531,14 @@ static void show_encryption(QPDF&amp; pdf) @@ -520,8 +531,14 @@ static void show_encryption(QPDF&amp; pdf)
520 std::cout << "R = " << R << std::endl; 531 std::cout << "R = " << R << std::endl;
521 std::cout << "P = " << P << std::endl; 532 std::cout << "P = " << P << std::endl;
522 std::string user_password = pdf.getTrimmedUserPassword(); 533 std::string user_password = pdf.getTrimmedUserPassword();
523 - std::cout << "User password = " << user_password << std::endl  
524 - << "extract for accessibility: " 534 + std::string encryption_key = pdf.getEncryptionKey();
  535 + std::cout << "User password = " << user_password << std::endl;
  536 + if (o.show_encryption_key)
  537 + {
  538 + std::cout << "Encryption key = "
  539 + << QUtil::hex_encode(encryption_key) << std::endl;
  540 + }
  541 + std::cout << "extract for accessibility: "
525 << show_bool(pdf.allowAccessibility()) << std::endl 542 << show_bool(pdf.allowAccessibility()) << std::endl
526 << "extract for any purpose: " 543 << "extract for any purpose: "
527 << show_bool(pdf.allowExtractAll()) << std::endl 544 << show_bool(pdf.allowExtractAll()) << std::endl
@@ -1339,6 +1356,10 @@ static void parse_options(int argc, char* argv[], Options&amp; o) @@ -1339,6 +1356,10 @@ static void parse_options(int argc, char* argv[], Options&amp; o)
1339 o.encrypt = false; 1356 o.encrypt = false;
1340 o.copy_encryption = false; 1357 o.copy_encryption = false;
1341 } 1358 }
  1359 + else if (strcmp(arg, "password-is-hex-key") == 0)
  1360 + {
  1361 + o.password_is_hex_key = true;
  1362 + }
1342 else if (strcmp(arg, "copy-encryption") == 0) 1363 else if (strcmp(arg, "copy-encryption") == 0)
1343 { 1364 {
1344 if (parameter == 0) 1365 if (parameter == 0)
@@ -1559,6 +1580,10 @@ static void parse_options(int argc, char* argv[], Options&amp; o) @@ -1559,6 +1580,10 @@ static void parse_options(int argc, char* argv[], Options&amp; o)
1559 o.show_encryption = true; 1580 o.show_encryption = true;
1560 o.require_outfile = false; 1581 o.require_outfile = false;
1561 } 1582 }
  1583 + else if (strcmp(arg, "show-encryption-key") == 0)
  1584 + {
  1585 + o.show_encryption_key = true;
  1586 + }
1562 else if (strcmp(arg, "check-linearization") == 0) 1587 else if (strcmp(arg, "check-linearization") == 0)
1563 { 1588 {
1564 o.check_linearization = true; 1589 o.check_linearization = true;
@@ -1673,6 +1698,10 @@ static void set_qpdf_options(QPDF&amp; pdf, Options&amp; o) @@ -1673,6 +1698,10 @@ static void set_qpdf_options(QPDF&amp; pdf, Options&amp; o)
1673 { 1698 {
1674 pdf.setAttemptRecovery(false); 1699 pdf.setAttemptRecovery(false);
1675 } 1700 }
  1701 + if (o.password_is_hex_key)
  1702 + {
  1703 + pdf.setPasswordIsHexKey(true);
  1704 + }
1676 } 1705 }
1677 1706
1678 static void do_check(QPDF& pdf, Options& o, int& exit_code) 1707 static void do_check(QPDF& pdf, Options& o, int& exit_code)
@@ -1693,7 +1722,7 @@ static void do_check(QPDF&amp; pdf, Options&amp; o, int&amp; exit_code) @@ -1693,7 +1722,7 @@ static void do_check(QPDF&amp; pdf, Options&amp; o, int&amp; exit_code)
1693 << pdf.getExtensionLevel(); 1722 << pdf.getExtensionLevel();
1694 } 1723 }
1695 std::cout << std::endl; 1724 std::cout << std::endl;
1696 - show_encryption(pdf); 1725 + show_encryption(pdf, o);
1697 if (pdf.isLinearized()) 1726 if (pdf.isLinearized())
1698 { 1727 {
1699 std::cout << "File is linearized\n"; 1728 std::cout << "File is linearized\n";
@@ -1877,7 +1906,7 @@ static void do_inspection(QPDF&amp; pdf, Options&amp; o) @@ -1877,7 +1906,7 @@ static void do_inspection(QPDF&amp; pdf, Options&amp; o)
1877 } 1906 }
1878 if (o.show_encryption) 1907 if (o.show_encryption)
1879 { 1908 {
1880 - show_encryption(pdf); 1909 + show_encryption(pdf, o);
1881 } 1910 }
1882 if (o.check_linearization) 1911 if (o.check_linearization)
1883 { 1912 {
qpdf/qtest/qpdf.test
@@ -314,7 +314,7 @@ foreach my $file (qw(short-id long-id)) @@ -314,7 +314,7 @@ foreach my $file (qw(short-id long-id))
314 $td->NORMALIZE_NEWLINES); 314 $td->NORMALIZE_NEWLINES);
315 315
316 $td->runtest("check $file.pdf", 316 $td->runtest("check $file.pdf",
317 - {$td->COMMAND => "qpdf --check a.pdf"}, 317 + {$td->COMMAND => "qpdf --check --show-encryption-key a.pdf"},
318 {$td->FILE => "$file-check.out", 318 {$td->FILE => "$file-check.out",
319 $td->EXIT_STATUS => 0}, 319 $td->EXIT_STATUS => 0},
320 $td->NORMALIZE_NEWLINES); 320 $td->NORMALIZE_NEWLINES);
@@ -2244,6 +2244,21 @@ $td-&gt;runtest(&quot;copy of unfilterable with crypt&quot;, @@ -2244,6 +2244,21 @@ $td-&gt;runtest(&quot;copy of unfilterable with crypt&quot;,
2244 $td->EXIT_STATUS => 0}, 2244 $td->EXIT_STATUS => 0},
2245 $td->NORMALIZE_NEWLINES); 2245 $td->NORMALIZE_NEWLINES);
2246 2246
  2247 +# Raw encryption key
  2248 +my @enc_key = (['user', '--password=user3'],
  2249 + ['owner', '--password=owner3'],
  2250 + ['hex', '--password-is-hex-key --password=35ea16a48b6a3045133b69ac0906c2e8fb0a2cc97903ae17b51a5786ebdba020']);
  2251 +$n_tests += scalar(@enc_key);
  2252 +foreach my $d (@enc_key)
  2253 +{
  2254 + my ($description, $pass) = @$d;
  2255 + $td->runtest("use/show encryption key ($description)",
  2256 + {$td->COMMAND =>
  2257 + "qpdf --check --show-encryption-key c-r5-in.pdf $pass"},
  2258 + {$td->FILE => "c-r5-key-$description.out", $td->EXIT_STATUS => 0},
  2259 + $td->NORMALIZE_NEWLINES);
  2260 +}
  2261 +
2247 show_ntests(); 2262 show_ntests();
2248 # ---------- 2263 # ----------
2249 $td->notify("--- Content Preservation Tests ---"); 2264 $td->notify("--- Content Preservation Tests ---");
qpdf/qtest/qpdf/c-r5-key-hex.out 0 → 100644
  1 +checking c-r5-in.pdf
  2 +PDF Version: 1.7 extension level 3
  3 +R = 5
  4 +P = -2052
  5 +User password =
  6 +Encryption key = 35ea16a48b6a3045133b69ac0906c2e8fb0a2cc97903ae17b51a5786ebdba020
  7 +extract for accessibility: allowed
  8 +extract for any purpose: allowed
  9 +print low resolution: allowed
  10 +print high resolution: not allowed
  11 +modify document assembly: allowed
  12 +modify forms: allowed
  13 +modify annotations: allowed
  14 +modify other: allowed
  15 +modify anything: allowed
  16 +stream encryption method: AESv3
  17 +string encryption method: AESv3
  18 +file encryption method: AESv3
  19 +File is not linearized
  20 +No syntax or stream encoding errors found; the file may still contain
  21 +errors that qpdf cannot detect
qpdf/qtest/qpdf/c-r5-key-owner.out 0 → 100644
  1 +checking c-r5-in.pdf
  2 +PDF Version: 1.7 extension level 3
  3 +R = 5
  4 +P = -2052
  5 +User password =
  6 +Encryption key = 35ea16a48b6a3045133b69ac0906c2e8fb0a2cc97903ae17b51a5786ebdba020
  7 +extract for accessibility: allowed
  8 +extract for any purpose: allowed
  9 +print low resolution: allowed
  10 +print high resolution: not allowed
  11 +modify document assembly: allowed
  12 +modify forms: allowed
  13 +modify annotations: allowed
  14 +modify other: allowed
  15 +modify anything: allowed
  16 +stream encryption method: AESv3
  17 +string encryption method: AESv3
  18 +file encryption method: AESv3
  19 +File is not linearized
  20 +No syntax or stream encoding errors found; the file may still contain
  21 +errors that qpdf cannot detect
qpdf/qtest/qpdf/c-r5-key-user.out 0 → 100644
  1 +checking c-r5-in.pdf
  2 +PDF Version: 1.7 extension level 3
  3 +R = 5
  4 +P = -2052
  5 +User password = user3
  6 +Encryption key = 35ea16a48b6a3045133b69ac0906c2e8fb0a2cc97903ae17b51a5786ebdba020
  7 +extract for accessibility: allowed
  8 +extract for any purpose: allowed
  9 +print low resolution: allowed
  10 +print high resolution: not allowed
  11 +modify document assembly: allowed
  12 +modify forms: allowed
  13 +modify annotations: allowed
  14 +modify other: allowed
  15 +modify anything: allowed
  16 +stream encryption method: AESv3
  17 +string encryption method: AESv3
  18 +file encryption method: AESv3
  19 +File is not linearized
  20 +No syntax or stream encoding errors found; the file may still contain
  21 +errors that qpdf cannot detect
qpdf/qtest/qpdf/long-id-check.out
@@ -3,6 +3,7 @@ PDF Version: 1.3 @@ -3,6 +3,7 @@ PDF Version: 1.3
3 R = 2 3 R = 2
4 P = -4 4 P = -4
5 User password = 5 User password =
  6 +Encryption key = 2f382cf6e1
6 extract for accessibility: allowed 7 extract for accessibility: allowed
7 extract for any purpose: allowed 8 extract for any purpose: allowed
8 print low resolution: allowed 9 print low resolution: allowed
qpdf/qtest/qpdf/short-id-check.out
@@ -3,6 +3,7 @@ PDF Version: 1.3 @@ -3,6 +3,7 @@ PDF Version: 1.3
3 R = 2 3 R = 2
4 P = -4 4 P = -4
5 User password = 5 User password =
  6 +Encryption key = 897d768fbd
6 extract for accessibility: allowed 7 extract for accessibility: allowed
7 extract for any purpose: allowed 8 extract for any purpose: allowed
8 print low resolution: allowed 9 print low resolution: allowed