Commit 8ccd3a8a89d95ae0613679ba7b394a4f87699e12

Authored by Jay Berkenbilt
1 parent 2213ed0c

Mark weak encryption with API changes (fixes #576)

ChangeLog
1 2022-04-30 Jay Berkenbilt <ejb@ql.org> 1 2022-04-30 Jay Berkenbilt <ejb@ql.org>
2 2
  3 + * QPDFWriter: change encryption API calls
  4 + - Remove deprecated versions of setR*EncryptionParameters
  5 + methods from before qpdf 8.4.0
  6 + - Replace setR2EncryptionParameters with
  7 + setR2EncryptionParametersInsecure
  8 + - Replace setR3EncryptionParameters with
  9 + setR3EncryptionParametersInsecure
  10 + - Replace setR4EncryptionParameters with
  11 + setR4EncryptionParametersInsecure
  12 +
  13 + * C API: change encryption API calls to match C++ interface
  14 + - Remove pre-8.4.0 functions:
  15 + - qpdf_set_r3_encryption_parameters
  16 + - qpdf_set_r4_encryption_parameters
  17 + - qpdf_set_r5_encryption_parameters
  18 + - qpdf_set_r6_encryption_parameters
  19 + - Add "_insecure" to insecure encryption triggers:
  20 + - Replace void qpdf_set_r2_encryption_parameters
  21 + with qpdf_set_r2_encryption_parameters_insecure
  22 + - Replace void qpdf_set_r3_encryption_parameters2
  23 + with qpdf_set_r3_encryption_parameters_insecure
  24 + - Replace void qpdf_set_r4_encryption_parameters2
  25 + with qpdf_set_r4_encryption_parameters_insecure
  26 +
3 * Make attempting to write encrypted files that use RC4 (40-bit or 27 * Make attempting to write encrypted files that use RC4 (40-bit or
4 128-bit without AES) an error rather than a warning when 28 128-bit without AES) an error rather than a warning when
5 - --allow-weak-crypto is not specified. 29 + --allow-weak-crypto is not specified. Fixes #576.
6 30
7 2022-04-29 Jay Berkenbilt <ejb@ql.org> 31 2022-04-29 Jay Berkenbilt <ejb@ql.org>
8 32
@@ -475,18 +475,6 @@ Comments appear in the code prefixed by &quot;ABI&quot;. Always Search for ABI @@ -475,18 +475,6 @@ Comments appear in the code prefixed by &quot;ABI&quot;. Always Search for ABI
475 in source and header files to find items not listed here. Also search 475 in source and header files to find items not listed here. Also search
476 for "[[deprecated" to find deprecated APIs that can be removed. 476 for "[[deprecated" to find deprecated APIs that can be removed.
477 477
478 -* Deal with weak cryptographic algorithms:  
479 - * Github issue #576  
480 - * Add something to QPDFWriter that you must call in order to allow  
481 - creation of files with insecure crypto. Maybe  
482 - QPDFWriter::allowWeakCrypto. Call this when --allow-weak-crypto is  
483 - passed and probably also when copying encryption by default from  
484 - an input file. There should be some API change so that, when  
485 - people recompile with qpdf 11, their code won't suddenly stop  
486 - working. Getting this right will take careful consideration of the  
487 - developer and user experience. We don't want to create a situation  
488 - where exactly the same code fails to work in 11 but worked on 10.  
489 - See #576 for latest notes.  
490 478
491 Page splitting/merging 479 Page splitting/merging
492 ====================== 480 ======================
fuzz/qpdf_fuzzer.cc
@@ -107,7 +107,7 @@ FuzzHelper::testWrite() @@ -107,7 +107,7 @@ FuzzHelper::testWrite()
107 w = getWriter(q); 107 w = getWriter(q);
108 w->setStaticID(true); 108 w->setStaticID(true);
109 w->setObjectStreamMode(qpdf_o_disable); 109 w->setObjectStreamMode(qpdf_o_disable);
110 - w->setR3EncryptionParameters( 110 + w->setR3EncryptionParametersInsecure(
111 "u", "o", true, true, true, true, true, true, qpdf_r3p_full); 111 "u", "o", true, true, true, true, true, true, qpdf_r3p_full);
112 doWrite(w); 112 doWrite(w);
113 113
include/qpdf/QPDFWriter.hh
@@ -366,10 +366,12 @@ class QPDFWriter @@ -366,10 +366,12 @@ class QPDFWriter
366 // functions that could be useful to you, most notably 366 // functions that could be useful to you, most notably
367 // utf8_to_pdf_doc. 367 // utf8_to_pdf_doc.
368 368
369 - // R3 uses RC4, which is a weak cryptographic algorithm. Don't use  
370 - // it unless you have to. 369 + // R2 uses RC4, which is a weak cryptographic algorithm. Don't use
  370 + // it unless you have to. See "Weak Cryptography" in the manual.
  371 + // This encryption format is deprecated in the PDF 2.0
  372 + // specification.
371 QPDF_DLL 373 QPDF_DLL
372 - void setR2EncryptionParameters( 374 + void setR2EncryptionParametersInsecure(
373 char const* user_password, 375 char const* user_password,
374 char const* owner_password, 376 char const* owner_password,
375 bool allow_print, 377 bool allow_print,
@@ -377,9 +379,11 @@ class QPDFWriter @@ -377,9 +379,11 @@ class QPDFWriter
377 bool allow_extract, 379 bool allow_extract,
378 bool allow_annotate); 380 bool allow_annotate);
379 // R3 uses RC4, which is a weak cryptographic algorithm. Don't use 381 // R3 uses RC4, which is a weak cryptographic algorithm. Don't use
380 - // it unless you have to. 382 + // it unless you have to. See "Weak Cryptography" in the manual.
  383 + // This encryption format is deprecated in the PDF 2.0
  384 + // specification.
381 QPDF_DLL 385 QPDF_DLL
382 - void setR3EncryptionParameters( 386 + void setR3EncryptionParametersInsecure(
383 char const* user_password, 387 char const* user_password,
384 char const* owner_password, 388 char const* owner_password,
385 bool allow_accessibility, 389 bool allow_accessibility,
@@ -389,10 +393,13 @@ class QPDFWriter @@ -389,10 +393,13 @@ class QPDFWriter
389 bool allow_form_filling, 393 bool allow_form_filling,
390 bool allow_modify_other, 394 bool allow_modify_other,
391 qpdf_r3_print_e print); 395 qpdf_r3_print_e print);
392 - // R4 uses RC4, which is a weak cryptographic algorithm, when  
393 - // use_aes=false. Don't use it unless you have to. 396 + // When use_aes=false, this call enables R4 with RC4, which is a
  397 + // weak cryptographic algorithm. Even with use_aes=true, the
  398 + // overall encryption scheme is weak. Don't use it unless you have
  399 + // to. See "Weak Cryptography" in the manual. This encryption
  400 + // format is deprecated in the PDF 2.0 specification.
394 QPDF_DLL 401 QPDF_DLL
395 - void setR4EncryptionParameters( 402 + void setR4EncryptionParametersInsecure(
396 char const* user_password, 403 char const* user_password,
397 char const* owner_password, 404 char const* owner_password,
398 bool allow_accessibility, 405 bool allow_accessibility,
@@ -419,6 +426,8 @@ class QPDFWriter @@ -419,6 +426,8 @@ class QPDFWriter
419 bool allow_modify_other, 426 bool allow_modify_other,
420 qpdf_r3_print_e print, 427 qpdf_r3_print_e print,
421 bool encrypt_metadata); 428 bool encrypt_metadata);
  429 + // This is the only password-based encryption format supported by
  430 + // the PDF specification.
422 QPDF_DLL 431 QPDF_DLL
423 void setR6EncryptionParameters( 432 void setR6EncryptionParameters(
424 char const* user_password, 433 char const* user_password,
include/qpdf/qpdf-c.h
@@ -458,8 +458,13 @@ extern &quot;C&quot; { @@ -458,8 +458,13 @@ extern &quot;C&quot; {
458 QPDF_DLL 458 QPDF_DLL
459 void qpdf_set_preserve_encryption(qpdf_data qpdf, QPDF_BOOL value); 459 void qpdf_set_preserve_encryption(qpdf_data qpdf, QPDF_BOOL value);
460 460
  461 + /* The *_insecure functions are identical to the old versions but
  462 + * have been renamed as a an alert to the caller that they are
  463 + * insecure. See "Weak Cryptographic" in the manual for
  464 + * details.
  465 + */
461 QPDF_DLL 466 QPDF_DLL
462 - void qpdf_set_r2_encryption_parameters( 467 + void qpdf_set_r2_encryption_parameters_insecure(
463 qpdf_data qpdf, 468 qpdf_data qpdf,
464 char const* user_password, 469 char const* user_password,
465 char const* owner_password, 470 char const* owner_password,
@@ -469,7 +474,7 @@ extern &quot;C&quot; { @@ -469,7 +474,7 @@ extern &quot;C&quot; {
469 QPDF_BOOL allow_annotate); 474 QPDF_BOOL allow_annotate);
470 475
471 QPDF_DLL 476 QPDF_DLL
472 - void qpdf_set_r3_encryption_parameters2( 477 + void qpdf_set_r3_encryption_parameters_insecure(
473 qpdf_data qpdf, 478 qpdf_data qpdf,
474 char const* user_password, 479 char const* user_password,
475 char const* owner_password, 480 char const* owner_password,
@@ -482,7 +487,7 @@ extern &quot;C&quot; { @@ -482,7 +487,7 @@ extern &quot;C&quot; {
482 enum qpdf_r3_print_e print); 487 enum qpdf_r3_print_e print);
483 488
484 QPDF_DLL 489 QPDF_DLL
485 - void qpdf_set_r4_encryption_parameters2( 490 + void qpdf_set_r4_encryption_parameters_insecure(
486 qpdf_data qpdf, 491 qpdf_data qpdf,
487 char const* user_password, 492 char const* user_password,
488 char const* owner_password, 493 char const* owner_password,
job.sums
@@ -14,4 +14,4 @@ libqpdf/qpdf/auto_job_json_decl.hh 06caa46eaf71db8a50c046f91866baa8087745a947431 @@ -14,4 +14,4 @@ libqpdf/qpdf/auto_job_json_decl.hh 06caa46eaf71db8a50c046f91866baa8087745a947431
14 libqpdf/qpdf/auto_job_json_init.hh 06d51f11c117011256e175386eee9946441f3c22b49dd91fc591bbc1fa3bbeec 14 libqpdf/qpdf/auto_job_json_init.hh 06d51f11c117011256e175386eee9946441f3c22b49dd91fc591bbc1fa3bbeec
15 libqpdf/qpdf/auto_job_schema.hh 43273b9edfc48b1f4cccbff1d2b31916a9057c474ef97d2936b2f1f14170885b 15 libqpdf/qpdf/auto_job_schema.hh 43273b9edfc48b1f4cccbff1d2b31916a9057c474ef97d2936b2f1f14170885b
16 manual/_ext/qpdf.py e9ac9d6c70642a3d29281ee5ad92ae2422dee8be9306fb8a0bc9dba0ed5e28f3 16 manual/_ext/qpdf.py e9ac9d6c70642a3d29281ee5ad92ae2422dee8be9306fb8a0bc9dba0ed5e28f3
17 -manual/cli.rst 6a2d99acedbd207370a8dc2807f6657323c42bccbe51ebdc6bc2d00f6851219c 17 +manual/cli.rst 70258db13d89b0476248e9703bf5f50ffe28fce2a179dfeca241582dd28b455c
libqpdf/QPDFJob.cc
@@ -2815,19 +2815,22 @@ QPDFJob::setEncryptionOptions(QPDF&amp; pdf, QPDFWriter&amp; w) @@ -2815,19 +2815,22 @@ QPDFJob::setEncryptionOptions(QPDF&amp; pdf, QPDFWriter&amp; w)
2815 QTC::TC("qpdf", "QPDFJob weak crypto error"); 2815 QTC::TC("qpdf", "QPDFJob weak crypto error");
2816 *(this->m->cerr) 2816 *(this->m->cerr)
2817 << this->m->message_prefix 2817 << this->m->message_prefix
2818 - << ": refusing to write a file with RC4, a weak cryptographic algorithm" 2818 + << ": refusing to write a file with RC4, a weak cryptographic "
  2819 + "algorithm"
2819 << std::endl 2820 << std::endl
2820 << "Please use 256-bit keys for better security." << std::endl 2821 << "Please use 256-bit keys for better security." << std::endl
2821 << "Pass --allow-weak-crypto to enable writing insecure files." 2822 << "Pass --allow-weak-crypto to enable writing insecure files."
2822 << std::endl 2823 << std::endl
2823 - << "See also https://qpdf.readthedocs.io/en/stable/weak-crypto.html" 2824 + << "See also "
  2825 + "https://qpdf.readthedocs.io/en/stable/weak-crypto.html"
2824 << std::endl; 2826 << std::endl;
2825 - throw std::runtime_error("refusing to write a file with weak crypto"); 2827 + throw std::runtime_error(
  2828 + "refusing to write a file with weak crypto");
2826 } 2829 }
2827 } 2830 }
2828 switch (R) { 2831 switch (R) {
2829 case 2: 2832 case 2:
2830 - w.setR2EncryptionParameters( 2833 + w.setR2EncryptionParametersInsecure(
2831 m->user_password.c_str(), 2834 m->user_password.c_str(),
2832 m->owner_password.c_str(), 2835 m->owner_password.c_str(),
2833 m->r2_print, 2836 m->r2_print,
@@ -2836,7 +2839,7 @@ QPDFJob::setEncryptionOptions(QPDF&amp; pdf, QPDFWriter&amp; w) @@ -2836,7 +2839,7 @@ QPDFJob::setEncryptionOptions(QPDF&amp; pdf, QPDFWriter&amp; w)
2836 m->r2_annotate); 2839 m->r2_annotate);
2837 break; 2840 break;
2838 case 3: 2841 case 3:
2839 - w.setR3EncryptionParameters( 2842 + w.setR3EncryptionParametersInsecure(
2840 m->user_password.c_str(), 2843 m->user_password.c_str(),
2841 m->owner_password.c_str(), 2844 m->owner_password.c_str(),
2842 m->r3_accessibility, 2845 m->r3_accessibility,
@@ -2848,7 +2851,7 @@ QPDFJob::setEncryptionOptions(QPDF&amp; pdf, QPDFWriter&amp; w) @@ -2848,7 +2851,7 @@ QPDFJob::setEncryptionOptions(QPDF&amp; pdf, QPDFWriter&amp; w)
2848 m->r3_print); 2851 m->r3_print);
2849 break; 2852 break;
2850 case 4: 2853 case 4:
2851 - w.setR4EncryptionParameters( 2854 + w.setR4EncryptionParametersInsecure(
2852 m->user_password.c_str(), 2855 m->user_password.c_str(),
2853 m->owner_password.c_str(), 2856 m->owner_password.c_str(),
2854 m->r3_accessibility, 2857 m->r3_accessibility,
libqpdf/QPDFWriter.cc
@@ -365,7 +365,7 @@ QPDFWriter::setPCLm(bool val) @@ -365,7 +365,7 @@ QPDFWriter::setPCLm(bool val)
365 } 365 }
366 366
367 void 367 void
368 -QPDFWriter::setR2EncryptionParameters( 368 +QPDFWriter::setR2EncryptionParametersInsecure(
369 char const* user_password, 369 char const* user_password,
370 char const* owner_password, 370 char const* owner_password,
371 bool allow_print, 371 bool allow_print,
@@ -391,7 +391,7 @@ QPDFWriter::setR2EncryptionParameters( @@ -391,7 +391,7 @@ QPDFWriter::setR2EncryptionParameters(
391 } 391 }
392 392
393 void 393 void
394 -QPDFWriter::setR3EncryptionParameters( 394 +QPDFWriter::setR3EncryptionParametersInsecure(
395 char const* user_password, 395 char const* user_password,
396 char const* owner_password, 396 char const* owner_password,
397 bool allow_accessibility, 397 bool allow_accessibility,
@@ -419,7 +419,7 @@ QPDFWriter::setR3EncryptionParameters( @@ -419,7 +419,7 @@ QPDFWriter::setR3EncryptionParameters(
419 } 419 }
420 420
421 void 421 void
422 -QPDFWriter::setR4EncryptionParameters( 422 +QPDFWriter::setR4EncryptionParametersInsecure(
423 char const* user_password, 423 char const* user_password,
424 char const* owner_password, 424 char const* owner_password,
425 bool allow_accessibility, 425 bool allow_accessibility,
libqpdf/qpdf-c.cc
@@ -674,7 +674,7 @@ qpdf_set_preserve_encryption(qpdf_data qpdf, QPDF_BOOL value) @@ -674,7 +674,7 @@ qpdf_set_preserve_encryption(qpdf_data qpdf, QPDF_BOOL value)
674 } 674 }
675 675
676 void 676 void
677 -qpdf_set_r2_encryption_parameters( 677 +qpdf_set_r2_encryption_parameters_insecure(
678 qpdf_data qpdf, 678 qpdf_data qpdf,
679 char const* user_password, 679 char const* user_password,
680 char const* owner_password, 680 char const* owner_password,
@@ -683,8 +683,8 @@ qpdf_set_r2_encryption_parameters( @@ -683,8 +683,8 @@ qpdf_set_r2_encryption_parameters(
683 QPDF_BOOL allow_extract, 683 QPDF_BOOL allow_extract,
684 QPDF_BOOL allow_annotate) 684 QPDF_BOOL allow_annotate)
685 { 685 {
686 - QTC::TC("qpdf", "qpdf-c called qpdf_set_r2_encryption_parameters");  
687 - qpdf->qpdf_writer->setR2EncryptionParameters( 686 + QTC::TC("qpdf", "qpdf-c called qpdf_set_r2_encryption_parameters_insecure");
  687 + qpdf->qpdf_writer->setR2EncryptionParametersInsecure(
688 user_password, 688 user_password,
689 owner_password, 689 owner_password,
690 allow_print != QPDF_FALSE, 690 allow_print != QPDF_FALSE,
@@ -694,7 +694,7 @@ qpdf_set_r2_encryption_parameters( @@ -694,7 +694,7 @@ qpdf_set_r2_encryption_parameters(
694 } 694 }
695 695
696 void 696 void
697 -qpdf_set_r3_encryption_parameters2( 697 +qpdf_set_r3_encryption_parameters_insecure(
698 qpdf_data qpdf, 698 qpdf_data qpdf,
699 char const* user_password, 699 char const* user_password,
700 char const* owner_password, 700 char const* owner_password,
@@ -706,8 +706,8 @@ qpdf_set_r3_encryption_parameters2( @@ -706,8 +706,8 @@ qpdf_set_r3_encryption_parameters2(
706 QPDF_BOOL allow_modify_other, 706 QPDF_BOOL allow_modify_other,
707 enum qpdf_r3_print_e print) 707 enum qpdf_r3_print_e print)
708 { 708 {
709 - QTC::TC("qpdf", "qpdf-c called qpdf_set_r3_encryption_parameters");  
710 - qpdf->qpdf_writer->setR3EncryptionParameters( 709 + QTC::TC("qpdf", "qpdf-c called qpdf_set_r3_encryption_parameters_insecure");
  710 + qpdf->qpdf_writer->setR3EncryptionParametersInsecure(
711 user_password, 711 user_password,
712 owner_password, 712 owner_password,
713 allow_accessibility != QPDF_FALSE, 713 allow_accessibility != QPDF_FALSE,
@@ -720,7 +720,7 @@ qpdf_set_r3_encryption_parameters2( @@ -720,7 +720,7 @@ qpdf_set_r3_encryption_parameters2(
720 } 720 }
721 721
722 void 722 void
723 -qpdf_set_r4_encryption_parameters2( 723 +qpdf_set_r4_encryption_parameters_insecure(
724 qpdf_data qpdf, 724 qpdf_data qpdf,
725 char const* user_password, 725 char const* user_password,
726 char const* owner_password, 726 char const* owner_password,
@@ -734,8 +734,8 @@ qpdf_set_r4_encryption_parameters2( @@ -734,8 +734,8 @@ qpdf_set_r4_encryption_parameters2(
734 QPDF_BOOL encrypt_metadata, 734 QPDF_BOOL encrypt_metadata,
735 QPDF_BOOL use_aes) 735 QPDF_BOOL use_aes)
736 { 736 {
737 - QTC::TC("qpdf", "qpdf-c called qpdf_set_r4_encryption_parameters");  
738 - qpdf->qpdf_writer->setR4EncryptionParameters( 737 + QTC::TC("qpdf", "qpdf-c called qpdf_set_r4_encryption_parameters_insecure");
  738 + qpdf->qpdf_writer->setR4EncryptionParametersInsecure(
739 user_password, 739 user_password,
740 owner_password, 740 owner_password,
741 allow_accessibility != QPDF_FALSE, 741 allow_accessibility != QPDF_FALSE,
manual/cli.rst
@@ -470,12 +470,18 @@ Related Options @@ -470,12 +470,18 @@ Related Options
470 option is necessary to create 40-bit files or 128-bit files that 470 option is necessary to create 40-bit files or 128-bit files that
471 use RC4 encryption. 471 use RC4 encryption.
472 472
473 - Starting with version 10.4, qpdf issues warnings when requested to  
474 - create files using RC4 encryption. This option suppresses those  
475 - warnings. In future versions of qpdf, qpdf will refuse to create  
476 - files with weak cryptography when this flag is not given. See 473 + Encrypted PDF files using 40-bit keys or 128-bit keys without AES
  474 + use the insecure *RC4* encryption algorithm. Starting with version
  475 + 11.0, qpdf's default behavior is to refuse to write files using RC4
  476 + encryption. Use this option to allow creation of such files. In
  477 + versions 10.4 through 10.6, attempting to create weak encrypted
  478 + files was a warning, rather than an error, without this flag. See
477 :ref:`weak-crypto` for additional details. 479 :ref:`weak-crypto` for additional details.
478 480
  481 + No check is performed for weak crypto when preserving encryption
  482 + parameters from or copying encryption parameters from other files.
  483 + The rationale for this is discussed in :ref:`weak-crypto`.
  484 +
479 .. qpdf:option:: --keep-files-open=[y|n] 485 .. qpdf:option:: --keep-files-open=[y|n]
480 486
481 .. help: manage keeping multiple files open 487 .. help: manage keeping multiple files open
@@ -741,6 +747,9 @@ Related Options @@ -741,6 +747,9 @@ Related Options
741 file without having to manual specify all the individual settings. 747 file without having to manual specify all the individual settings.
742 See also :qpdf:ref:`--decrypt`. 748 See also :qpdf:ref:`--decrypt`.
743 749
  750 + Checks for weak cryptographic algorithms are intentionally not made
  751 + by this operation. See :ref:`weak-crypto` for the rationale.
  752 +
744 .. qpdf:option:: --encryption-file-password=password 753 .. qpdf:option:: --encryption-file-password=password
745 754
746 .. help: supply password for --copy-encryption 755 .. help: supply password for --copy-encryption
manual/release-notes.rst
@@ -62,6 +62,10 @@ For a detailed list of changes, please see the file @@ -62,6 +62,10 @@ For a detailed list of changes, please see the file
62 - The default json output version when :qpdf:ref:`--json` is 62 - The default json output version when :qpdf:ref:`--json` is
63 specified has been changed from ``1`` to ``latest``. 63 specified has been changed from ``1`` to ``latest``.
64 64
  65 + - The :qpdf:ref:`--allow-weak-crypto` flag is now mandatory when
  66 + explicitly creating files with weak cryptographic algorithms.
  67 + See :ref:`weak-crypto` for a discussion.
  68 +
65 - API: breaking changes 69 - API: breaking changes
66 70
67 - Remove 71 - Remove
@@ -73,6 +77,19 @@ For a detailed list of changes, please see the file @@ -73,6 +77,19 @@ For a detailed list of changes, please see the file
73 ``QPDFNumberTreeObjectHelper`` constructors that don't take a 77 ``QPDFNumberTreeObjectHelper`` constructors that don't take a
74 ``QPDF&`` argument. 78 ``QPDF&`` argument.
75 79
  80 + - Intentionally break API to call attention to operations that
  81 + write files with insecure encryption:
  82 +
  83 + - Remove pre qpdf-8.4.0 encryption API methods from ``QPDFWriter``
  84 + and their corresponding C API functions
  85 +
  86 + - Add ``Insecure`` to the names of some ``QPDFWriter`` methods
  87 + and ``_insecure`` to the names of some C API functions without
  88 + otherwise changing their behavior
  89 +
  90 + - See :ref:`breaking-crypto-api` for specific details, and see
  91 + :ref:`weak-crypto` for a general discussion.
  92 +
76 - Library Enhancements 93 - Library Enhancements
77 94
78 - Support for more fluent programming with ``QPDFObjectHandle``. 95 - Support for more fluent programming with ``QPDFObjectHandle``.
manual/weak-crypto.rst
@@ -3,24 +3,55 @@ @@ -3,24 +3,55 @@
3 Weak Cryptography 3 Weak Cryptography
4 ================= 4 =================
5 5
6 -Start with version 10.4, qpdf is taking steps to reduce the likelihood  
7 -of a user *accidentally* creating PDF files with insecure cryptography  
8 -but will continue to allow creation of such files indefinitely with  
9 -explicit acknowledgment.  
10 -  
11 -The PDF file format makes use of RC4, which is known to be a weak  
12 -cryptography algorithm, and MD5, which is a weak hashing algorithm. In  
13 -version 10.4, qpdf generates warnings for some (but not all) cases of  
14 -writing files with weak cryptography when invoked from the command-line.  
15 -These warnings can be suppressed using the  
16 -:qpdf:ref:`--allow-weak-crypto` option. 6 +For help with compiler errors in qpdf 11.0 or newer, see
  7 +:ref:`breaking-crypto-api`.
  8 +
  9 +Since 2006, the PDF specification has offered ways to create encrypted
  10 +PDF files without using weak cryptography, though it took a few years
  11 +for many PDF readers and writers to catch up. It is still necessary to
  12 +support weak encryption algorithms to read encrypted PDF files that
  13 +were created using weak encryption algorithms, including all PDF files
  14 +created before the modern formats were introduced or widely supported.
  15 +
  16 +Starting with version 10.4, qpdf began taking steps to reduce the
  17 +likelihood of a user *accidentally* creating PDF files with insecure
  18 +cryptography but will continue to allow creation of such files
  19 +indefinitely with explicit acknowledgment. The restrictions on use of
  20 +weak cryptography were made stricter with qpdf 11.
  21 +
  22 +Definition of Weak Cryptographic Algorithm
  23 +------------------------------------------
  24 +
  25 +We divide weak cryptographic algorithms into two categories: weak
  26 +encryption and weak hashing. Encryption is encoding data such that a
  27 +key of some sort is required to decode it. Hashing is creating a short
  28 +value from data in such a way that it is extremely improbable to find
  29 +two documents with the same hash (known has a hash collision) and
  30 +extremely difficult to intentionally create a document with a specific
  31 +hash or two documents with the same hash.
17 32
18 -It is planned for qpdf version 11 to be stricter, making it an error to  
19 -write files with insecure cryptography from the command-line tool in  
20 -most cases without specifying the  
21 -:qpdf:ref:`--allow-weak-crypto` flag and also to require  
22 -explicit steps when using the C++ library to enable use of insecure  
23 -cryptography. 33 +When we say that an encryption algorithm is weak, we either mean that
  34 +a mathematical flaw has been discovered that makes it inherently
  35 +crackable or that it is sufficiently simple that modern computer
  36 +technology makes it possible to use "brute force" to crack. For
  37 +example, when 40-bit keys were originally introduced, it wasn't
  38 +practical to consider trying all possible keys, but today such a thing
  39 +is possible.
  40 +
  41 +When we say that a hashing algorithm is weak, we mean that, either
  42 +because of mathematical flaw or insufficient complexity, it is
  43 +computationally feasible to intentionally construct a hash collision.
  44 +
  45 +While weak encryption should always be avoided, there are cases in
  46 +which it is safe to use a weak hashing algorithm when security is not
  47 +a factor. For example, a weak hashing algorithm should not be used as
  48 +the only mechanism to test whether a file has been tampered with. In
  49 +other words, you can't use a weak hash as a digital signature. There
  50 +is no harm, however, in using a weak hash as a way to sort or index
  51 +documents as long as hash collisions are tolerated. It is also common
  52 +to use weak hashes as checksums, which are often used a check that a
  53 +file wasn't damanged in transit or storage, though for true integrity,
  54 +a strong hash would be better.
24 55
25 Note that qpdf must always retain support for weak cryptographic 56 Note that qpdf must always retain support for weak cryptographic
26 algorithms since this is required for reading older PDF files that use 57 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 @@ -31,3 +62,135 @@ since these are sometimes needed to test or work with older versions of
31 software. Even if other cryptography libraries drop support for RC4 or 62 software. Even if other cryptography libraries drop support for RC4 or
32 MD5, qpdf can always fall back to its internal implementations of those 63 MD5, qpdf can always fall back to its internal implementations of those
33 algorithms, so they are not going to disappear from qpdf. 64 algorithms, so they are not going to disappear from qpdf.
  65 +
  66 +Uses of Weak Encryption in qpdf
  67 +---------------------------------
  68 +
  69 +When PDF files are encrypted using 40-bit encryption or 128-bit
  70 +encryption without AES, then the weak *RC4* algorithm is used. You can
  71 +avoid using weak encryption in qpdf by always using 256-bit
  72 +encryption. Unless you are trying to create files that need to be
  73 +opened with PDF readers from before about 2010 (by which time most
  74 +readers had added support for the stronger encryption algorithms) or
  75 +are creating insecure files explicitly for testing or some similar
  76 +purpose, there is no reason to use anything other than 256-bit
  77 +encryption.
  78 +
  79 +By default, qpdf refuses to write a file that uses weak encryption.
  80 +You can explicitly allow this by specifying the
  81 +:qpdf:ref:`--allow-weak-crypto` option.
  82 +
  83 +In qpdf 11, all library methods that could potentially cause files to
  84 +be written with weak encryption were deprecated, and methods to enable
  85 +weak encryption were either given explicit names indicating this or
  86 +take required arguments to enable the insecure behavior.
  87 +
  88 +There is one exception: when encryption parameters are copied from the
  89 +input file or another file to the output file, there is no prohibition
  90 +or even warning against using insecure encryption. The reason is that
  91 +many qpdf operations simply preserve whatever encryption is there, and
  92 +requiring confirmation to *preserve* insecure encryption would cause
  93 +qpdf to break when non-encryption-related operations were performed on
  94 +files that happened to be encrypted. Failing or generating warnings in
  95 +this case would likely have the effect of making people use the
  96 +:qpdf:ref:`--allow-weak-crypto` option blindly, which would be worse
  97 +than just letting those files go so that explicit, conscious selection
  98 +of weak crypto would be more likely to be noticed. Why, you might ask,
  99 +does this apply to :qpdf:ref:`--copy-encryption` as well as to the
  100 +default behavior preserving encryption? The answer is that
  101 +:qpdf:ref:`--copy-encryption` works with an unencrypted file as input,
  102 +which enables workflows where one may start with a file, decrypt it
  103 +*just in case*, perform a series of operations, and then reapply the
  104 +original encryption, *if any*. Also, one may have a template used for
  105 +encryption that one may apply to a variety of output files, and it
  106 +would be annoying to be warned about it for every output file.
  107 +
  108 +Uses of Weak Hashing In QPDF
  109 +----------------------------
  110 +
  111 +The PDF specification makes use the weak *MD5* hashing algorithm in
  112 +several places. While it is used in the encryption algorithms,
  113 +breaking MD5 would not be adequate to crack an encrypted file when
  114 +256-bit encryption is in use, so using 256-bit encryption is adequate
  115 +for avoiding the use of MD5 for anything security-sensitive.
  116 +
  117 +MD5 is used in the following non-security-sensitive ways:
  118 +
  119 +- Generation of the document ID. The document ID is an input parameter
  120 + to the document encryption but is not itself considered to be
  121 + secure. They are supposed to be unique, but they are not
  122 + tamper-resistent in non-encrypted PDF files, and hash collisions
  123 + must be tolerated.
  124 +
  125 + The PDF specification recommends but does not require the use of MD5
  126 + in generation of document IDs. Usually there is also a random
  127 + component to document ID generation. There is a qpdf-specific
  128 + feature of generating a *deterministic ID* (see
  129 + :qpdf:ref:`--deterministic-id`) which also uses MD5. While it would
  130 + certainly be possible to change the deterministic ID algorithm to
  131 + not use MD5, doing so would break all previous deterministic IDs
  132 + (which would render the feature useless for many cases) and would
  133 + offer very little benefit since even a securely generated document
  134 + ID is not itself a security-sensitive value.
  135 +
  136 +- Checksums in embedded file streams -- the PDF specification
  137 + specifies the use of MD5.
  138 +
  139 +It is therefore not possible completely avoid the use of MD5 with
  140 +qpdf, but as long as you are using 256-bit encryption, it is not used
  141 +in a securty-sensitive fashion.
  142 +
  143 +.. _breaking-crypto-api:
  144 +
  145 +API-Breaking Changes in qpdf 11.0
  146 +---------------------------------
  147 +
  148 +In qpdf 11, several deprecated functions and methods were removed.
  149 +These methods provided an incomplete API. Alternatives were added in
  150 +qpdf 8.4.0. The removed functions are
  151 +
  152 +- C API: ``qpdf_set_r3_encryption_parameters``,
  153 + ``qpdf_set_r4_encryption_parameters``,
  154 + ``qpdf_set_r5_encryption_parameters``,
  155 + ``qpdf_set_r6_encryption_parameters``
  156 +
  157 +- ``QPDFWriter``: overloaded versions of these methods with fewer
  158 + arguments: ``setR3EncryptionParameters``,
  159 + ``setR4EncryptionParameters``, ``setR5EncryptionParameters``, and
  160 + ``setR6EncryptionParameters``
  161 +
  162 +Additionally, remaining functions/methods had their names changed to
  163 +signal that they are insecure and to force developers to make a
  164 +decision. If you intentionally want to continue to use insecure
  165 +cryptographic algorithms and create insecure files, you can change
  166 +your code just add ``_insecure`` or ``Insecure`` to the end of the
  167 +function as needed. (Note the disappearance of ``2`` in some of the C
  168 +functions as well.) Better, you should migrate your code to use more
  169 +secure encryption as documented in :file:`QPDFWriter.hh`. Use the
  170 +``R6`` methods (or their corresponding C functions) to create files
  171 +with 256-bit encryption.
  172 +
  173 +.. list-table:: Renamed Functions
  174 + :widths: 50 50
  175 + :header-rows: 1
  176 +
  177 + - - Old Name
  178 + - New Name
  179 +
  180 + - - qpdf_set_r2_encryption_parameters
  181 + - qpdf_set_r2_encryption_parameters_insecure
  182 +
  183 + - - qpdf_set_r3_encryption_parameters2
  184 + - qpdf_set_r3_encryption_parameters_insecure
  185 +
  186 + - - qpdf_set_r4_encryption_parameters2
  187 + - qpdf_set_r2_encryption_parameters_insecure
  188 +
  189 + - - QPDFWriter::setR2EncryptionParameters
  190 + - QPDFWriter::setR2EncryptionParametersInsecure
  191 +
  192 + - - QPDFWriter::setR3EncryptionParameters
  193 + - QPDFWriter::setR3EncryptionParametersInsecure
  194 +
  195 + - - QPDFWriter::setR4EncryptionParameters
  196 + - QPDFWriter::setR4EncryptionParametersInsecure
qpdf/qpdf-ctest.c
@@ -320,7 +320,7 @@ test11( @@ -320,7 +320,7 @@ test11(
320 qpdf_read(qpdf, infile, password); 320 qpdf_read(qpdf, infile, password);
321 qpdf_init_write(qpdf, outfile); 321 qpdf_init_write(qpdf, outfile);
322 qpdf_set_static_ID(qpdf, QPDF_TRUE); 322 qpdf_set_static_ID(qpdf, QPDF_TRUE);
323 - qpdf_set_r2_encryption_parameters( 323 + qpdf_set_r2_encryption_parameters_insecure(
324 qpdf, "user1", "owner1", QPDF_FALSE, QPDF_TRUE, QPDF_TRUE, QPDF_TRUE); 324 qpdf, "user1", "owner1", QPDF_FALSE, QPDF_TRUE, QPDF_TRUE, QPDF_TRUE);
325 qpdf_write(qpdf); 325 qpdf_write(qpdf);
326 report_errors(); 326 report_errors();
@@ -336,7 +336,7 @@ test12( @@ -336,7 +336,7 @@ test12(
336 qpdf_read(qpdf, infile, password); 336 qpdf_read(qpdf, infile, password);
337 qpdf_init_write(qpdf, outfile); 337 qpdf_init_write(qpdf, outfile);
338 qpdf_set_static_ID(qpdf, QPDF_TRUE); 338 qpdf_set_static_ID(qpdf, QPDF_TRUE);
339 - qpdf_set_r3_encryption_parameters2( 339 + qpdf_set_r3_encryption_parameters_insecure(
340 qpdf, 340 qpdf,
341 "user2", 341 "user2",
342 "owner2", 342 "owner2",
@@ -397,7 +397,7 @@ test15( @@ -397,7 +397,7 @@ test15(
397 qpdf_init_write(qpdf, outfile); 397 qpdf_init_write(qpdf, outfile);
398 qpdf_set_static_ID(qpdf, QPDF_TRUE); 398 qpdf_set_static_ID(qpdf, QPDF_TRUE);
399 qpdf_set_static_aes_IV(qpdf, QPDF_TRUE); 399 qpdf_set_static_aes_IV(qpdf, QPDF_TRUE);
400 - qpdf_set_r4_encryption_parameters2( 400 + qpdf_set_r4_encryption_parameters_insecure(
401 qpdf, 401 qpdf,
402 "user2", 402 "user2",
403 "owner2", 403 "owner2",
qpdf/qpdf.testcov
@@ -130,8 +130,8 @@ qpdf-c called qpdf_set_qdf_mode 0 @@ -130,8 +130,8 @@ qpdf-c called qpdf_set_qdf_mode 0
130 qpdf-c called qpdf_set_static_ID 0 130 qpdf-c called qpdf_set_static_ID 0
131 qpdf-c called qpdf_set_suppress_original_object_IDs 0 131 qpdf-c called qpdf_set_suppress_original_object_IDs 0
132 qpdf-c called qpdf_set_preserve_encryption 0 132 qpdf-c called qpdf_set_preserve_encryption 0
133 -qpdf-c called qpdf_set_r2_encryption_parameters 0  
134 -qpdf-c called qpdf_set_r3_encryption_parameters 0 133 +qpdf-c called qpdf_set_r2_encryption_parameters_insecure 0
  134 +qpdf-c called qpdf_set_r3_encryption_parameters_insecure 0
135 qpdf-c called qpdf_set_linearization 0 135 qpdf-c called qpdf_set_linearization 0
136 qpdf-c called qpdf_write 1 136 qpdf-c called qpdf_write 1
137 qpdf-c called qpdf_allow_accessibility 0 137 qpdf-c called qpdf_allow_accessibility 0
@@ -158,7 +158,7 @@ QPDF_encryption cleartext metadata 0 @@ -158,7 +158,7 @@ QPDF_encryption cleartext metadata 0
158 QPDF_encryption aes decode stream 0 158 QPDF_encryption aes decode stream 0
159 QPDFWriter forcing object stream disable 0 159 QPDFWriter forcing object stream disable 0
160 QPDFWriter forced version disabled encryption 0 160 QPDFWriter forced version disabled encryption 0
161 -qpdf-c called qpdf_set_r4_encryption_parameters 0 161 +qpdf-c called qpdf_set_r4_encryption_parameters_insecure 0
162 qpdf-c called qpdf_set_static_aes_IV 0 162 qpdf-c called qpdf_set_static_aes_IV 0
163 QPDF_encryption stream crypt filter 0 163 QPDF_encryption stream crypt filter 0
164 QPDF ERR object stream with wrong type 0 164 QPDF ERR object stream with wrong type 0