Hi,

I have a patch that adds support for revision 6 encryption (PDF1.7 extension 8, PDF 2.0).

Best regards,

Andreas

Am 29.10.2021 um 18:43 schrieb Francesco Pretto:
Thank you for the clarification, I actually skipped reading a part of your question. Actually version /V key 5 is supported but it's not supported /R 6 as you found out. I have a (paid) official copy of ISO 32000-2:2017 (not very latest 2020), and about /R it says:

R, number: A number specifying which revision of the standard security handler shall be used to interpret this dictionary:
[...]
5      (PDF 2.0; deprecated) Shall not be used. This value was used by a deprecated proprietary Adobe extension.
6      (PDF 2.0) if the document is encrypted with a V value of 5.

It seems PoDoFo supports revision /R 5 as part of supporting this deprecated Adobe extension. The question now is how much different it is this /6 from /R 5 (there are certainly differences). If anybody is going to implement it I recommend only to do it with the very latest non-draft version of the standard since I can see the exact algorithm being changed between drafts and final version, even in 2017 version. Unfortunately ISO now holds the copyright for the newer versions of the PDF standard and it's not a freely downloadable standard anymore :( .


On Fri, 29 Oct 2021 at 17:32, F.E. <exler7...@gmail.com> wrote:

    Yes, Podofo supports the pdf 1.7 version of AES256 (-> extension
    level 3), when libidn is available. We use this already.

    But an AES256 encryption with 'Version 5' should refer to pdf 2.0
    (or pdf 1.7 extension level 8). It's essentially a new, more
    secure AES256 encryption implementation. Podofo does not support
    that as of now.

    Am 29.10.2021 um 13:35 schrieb Francesco Pretto:
    Support should already be present in PoDoFo but you must add
    support for Libidn[1]. As far as I remember only  Libidn  1.x is
    supported in PoDoFo because  Libidn  2.x is API incompatible.

    [1] https://www.gnu.org/software/libidn/

    On Fri, 29 Oct 2021 at 13:24, F. E. <exler7...@gmail.com> wrote:

        Hello dear podofo users,

        I have a password protected pdf file (Pwd: test), which
        podofo fails to load:

        ePdfError_UnsupportedFilter: Unable to load objects from file.: Error 
while loading object 8 0  

        Unsupported encryption method Version=5 Revision=6

        I checked in the pdf reference 1.7, and there was indeed no
        Version 5 or Revision 6 listed.

        After some more digging I found out, that the pdf should
        follow the pdf 2.0 standard (iso 32000-2), which features a
        more secure AES 256 encryption.

        My question now is, is anyone familiar with this kind of
        encryption, and would it be theoretically possible to add it
        to podofo (if anyone had the time for it ...) ?

        Greetiings,

        F.E.



--

dots Software <http://www.dots.de/en/>

Andreas Brzesowsky
Software Developer

dots Gesellschaft für Softwareentwicklung mbH
Schlesische Str. 27, 10997 Berlin, Germany

Tel: +49 (0)30 695 799-33

andreas.brzesow...@dots.de
https://www.dots.de

District court | Amtsgericht: Berlin Charlottenburg HRB 65201
Managing Director | Geschäftsführer: Katsuji Kondo
Index: src/podofo/base/PdfEncrypt.cpp
===================================================================
--- src/podofo/base/PdfEncrypt.cpp      (revision 2047)
+++ src/podofo/base/PdfEncrypt.cpp      (working copy)
@@ -45,7 +45,12 @@
 typedef SSIZE_T ssize_t;
 #endif
 #include <stringprep.h>
+#ifdef _WIN32
+// The entry point to the Windows memory de-allocation function
+#include <idn-free.h>
+#endif
 #include <openssl/sha.h>
+#include <openssl/aes.h>
 #endif // PODOFO_HAVE_LIBIDN
 
 #include <openssl/opensslconf.h>
@@ -68,7 +73,8 @@
 ePdfEncryptAlgorithm_RC4V2 |
 #endif // PODOFO_HAVE_OPENSSL_NO_RC4
 ePdfEncryptAlgorithm_AESV2 |
-ePdfEncryptAlgorithm_AESV3;
+ePdfEncryptAlgorithm_AESV3 |
+ePdfEncryptAlgorithm_AESV3R6;
 #else // PODOFO_HAVE_LIBIDN
 int PdfEncrypt::s_nEnabledEncryptionAlgorithms =
 #ifndef PODOFO_HAVE_OPENSSL_NO_RC4
@@ -527,7 +533,8 @@
             break;
 #ifdef PODOFO_HAVE_LIBIDN
         case ePdfEncryptAlgorithm_AESV3:
-            pdfEncrypt = new PdfEncryptAESV3(userPassword, ownerPassword, 
protection);
+        case ePdfEncryptAlgorithm_AESV3R6:
+            pdfEncrypt = new PdfEncryptAESV3(userPassword, ownerPassword, 
protection, eAlgorithm);
             break;
 #endif // PODOFO_HAVE_LIBIDN
 #ifndef PODOFO_HAVE_OPENSSL_NO_RC4
@@ -631,14 +638,16 @@
         pdfEncrypt = new PdfEncryptAESV2(oValue, uValue, pValue, 
encryptMetadata);      
     }
 #ifdef PODOFO_HAVE_LIBIDN
-    else if( (lV == 5L) && (rValue == 5L) 
-            && PdfEncrypt::IsEncryptionEnabled( ePdfEncryptAlgorithm_AESV3 ) ) 
+    else if( (lV == 5L) && (
+            ( rValue == 5L && PdfEncrypt::IsEncryptionEnabled( 
ePdfEncryptAlgorithm_AESV3 ) ) 
+            || ( rValue == 6L && PdfEncrypt::IsEncryptionEnabled( 
ePdfEncryptAlgorithm_AESV3R6 ) ) ) )
     {
         PdfString permsValue   = pObject->GetDictionary().GetKey( 
PdfName("Perms") )->GetString();
         PdfString oeValue      = pObject->GetDictionary().GetKey( 
PdfName("OE") )->GetString();
         PdfString ueValue      = pObject->GetDictionary().GetKey( 
PdfName("UE") )->GetString();
         
-        pdfEncrypt = new PdfEncryptAESV3(oValue, oeValue, uValue, ueValue, 
pValue, permsValue);     
+        EPdfEncryptAlgorithm eAlgorithm = rValue == 6L ? 
ePdfEncryptAlgorithm_AESV3R6 : ePdfEncryptAlgorithm_AESV3;
+        pdfEncrypt = new PdfEncryptAESV3(oValue, oeValue, uValue, ueValue, 
pValue, permsValue, eAlgorithm);
     }
 #endif // PODOFO_HAVE_LIBIDN
     else
@@ -658,7 +667,7 @@
     if (rhs.m_eAlgorithm == ePdfEncryptAlgorithm_AESV2)
         pdfEncrypt = new PdfEncryptAESV2(rhs);
 #ifdef PODOFO_HAVE_LIBIDN
-    else if (rhs.m_eAlgorithm == ePdfEncryptAlgorithm_AESV3)
+    else if (rhs.m_eAlgorithm == ePdfEncryptAlgorithm_AESV3 || 
rhs.m_eAlgorithm == ePdfEncryptAlgorithm_AESV3R6)
         pdfEncrypt = new PdfEncryptAESV3(rhs);
 #endif // PODOFO_HAVE_LIBIDN
 #ifndef PODOFO_HAVE_OPENSSL_NO_RC4
@@ -1229,6 +1238,7 @@
         case ePdfEncryptAlgorithm_AESV2:
 #ifdef PODOFO_HAVE_LIBIDN
         case ePdfEncryptAlgorithm_AESV3:
+        case ePdfEncryptAlgorithm_AESV3R6:
 #endif // PODOFO_HAVE_LIBIDN
             break;
     }
@@ -1505,6 +1515,70 @@
     memcpy( m_oeValue, static_cast<const PdfEncryptSHABase*>(ptr)->m_oeValue, 
sizeof(unsigned char) * 32 );
 }
     
+void PdfEncryptSHABase::ComputeHash(const unsigned char * pswd, int pswdLen, 
unsigned char salt[8], unsigned char uValue[48], unsigned char hashValue[32])
+{
+    SHA256_CTX sha256;
+    SHA256_Init(&sha256);
+    if(pswdLen)
+        SHA256_Update(&sha256, pswd, pswdLen);
+    SHA256_Update(&sha256, salt, 8);
+    if(uValue)
+        SHA256_Update(&sha256, uValue, 48);
+    SHA256_Final(hashValue, &sha256);
+    
+    if(m_rValue > 5) // AES-256 according to PDF 1.7 Adobe Extension Level 8 
(PDF 2.0)
+    {
+        SHA512_CTX sha384;
+        SHA512_CTX sha512;
+        AES_KEY aes;
+        int dataLen = 0;
+        int blockLen = 32; // Start with current SHA256 hash
+        unsigned char data[(127 + 64 + 48) * 64]; // 127 for password, 64 for 
hash up to SHA512, 48 for uValue
+        unsigned char block[64];
+        memcpy(block, hashValue, 32);
+        
+        for(int i = 0; i < 64 || i < 32 + data[dataLen - 1]; ++i)
+        {
+            dataLen = pswdLen + blockLen;
+            memcpy(data, pswd, pswdLen);
+            memcpy(data + pswdLen, block, blockLen);
+            if (uValue) {
+                memcpy(data + dataLen, uValue, 48);
+                dataLen += 48;
+            }
+            for(int j = 1; j < 64; ++j)
+                memcpy(data + j * dataLen, data, dataLen);
+            dataLen *= 64;
+            
+            AES_set_encrypt_key(block, 128, &aes);
+            AES_cbc_encrypt(data, data, dataLen, &aes, block + 16, 
AES_ENCRYPT);
+            int sum = 0;
+            for(int j = 0; j < 16; ++j)
+                sum += data[j];
+            blockLen = 32 + (sum % 3) * 16;
+            
+            if(blockLen == 32) {
+                SHA256_Init(&sha256);
+                SHA256_Update(&sha256, data, dataLen);
+                SHA256_Final(block, &sha256);
+            }
+            else if(blockLen == 48)
+            {
+                SHA384_Init(&sha384);
+                SHA384_Update(&sha384, data, dataLen);
+                SHA384_Final(block, &sha384);
+            }
+            else
+            {
+                SHA512_Init(&sha512);
+                SHA512_Update(&sha512, data, dataLen);
+                SHA512_Final(block, &sha512);
+            }
+        }
+        memcpy(hashValue, block, 32);
+    }
+}
+
 void PdfEncryptSHABase::ComputeUserKey(const unsigned char * userpswd, int len)
 {
     // Generate User Salts
@@ -1520,13 +1594,8 @@
     // Generate hash for U
     unsigned char hashValue[32];
     
-    SHA256_CTX context;
-    SHA256_Init(&context);
+    ComputeHash(userpswd, len, vSalt, 0, hashValue);
     
-    SHA256_Update(&context, userpswd, len);
-    SHA256_Update(&context, vSalt, 8);
-    SHA256_Final(hashValue, &context);
-    
     // U = hash + validation salt + key salt
     memcpy(m_uValue, hashValue, 32);
     memcpy(m_uValue+32, vSalt, 8);
@@ -1533,10 +1602,7 @@
     memcpy(m_uValue+32+8, kSalt, 8);
     
     // Generate hash for UE
-    SHA256_Init(&context);
-    SHA256_Update(&context, userpswd, len);
-    SHA256_Update(&context, kSalt, 8);
-    SHA256_Final(hashValue, &context);
+    ComputeHash(userpswd, len, kSalt, 0, hashValue);
     
     // UE = AES-256 encoded file encryption key with key=hash
     // CBC mode, no padding, init vector=0
@@ -1585,12 +1651,7 @@
     
     // Generate hash for O
     unsigned char hashValue[32];
-    SHA256_CTX context;
-    SHA256_Init(&context);
-    SHA256_Update(&context, ownerpswd, len);
-    SHA256_Update(&context, vSalt, 8);
-    SHA256_Update(&context, m_uValue, 48);
-    SHA256_Final(hashValue, &context);
+    ComputeHash(ownerpswd, len, vSalt, m_uValue, hashValue);
     
     // O = hash + validation salt + key salt
     memcpy(m_oValue, hashValue, 32);
@@ -1598,11 +1659,7 @@
     memcpy(m_oValue+32+8, kSalt, 8);
     
     // Generate hash for OE
-    SHA256_Init(&context);
-    SHA256_Update(&context, ownerpswd, len);
-    SHA256_Update(&context, kSalt, 8);
-    SHA256_Update(&context, m_uValue, 48);
-    SHA256_Final(hashValue, &context);
+    ComputeHash(ownerpswd, len, kSalt, m_uValue, hashValue);
     
     // OE = AES-256 encoded file encryption key with key=hash
     // CBC mode, no padding, init vector=0
@@ -1650,7 +1707,12 @@
     len = l > 127 ? 127 : l;
     
     memcpy(outBuf, password_sasl, len);
+#ifdef _WIN32
+    // In Windows the libidn.dll could use an other heap and then the normal 
free will crash.
+    idn_free(password_sasl);
+#else
     free(password_sasl); // Do not change to podofo_free, as memory is 
allocated by stringprep_profile
+#endif
 }
 
 void
@@ -1697,7 +1759,7 @@
     PdfDictionary stdCf;
     
     rDictionary.AddKey( PdfName("V"), 
static_cast<pdf_int64>(PODOFO_LL_LITERAL(5)) );
-    rDictionary.AddKey( PdfName("R"), 
static_cast<pdf_int64>(PODOFO_LL_LITERAL(5)) );
+    rDictionary.AddKey( PdfName("R"), static_cast<pdf_int64>(m_rValue) );
     rDictionary.AddKey( PdfName("Length"), 
static_cast<pdf_int64>(PODOFO_LL_LITERAL(256)) );
     
     stdCf.AddKey( PdfName("CFM"), PdfName("AESV3") );
@@ -1774,7 +1836,7 @@
     aes = &aes_local;
     #endif
     
-    int status = EVP_EncryptInit_ex(aes, EVP_aes_256_ecb(), NULL, 
m_encryptionKey, NULL);
+    int status = EVP_EncryptInit_ex(aes, EVP_aes_256_cbc(), NULL, 
m_encryptionKey, NULL);
     if(status != 1)
         PODOFO_RAISE_ERROR_INFO( ePdfError_InternalLogic, "Error initializing 
AES encryption engine" );
     EVP_CIPHER_CTX_set_padding(aes, 0); // disable padding
@@ -1806,21 +1868,13 @@
     
     // Test 1: is it the user key ?
     unsigned char hashValue[32];
-    SHA256_CTX context;
-    SHA256_Init(&context);
-    SHA256_Update(&context, pswd_sasl, pswdLen); // password
-    SHA256_Update(&context, m_uValue + 32, 8); // user Validation Salt
-    SHA256_Final(hashValue, &context);
+    ComputeHash(pswd_sasl, pswdLen, m_uValue + 32, 0, hashValue); // user 
Validation Salt
     
     ok = CheckKey(hashValue, m_uValue);
     if(!ok)
     {
         // Test 2: is it the owner key ?
-        SHA256_Init(&context);
-        SHA256_Update(&context, pswd_sasl, pswdLen); // password
-        SHA256_Update(&context, m_oValue + 32, 8); // owner Validation Salt
-        SHA256_Update(&context, m_uValue, 48); // U string
-        SHA256_Final(hashValue, &context);
+        ComputeHash(pswd_sasl, pswdLen, m_oValue + 32, m_uValue, hashValue); 
// owner Validation Salt
         
         ok = CheckKey(hashValue, m_oValue);
         
@@ -1829,11 +1883,7 @@
             m_ownerPass = password;
                        // ISO 32000: "Compute an intermediate owner key by 
computing the SHA-256 hash of
                        // the UTF-8 password concatenated with the 8 bytes of 
owner Key Salt, concatenated with the 48-byte U string."
-                       SHA256_Init(&context);
-                       SHA256_Update(&context, pswd_sasl, pswdLen); // password
-                       SHA256_Update(&context, m_oValue + 40, 8); // owner Key 
Salt
-                       SHA256_Update(&context, m_uValue, 48); // U string
-                       SHA256_Final(hashValue, &context);
+                       ComputeHash(pswd_sasl, pswdLen, m_oValue + 40, 
m_uValue, hashValue); // owner Key Salt
 
                        // ISO 32000: "The 32-byte result is the key used to 
decrypt the 32-byte OE string using
                        // AES-256 in CBC mode with no padding and an 
initialization vector of zero.
@@ -1850,10 +1900,7 @@
         m_userPass = password;
                // ISO 32000: "Compute an intermediate user key by computing 
the SHA-256 hash of
                // the UTF-8 password concatenated with the 8 bytes of user Key 
Salt"
-               SHA256_Init(&context);
-               SHA256_Update(&context, pswd_sasl, pswdLen); // password
-               SHA256_Update(&context, m_uValue + 40, 8); // user Key Salt
-               SHA256_Final(hashValue, &context);
+               ComputeHash(pswd_sasl, pswdLen, m_uValue + 40, 0, hashValue); 
// user Key Salt
 
                // ISO 32000: "The 32-byte result is the key used to decrypt 
the 32-byte UE string using
                // AES-256 in CBC mode with no padding and an initialization 
vector of zero.
@@ -1890,18 +1937,22 @@
                          unsigned char* outStr, pdf_long &outLen) const
 {
     pdf_long offset = CalculateStreamOffset();
+    if( inLen <= offset ) { // Is empty
+        outLen = 0;
+        return;
+    }
     
     const_cast<PdfEncryptAESV3*>(this)->BaseDecrypt(const_cast<unsigned 
char*>(m_encryptionKey), m_keyLength, inStr, &inStr[offset], inLen-offset, 
outStr, outLen);
 }
 
-PdfEncryptAESV3::PdfEncryptAESV3( const std::string & userPassword, const 
std::string & ownerPassword, int protection) : PdfEncryptAESBase()
+PdfEncryptAESV3::PdfEncryptAESV3( const std::string & userPassword, const 
std::string & ownerPassword, int protection, EPdfEncryptAlgorithm eAlgorithm) : 
PdfEncryptAESBase()
 {
     // setup object
     m_userPass = userPassword;
     m_ownerPass = ownerPassword;
-    m_eAlgorithm = ePdfEncryptAlgorithm_AESV3;
+    m_eAlgorithm = eAlgorithm == ePdfEncryptAlgorithm_AESV3R6 ? 
ePdfEncryptAlgorithm_AESV3R6 : ePdfEncryptAlgorithm_AESV3;
     
-    m_rValue = 5;
+    m_rValue = eAlgorithm == ePdfEncryptAlgorithm_AESV3R6 ? 6 : 5;
     m_eKeyLength = ePdfKeyLength_256;
     m_keyLength = ePdfKeyLength_256 / 8;
     
@@ -1916,14 +1967,14 @@
     m_pValue = PERMS_DEFAULT | protection;
 }
 
-PdfEncryptAESV3::PdfEncryptAESV3(PdfString oValue,PdfString oeValue, PdfString 
uValue, PdfString ueValue, int pValue, PdfString permsValue) : 
PdfEncryptAESBase()
+PdfEncryptAESV3::PdfEncryptAESV3(PdfString oValue,PdfString oeValue, PdfString 
uValue, PdfString ueValue, int pValue, PdfString permsValue, 
EPdfEncryptAlgorithm eAlgorithm) : PdfEncryptAESBase()
 {
     m_pValue = pValue;
-    m_eAlgorithm = ePdfEncryptAlgorithm_AESV3;
+    m_eAlgorithm = eAlgorithm == ePdfEncryptAlgorithm_AESV3R6 ? 
ePdfEncryptAlgorithm_AESV3R6 : ePdfEncryptAlgorithm_AESV3;
     
     m_eKeyLength = ePdfKeyLength_256;
     m_keyLength  = ePdfKeyLength_256 / 8;
-    m_rValue    = 5;
+    m_rValue    = eAlgorithm == ePdfEncryptAlgorithm_AESV3R6 ? 6 : 5;
     memcpy( m_oValue, oValue.GetString(), 48 );
     memcpy( m_oeValue, oeValue.GetString(), 32 );
     memcpy( m_uValue, uValue.GetString(), 48 );
Index: src/podofo/base/PdfEncrypt.h
===================================================================
--- src/podofo/base/PdfEncrypt.h        (revision 2047)
+++ src/podofo/base/PdfEncrypt.h        (working copy)
@@ -120,6 +120,7 @@
         ePdfEncryptAlgorithm_AESV2 = 4  ///< AES encryption with a 128 bit key 
(PDF1.6)
 #ifdef PODOFO_HAVE_LIBIDN
         ,ePdfEncryptAlgorithm_AESV3 = 8 ///< AES encryption with a 256 bit key 
(PDF1.7 extension 3) - Support added by P. Zent
+        ,ePdfEncryptAlgorithm_AESV3R6 = 16 ///< AES encryption with a 256 bit 
key, Revision 6 (PDF1.7 extension 8, PDF 2.0)
 #endif //PODOFO_HAVE_LIBIDN
     } EPdfEncryptAlgorithm;
 
@@ -495,6 +496,9 @@
     /// Compute encryption key to be used with AES-256
     void ComputeEncryptionKey();
     
+    /// Compute hash for password and salt with optional uValue
+    void ComputeHash(const unsigned char * pswd, int pswdLen, unsigned char 
salt[8], unsigned char uValue[48], unsigned char hashValue[32]);
+    
     /// Generate the U and UE entries 
     void ComputeUserKey(const unsigned char * userpswd, int len);
     
@@ -710,7 +714,7 @@
     /*
      * Constructors of PdfEncryptAESV3
      */
-    PdfEncryptAESV3(PdfString oValue, PdfString oeValue, PdfString uValue, 
PdfString ueValue, int pValue, PdfString permsValue);
+    PdfEncryptAESV3(PdfString oValue, PdfString oeValue, PdfString uValue, 
PdfString ueValue, int pValue, PdfString permsValue, EPdfEncryptAlgorithm 
eAlgorithm = ePdfEncryptAlgorithm_AESV3);
     PdfEncryptAESV3( const PdfEncrypt & rhs ) : PdfEncryptSHABase(rhs) {}
     PdfEncryptAESV3( const std::string & userPassword,
                   const std::string & ownerPassword, 
@@ -721,7 +725,8 @@
                   ePdfPermissions_FillAndSign |
                   ePdfPermissions_Accessible |
                   ePdfPermissions_DocAssembly |
-                  ePdfPermissions_HighPrint
+                  ePdfPermissions_HighPrint,
+                  EPdfEncryptAlgorithm eAlgorithm = ePdfEncryptAlgorithm_AESV3
                   );   
     
     /*
_______________________________________________
Podofo-users mailing list
Podofo-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/podofo-users

Reply via email to