Commit 3aad28aed0726dd1147bfabbd2deb2e575aa80bb

Authored by Jay Berkenbilt
1 parent c543c1e4

Bug fix: honor encryption key length with R=3 (fixes #212)

libqpdf/QPDF_encryption.cc
@@ -183,7 +183,7 @@ truncate_password_V5(std::string const& password) @@ -183,7 +183,7 @@ truncate_password_V5(std::string const& password)
183 } 183 }
184 184
185 static void 185 static void
186 -iterate_md5_digest(MD5& md5, MD5::Digest& digest, int iterations) 186 +iterate_md5_digest(MD5& md5, MD5::Digest& digest, int iterations, int key_len)
187 { 187 {
188 md5.digest(digest); 188 md5.digest(digest);
189 189
@@ -191,7 +191,7 @@ iterate_md5_digest(MD5& md5, MD5::Digest& digest, int iterations) @@ -191,7 +191,7 @@ iterate_md5_digest(MD5& md5, MD5::Digest& digest, int iterations)
191 { 191 {
192 MD5 m; 192 MD5 m;
193 m.encodeDataIncrementally(reinterpret_cast<char*>(digest), 193 m.encodeDataIncrementally(reinterpret_cast<char*>(digest),
194 - sizeof(digest)); 194 + key_len);
195 m.digest(digest); 195 m.digest(digest);
196 } 196 }
197 } 197 }
@@ -437,7 +437,8 @@ QPDF::compute_encryption_key_from_password( @@ -437,7 +437,8 @@ QPDF::compute_encryption_key_from_password(
437 md5.encodeDataIncrementally(bytes, 4); 437 md5.encodeDataIncrementally(bytes, 4);
438 } 438 }
439 MD5::Digest digest; 439 MD5::Digest digest;
440 - iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0)); 440 + iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0),
  441 + data.getLengthBytes());
441 return std::string(reinterpret_cast<char*>(digest), 442 return std::string(reinterpret_cast<char*>(digest),
442 std::min(static_cast<int>(sizeof(digest)), 443 std::min(static_cast<int>(sizeof(digest)),
443 data.getLengthBytes())); 444 data.getLengthBytes()));
@@ -463,7 +464,8 @@ compute_O_rc4_key(std::string const&amp; user_password, @@ -463,7 +464,8 @@ compute_O_rc4_key(std::string const&amp; user_password,
463 md5.encodeDataIncrementally( 464 md5.encodeDataIncrementally(
464 pad_or_truncate_password_V4(password).c_str(), key_bytes); 465 pad_or_truncate_password_V4(password).c_str(), key_bytes);
465 MD5::Digest digest; 466 MD5::Digest digest;
466 - iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0)); 467 + iterate_md5_digest(md5, digest, ((data.getR() >= 3) ? 50 : 0),
  468 + data.getLengthBytes());
467 memcpy(key, digest, OU_key_bytes_V4); 469 memcpy(key, digest, OU_key_bytes_V4);
468 } 470 }
469 471
@@ -933,6 +935,11 @@ QPDF::initializeEncryption() @@ -933,6 +935,11 @@ QPDF::initializeEncryption()
933 if (encryption_dict.getKey("/Length").isInteger()) 935 if (encryption_dict.getKey("/Length").isInteger())
934 { 936 {
935 Length = encryption_dict.getKey("/Length").getIntValue(); 937 Length = encryption_dict.getKey("/Length").getIntValue();
  938 + if (R < 3)
  939 + {
  940 + // Force Length to 40 regardless of what the file says.
  941 + Length = 40;
  942 + }
936 if ((Length % 8) || (Length < 40) || (Length > 256)) 943 if ((Length % 8) || (Length < 40) || (Length > 256))
937 { 944 {
938 throw QPDFExc(qpdf_e_damaged_pdf, this->m->file->getName(), 945 throw QPDFExc(qpdf_e_damaged_pdf, this->m->file->getName(),
qpdf/qtest/qpdf.test
@@ -453,7 +453,7 @@ $td-&gt;runtest(&quot;check output&quot;, @@ -453,7 +453,7 @@ $td-&gt;runtest(&quot;check output&quot;,
453 show_ntests(); 453 show_ntests();
454 # ---------- 454 # ----------
455 $td->notify("--- ID and Encryption Parameter Issues ---"); 455 $td->notify("--- ID and Encryption Parameter Issues ---");
456 -$n_tests += 12; 456 +$n_tests += 13;
457 457
458 # Encrypt files whose /ID strings are other than 32 bytes long (bug 458 # Encrypt files whose /ID strings are other than 32 bytes long (bug
459 # 2991412). Also linearize these files, which was reported in a 459 # 2991412). Also linearize these files, which was reported in a
@@ -509,6 +509,19 @@ $td-&gt;runtest(&quot;short /O or /U&quot;, @@ -509,6 +509,19 @@ $td-&gt;runtest(&quot;short /O or /U&quot;,
509 $td->EXIT_STATUS => 0}, 509 $td->EXIT_STATUS => 0},
510 $td->NORMALIZE_NEWLINES); 510 $td->NORMALIZE_NEWLINES);
511 511
  512 +# A file was sent to me privately as part of issue 212. This file was
  513 +# encrypted and had /R=3 and /V=1 and was using a 40-bit key. qpdf was
  514 +# failing to work properly on files with /R=3 and 40-bit keys. The
  515 +# test file is not this private file, but the encryption parameters
  516 +# were copied from it. Like the bug file, qpdf < 8.1 can't decrypt it.
  517 +$td->runtest("/R 3 with 40-bit key",
  518 + {$td->COMMAND =>
  519 + "qpdf --password=623 --check --show-encryption-key" .
  520 + " encrypted-40-bit-R3.pdf"},
  521 + {$td->FILE => "encrypted-40-bit-R3.out",
  522 + $td->EXIT_STATUS => 0},
  523 + $td->NORMALIZE_NEWLINES);
  524 +
512 show_ntests(); 525 show_ntests();
513 # ---------- 526 # ----------
514 $td->notify("--- Min/force version ---"); 527 $td->notify("--- Min/force version ---");
qpdf/qtest/qpdf/encrypted-40-bit-R3.out 0 → 100644
  1 +checking encrypted-40-bit-R3.pdf
  2 +PDF Version: 1.4
  3 +R = 3
  4 +P = -12
  5 +User password = 623
  6 +Encryption key = e390e220da
  7 +extract for accessibility: allowed
  8 +extract for any purpose: allowed
  9 +print low resolution: allowed
  10 +print high resolution: allowed
  11 +modify document assembly: allowed
  12 +modify forms: allowed
  13 +modify annotations: allowed
  14 +modify other: not allowed
  15 +modify anything: not allowed
  16 +File is not linearized
  17 +No syntax or stream encoding errors found; the file may still contain
  18 +errors that qpdf cannot detect
qpdf/qtest/qpdf/encrypted-40-bit-R3.pdf 0 → 100644
No preview for this file type