You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ti...@apache.org on 2014/08/12 19:20:29 UTC

svn commit: r1617537 - /pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java

Author: tilman
Date: Tue Aug 12 17:20:28 2014
New Revision: 1617537

URL: http://svn.apache.org/r1617537
Log:
PDFBOX-2268, PDFBOX-2269: Support for AES-256 Rev. 5 Decryption (Acrobat 9), relaxed validation of the Perms dictionary, as done by Michele Balistreri

Modified:
    pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java

Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java?rev=1617537&r1=1617536&r2=1617537&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java Tue Aug 12 17:20:28 2014
@@ -22,12 +22,15 @@ import java.io.IOException;
 import java.math.BigInteger;
 import java.security.GeneralSecurityException;
 import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.util.Arrays;
  
 import javax.crypto.Cipher;
 import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 
 import org.apache.pdfbox.cos.COSArray;
 import org.apache.pdfbox.cos.COSName;
@@ -43,6 +46,11 @@ import org.apache.pdfbox.pdmodel.PDDocum
  */
 public final class StandardSecurityHandler extends SecurityHandler
 {
+    /**
+     * Log instance.
+     */
+    private static final Log LOG = LogFactory.getLog(StandardSecurityHandler.class);
+
     /** Type of security handler. */
     public static final String FILTER = "Standard";
 
@@ -142,6 +150,7 @@ public final class StandardSecurityHandl
      *
      * @throws IOException If there is an error accessing data.
      */
+    @Override
     public void decryptDocument(PDDocument doc, DecryptionMaterial decryptionMaterial)
         throws IOException
     {
@@ -167,6 +176,7 @@ public final class StandardSecurityHandl
      *
      * @throws IOException If there is an error accessing data.
      */
+    @Override
     public void prepareForDecryption(PDEncryption encryption, COSArray documentIDArray,
                                      DecryptionMaterial decryptionMaterial)
                                      throws IOException
@@ -210,7 +220,7 @@ public final class StandardSecurityHandl
         byte[] ue = null, oe = null;
 
         String passwordCharset = "ISO-8859-1";
-        if (dicRevision == 6)
+        if (dicRevision == 6 || dicRevision == 5)
         {
             passwordCharset = "UTF-8";
             ue = encryption.getUserEncryptionKey();
@@ -224,7 +234,7 @@ public final class StandardSecurityHandl
             currentAccessPermission = AccessPermission.getOwnerAccessPermission();
             
             byte[] computedPassword;
-            if (dicRevision == 6)
+            if (dicRevision == 6 || dicRevision == 5)
             {
                 computedPassword = password.getBytes(passwordCharset);
             }
@@ -264,9 +274,9 @@ public final class StandardSecurityHandl
             throw new IOException("Cannot decrypt PDF, the password is incorrect");
         }
 
-        if (dicRevision == 6)
+        if (dicRevision == 6 || dicRevision == 5)
         {
-            // Algorithm 13: validate permissions ("Perms" field)
+            // Algorithm 13: validate permissions ("Perms" field). Relaxed to accomodate buggy encoders
             try
             {
                 Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
@@ -275,7 +285,7 @@ public final class StandardSecurityHandl
                 
                 if (perms[9] != 'a' || perms[10] != 'd' || perms[11] != 'b')
                 {
-                    throw new IOException("Verification of permissions failed (constant)");
+                    LOG.warn("Verification of permissions failed (constant)");
                 }
 
                 int permsP = perms[0] & 0xFF | perms[1] & 0xFF << 8 | perms[2] & 0xFF << 16 |
@@ -283,17 +293,18 @@ public final class StandardSecurityHandl
                 
                 if (permsP != dicPermissions)
                 {
-                    throw new IOException("Verification of permissions failed (" + permsP +
+                    LOG.warn("Verification of permissions failed (" + permsP +
                             " != " + dicPermissions + ")");
                 }
                 
                 if (encryptMetadata && perms[8] != 'T' || !encryptMetadata && perms[8] != 'F')
                 {
-                    throw new IOException("Verification of permissions failed (EncryptMetadata)");
+                    LOG.warn("Verification of permissions failed (EncryptMetadata)");
                 }
             }
             catch (GeneralSecurityException e)
             {
+                logIfStrongEncryptionMissing();
                 throw new IOException(e);
             }
         }
@@ -442,6 +453,7 @@ public final class StandardSecurityHandl
             }
             catch (GeneralSecurityException e)
             {
+                logIfStrongEncryptionMissing();
                 throw new IOException(e);
             }
         }
@@ -511,7 +523,7 @@ public final class StandardSecurityHandl
                                    int permissions, byte[] id, int encRevision, int length,
                                    boolean encryptMetadata) throws IOException
     {
-        if (encRevision == 6)
+        if (encRevision == 6 || encRevision == 5)
         {            
             ownerPassword = truncate127(ownerPassword);
             
@@ -520,14 +532,18 @@ public final class StandardSecurityHandl
             System.arraycopy(owner, 0, oHash, 0, 32);
             System.arraycopy(owner, 32, oValidationSalt, 0, 8);
             
-            byte[] hash = computeHash2A(ownerPassword, oValidationSalt, user);
+            byte[] hash;
+            if (encRevision == 5)
+            {
+                hash = computeSHA256(ownerPassword, oValidationSalt, user);
+            }
+            else
+            {
+                hash = computeHash2A(ownerPassword, oValidationSalt, user);
+            }
+
             return Arrays.equals(hash, oHash);
         }
-        else if (encRevision == 5)
-        {
-            // Shall not be used. This value was used by a deprecated Adobe extension.
-            throw new IOException("Unsupported Encryption Revision " + encRevision);
-        }
         else
         {
             byte[] userPassword = getUserPassword( ownerPassword, owner, encRevision, length );
@@ -626,7 +642,7 @@ public final class StandardSecurityHandl
     {
         byte[] result = new byte[ length ];
         
-        if (encRevision == 6)
+        if (encRevision == 6 || encRevision == 5)
         {
             //Algorithm 2.A, based on SHA-2 and AES
             
@@ -635,14 +651,32 @@ public final class StandardSecurityHandl
             {
                 byte[] oKeySalt = new byte[8];
                 System.arraycopy(o, 40, oKeySalt, 0, 8);
-                hash = computeHash2A(password, oKeySalt, u);
+
+                if (encRevision == 5)
+                {
+                    hash = computeSHA256(password, oKeySalt, u);
+                }
+                else
+                {
+                    hash = computeHash2A(password, oKeySalt, u);
+                }
+
                 fileKeyEnc = oe;
             }
             else
             {
                 byte[] uKeySalt = new byte[8];
                 System.arraycopy(u, 40, uKeySalt, 0, 8);
-                hash = computeHash2A(password, uKeySalt, null);
+
+                if (encRevision == 5)
+                {
+                    hash = computeSHA256(password, uKeySalt, null);
+                }
+                else
+                {
+                    hash = computeHash2A(password, uKeySalt, null);
+                }
+
                 fileKeyEnc = ue;
             }
             
@@ -655,6 +689,7 @@ public final class StandardSecurityHandl
             }
             catch (GeneralSecurityException e)
             {
+                logIfStrongEncryptionMissing();
                 throw new IOException(e);
             }
         }
@@ -861,18 +896,20 @@ public final class StandardSecurityHandl
                                   byte[] id, int encRevision, int length, boolean encryptMetadata)
                                   throws IOException
     {
-        byte[] passwordBytes = computeUserPassword( password, owner, permissions, id, encRevision,
-                                                    length, encryptMetadata );
         if( encRevision == 2 )
         {
+            byte[] passwordBytes = computeUserPassword( password, owner, permissions, id, encRevision,
+                                                        length, encryptMetadata );
             return Arrays.equals(user, passwordBytes);
         }
         else if( encRevision == 3 || encRevision == 4 )
         {
+            byte[] passwordBytes = computeUserPassword( password, owner, permissions, id, encRevision,
+                                                        length, encryptMetadata );
             // compare first 16 bytes only
             return Arrays.equals(Arrays.copyOf(user, 16), Arrays.copyOf(passwordBytes, 16));
         }
-        else if (encRevision == 6)
+        else if (encRevision == 6 || encRevision == 5)
         {
             password = truncate127(password);
             
@@ -880,8 +917,17 @@ public final class StandardSecurityHandl
             byte[] uValidationSalt = new byte[8];
             System.arraycopy(user, 0, uHash, 0, 32);
             System.arraycopy(user, 32, uValidationSalt, 0, 8);
-            
-            byte[] hash = computeHash2A(password, uValidationSalt, null);
+
+            byte[] hash;
+            if (encRevision == 5)
+            {
+                hash = computeSHA256(password, uValidationSalt, null);
+            }
+            else
+            {
+                hash = computeHash2A(password, uValidationSalt, null);
+            }
+
             return Arrays.equals(hash, uHash);
         }
         else
@@ -910,7 +956,7 @@ public final class StandardSecurityHandl
                                   byte[] id, int encRevision,  int length, boolean encryptMetadata)
                                   throws IOException
     {
-        if (encRevision == 6)
+        if (encRevision == 6 || encRevision == 5)
         {
             return isUserPassword(password.getBytes("UTF-8"), user, owner, permissions, id,
                     encRevision, length, encryptMetadata);
@@ -1041,6 +1087,23 @@ public final class StandardSecurityHandl
         }
         catch (GeneralSecurityException e)
         {
+            logIfStrongEncryptionMissing();
+            throw new IOException(e);
+        }
+    }
+
+    private static byte[] computeSHA256(byte[] input, byte[] password, byte[] userKey) 
+            throws IOException
+    {
+        try
+        {
+            MessageDigest md = MessageDigest.getInstance("SHA-256");
+            md.update(input);
+            md.update(password);
+            return userKey == null ? md.digest() : md.digest(userKey);
+        }
+        catch (NoSuchAlgorithmException e)
+        {
             throw new IOException(e);
         }
     }
@@ -1072,4 +1135,19 @@ public final class StandardSecurityHandl
         System.arraycopy(in, 0, trunc, 0, 127);
         return trunc;
     }
+
+    private static void logIfStrongEncryptionMissing()
+    {
+        try
+        {
+            if (Cipher.getMaxAllowedKeyLength("AES") != Integer.MAX_VALUE)
+            {
+                LOG.warn("JCE unlimited strength jurisdiction policy files are not installed");
+            }
+        }
+        catch (NoSuchAlgorithmException ex)
+        {
+        }
+    }
+
 }