Home » pdfbox-1.1.0-src » org.apache.pdfbox.encryption » [javadoc | source]

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    * Unless required by applicable law or agreed to in writing, software
   12    * distributed under the License is distributed on an "AS IS" BASIS,
   13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    * See the License for the specific language governing permissions and
   15    * limitations under the License.
   16    */
   17   package org.apache.pdfbox.encryption;
   18   
   19   import java.io.ByteArrayInputStream;
   20   import java.io.ByteArrayOutputStream;
   21   import java.io.IOException;
   22   import java.io.InputStream;
   23   import java.io.OutputStream;
   24   
   25   import java.security.MessageDigest;
   26   import java.security.NoSuchAlgorithmException;
   27   
   28   import org.apache.pdfbox.exceptions.CryptographyException;
   29   
   30   /**
   31    * This class will deal with PDF encryption algorithms.
   32    *
   33    * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
   34    * @version $Revision: 1.15 $
   35    *
   36    * @deprecated use the new security layer instead
   37    *
   38    * @see org.apache.pdfbox.pdmodel.encryption.StandardSecurityHandler
   39    */
   40   public final class PDFEncryption
   41   {
   42       private ARCFour rc4 = new ARCFour();
   43       /**
   44        * The encryption padding defined in the PDF 1.4 Spec algorithm 3.2.
   45        */
   46       public static final byte[] ENCRYPT_PADDING =
   47       {
   48           (byte)0x28, (byte)0xBF, (byte)0x4E, (byte)0x5E, (byte)0x4E,
   49           (byte)0x75, (byte)0x8A, (byte)0x41, (byte)0x64, (byte)0x00,
   50           (byte)0x4E, (byte)0x56, (byte)0xFF, (byte)0xFA, (byte)0x01,
   51           (byte)0x08, (byte)0x2E, (byte)0x2E, (byte)0x00, (byte)0xB6,
   52           (byte)0xD0, (byte)0x68, (byte)0x3E, (byte)0x80, (byte)0x2F,
   53           (byte)0x0C, (byte)0xA9, (byte)0xFE, (byte)0x64, (byte)0x53,
   54           (byte)0x69, (byte)0x7A
   55       };
   56   
   57       /**
   58        * This will encrypt a piece of data.
   59        *
   60        * @param objectNumber The id for the object.
   61        * @param genNumber The generation id for the object.
   62        * @param key The key used to encrypt the data.
   63        * @param data The data to encrypt/decrypt.
   64        * @param output The stream to write to.
   65        *
   66        * @throws CryptographyException If there is an error encrypting the data.
   67        * @throws IOException If there is an io error.
   68        */
   69       public final void encryptData(
   70           long objectNumber,
   71           long genNumber,
   72           byte[] key,
   73           InputStream data,
   74           OutputStream output )
   75           throws CryptographyException, IOException
   76       {
   77           byte[] newKey = new byte[ key.length + 5 ];
   78           System.arraycopy( key, 0, newKey, 0, key.length );
   79           //PDF 1.4 reference pg 73
   80           //step 1
   81           //we have the reference
   82   
   83           //step 2
   84           newKey[newKey.length -5] = (byte)(objectNumber & 0xff);
   85           newKey[newKey.length -4] = (byte)((objectNumber >> 8) & 0xff);
   86           newKey[newKey.length -3] = (byte)((objectNumber >> 16) & 0xff);
   87           newKey[newKey.length -2] = (byte)(genNumber & 0xff);
   88           newKey[newKey.length -1] = (byte)((genNumber >> 8) & 0xff);
   89   
   90   
   91           //step 3
   92           byte[] digestedKey = null;
   93           try
   94           {
   95               MessageDigest md = MessageDigest.getInstance( "MD5" );
   96               digestedKey = md.digest( newKey );
   97           }
   98           catch( NoSuchAlgorithmException e )
   99           {
  100               throw new CryptographyException( e );
  101           }
  102   
  103           //step 4
  104           int length = Math.min( newKey.length, 16 );
  105           byte[] finalKey = new byte[ length ];
  106           System.arraycopy( digestedKey, 0, finalKey, 0, length );
  107   
  108           rc4.setKey( finalKey );
  109           rc4.write( data, output );
  110           output.flush();
  111       }
  112   
  113       /**
  114        * This will get the user password from the owner password and the documents o value.
  115        *
  116        * @param ownerPassword The plaintext owner password.
  117        * @param o The document's o entry.
  118        * @param revision The document revision number.
  119        * @param length The length of the encryption.
  120        *
  121        * @return The plaintext padded user password.
  122        *
  123        * @throws CryptographyException If there is an error getting the user password.
  124        * @throws IOException If there is an error reading data.
  125        */
  126       public final byte[] getUserPassword(
  127           byte[] ownerPassword,
  128           byte[] o,
  129           int revision,
  130           long length )
  131           throws CryptographyException, IOException
  132       {
  133           try
  134           {
  135               ByteArrayOutputStream result = new ByteArrayOutputStream();
  136   
  137               //3.3 STEP 1
  138               byte[] ownerPadded = truncateOrPad( ownerPassword );
  139   
  140               //3.3 STEP 2
  141               MessageDigest md = MessageDigest.getInstance( "MD5" );
  142               md.update( ownerPadded );
  143               byte[] digest = md.digest();
  144   
  145               //3.3 STEP 3
  146               if( revision == 3 || revision == 4 )
  147               {
  148                   for( int i=0; i<50; i++ )
  149                   {
  150                       md.reset();
  151                       md.update( digest );
  152                       digest = md.digest();
  153                   }
  154               }
  155               if( revision == 2 && length != 5 )
  156               {
  157                   throw new CryptographyException(
  158                       "Error: Expected length=5 actual=" + length );
  159               }
  160   
  161               //3.3 STEP 4
  162               byte[] rc4Key = new byte[ (int)length ];
  163               System.arraycopy( digest, 0, rc4Key, 0, (int)length );
  164   
  165               //3.7 step 2
  166               if( revision == 2 )
  167               {
  168                   rc4.setKey( rc4Key );
  169                   rc4.write( o, result );
  170               }
  171               else if( revision == 3 || revision == 4)
  172               {
  173                   /**
  174                   byte[] iterationKey = new byte[ rc4Key.length ];
  175                   byte[] dataToEncrypt = o;
  176                   for( int i=19; i>=0; i-- )
  177                   {
  178                       System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
  179                       for( int j=0; j< iterationKey.length; j++ )
  180                       {
  181                           iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
  182                       }
  183                       rc4.setKey( iterationKey );
  184                       rc4.write( dataToEncrypt, result );
  185                       dataToEncrypt = result.toByteArray();
  186                       result.reset();
  187                   }
  188                   result.write( dataToEncrypt, 0, dataToEncrypt.length );
  189                   */
  190                   byte[] iterationKey = new byte[ rc4Key.length ];
  191   
  192   
  193                   byte[] otemp = new byte[ o.length ]; //sm
  194                   System.arraycopy( o, 0, otemp, 0, o.length ); //sm
  195                   rc4.write( o, result);//sm
  196   
  197                   for( int i=19; i>=0; i-- )
  198                   {
  199                       System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
  200                       for( int j=0; j< iterationKey.length; j++ )
  201                       {
  202                           iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
  203                       }
  204                       rc4.setKey( iterationKey );
  205                       result.reset();  //sm
  206                       rc4.write( otemp, result ); //sm
  207                       otemp = result.toByteArray(); //sm
  208                   }
  209               }
  210   
  211   
  212               return result.toByteArray();
  213   
  214           }
  215           catch( NoSuchAlgorithmException e )
  216           {
  217               throw new CryptographyException( e );
  218           }
  219       }
  220   
  221       /**
  222        * This will tell if this is the owner password or not.
  223        *
  224        * @param ownerPassword The plaintext owner password.
  225        * @param u The U value from the PDF Document.
  226        * @param o The owner password hash.
  227        * @param permissions The document permissions.
  228        * @param id The document id.
  229        * @param revision The revision of the encryption.
  230        * @param length The length of the encryption key.
  231        *
  232        * @return true if the owner password matches the one from the document.
  233        *
  234        * @throws CryptographyException If there is an error while executing crypt functions.
  235        * @throws IOException If there is an error while checking owner password.
  236        */
  237       public final boolean isOwnerPassword(
  238           byte[] ownerPassword,
  239           byte[] u,
  240           byte[] o,
  241           int permissions,
  242           byte[] id,
  243           int revision,
  244           int length)
  245           throws CryptographyException, IOException
  246       {
  247           byte[] userPassword = getUserPassword( ownerPassword, o, revision, length );
  248           return isUserPassword( userPassword, u, o, permissions, id, revision, length );
  249       }
  250   
  251       /**
  252        * This will tell if this is a valid user password.
  253        *
  254        * Algorithm 3.6 pg 80
  255        *
  256        * @param password The password to test.
  257        * @param u The U value from the PDF Document.
  258        * @param o The owner password hash.
  259        * @param permissions The document permissions.
  260        * @param id The document id.
  261        * @param revision The revision of the encryption.
  262        * @param length The length of the encryption key.
  263        *
  264        * @return true If this is the correct user password.
  265        *
  266        * @throws CryptographyException If there is an error computing the value.
  267        * @throws IOException If there is an IO error while computing the owners password.
  268        */
  269       public final boolean isUserPassword(
  270           byte[] password,
  271           byte[] u,
  272           byte[] o,
  273           int permissions,
  274           byte[] id,
  275           int revision,
  276           int length)
  277           throws CryptographyException, IOException
  278       {
  279           boolean matches = false;
  280           //STEP 1
  281           byte[] computedValue = computeUserPassword( password, o, permissions, id, revision, length );
  282           if( revision == 2 )
  283           {
  284               //STEP 2
  285               matches = arraysEqual( u, computedValue );
  286           }
  287           else if( revision == 3 || revision == 4 )
  288           {
  289               //STEP 2
  290               matches = arraysEqual( u, computedValue, 16 );
  291           }
  292           return matches;
  293       }
  294   
  295       /**
  296        * This will compare two byte[] for equality for count number of bytes.
  297        *
  298        * @param first The first byte array.
  299        * @param second The second byte array.
  300        * @param count The number of bytes to compare.
  301        *
  302        * @return true If the arrays contain the exact same data.
  303        */
  304       private final boolean arraysEqual( byte[] first, byte[] second, int count )
  305       {
  306           boolean equal = first.length >= count && second.length >= count;
  307           for( int i=0; i<count && equal; i++ )
  308           {
  309               equal = first[i] == second[i];
  310           }
  311           return equal;
  312       }
  313   
  314       /**
  315        * This will compare two byte[] for equality.
  316        *
  317        * @param first The first byte array.
  318        * @param second The second byte array.
  319        *
  320        * @return true If the arrays contain the exact same data.
  321        */
  322       private final boolean arraysEqual( byte[] first, byte[] second )
  323       {
  324           boolean equal = first.length == second.length;
  325           for( int i=0; i<first.length && equal; i++ )
  326           {
  327               equal = first[i] == second[i];
  328           }
  329           return equal;
  330       }
  331   
  332       /**
  333        * This will compute the user password hash.
  334        *
  335        * @param password The plain text password.
  336        * @param o The owner password hash.
  337        * @param permissions The document permissions.
  338        * @param id The document id.
  339        * @param revision The revision of the encryption.
  340        * @param length The length of the encryption key.
  341        *
  342        * @return The user password.
  343        *
  344        * @throws CryptographyException If there is an error computing the user password.
  345        * @throws IOException If there is an IO error.
  346        */
  347       public final byte[] computeUserPassword(
  348           byte[] password,
  349           byte[] o,
  350           int permissions,
  351           byte[] id,
  352           int revision,
  353           int length )
  354           throws CryptographyException, IOException
  355       {
  356           ByteArrayOutputStream result = new ByteArrayOutputStream();
  357           //STEP 1
  358           byte[] encryptionKey = computeEncryptedKey( password, o, permissions, id, revision, length );
  359   
  360           if( revision == 2 )
  361           {
  362               //STEP 2
  363               rc4.setKey( encryptionKey );
  364               rc4.write( ENCRYPT_PADDING, result );
  365           }
  366           else if( revision == 3 || revision == 4 )
  367           {
  368               try
  369               {
  370                   //STEP 2
  371                   MessageDigest md = MessageDigest.getInstance("MD5");
  372                   //md.update( truncateOrPad( password ) );
  373                   md.update( ENCRYPT_PADDING );
  374   
  375                   //STEP 3
  376                   md.update( id );
  377                   result.write( md.digest() );
  378   
  379                   //STEP 4 and 5
  380                   byte[] iterationKey = new byte[ encryptionKey.length ];
  381                   for( int i=0; i<20; i++ )
  382                   {
  383                       System.arraycopy( encryptionKey, 0, iterationKey, 0, iterationKey.length );
  384                       for( int j=0; j< iterationKey.length; j++ )
  385                       {
  386                           iterationKey[j] = (byte)(iterationKey[j] ^ i);
  387                       }
  388                       rc4.setKey( iterationKey );
  389                       ByteArrayInputStream input = new ByteArrayInputStream( result.toByteArray() );
  390                       result.reset();
  391                       rc4.write( input, result );
  392                   }
  393   
  394                   //step 6
  395                   byte[] finalResult = new byte[32];
  396                   System.arraycopy( result.toByteArray(), 0, finalResult, 0, 16 );
  397                   System.arraycopy( ENCRYPT_PADDING, 0, finalResult, 16, 16 );
  398                   result.reset();
  399                   result.write( finalResult );
  400               }
  401               catch( NoSuchAlgorithmException e )
  402               {
  403                   throw new CryptographyException( e );
  404               }
  405           }
  406           return result.toByteArray();
  407       }
  408   
  409       /**
  410        * This will compute the encrypted key.
  411        *
  412        * @param password The password used to compute the encrypted key.
  413        * @param o The owner password hash.
  414        * @param permissions The permissions for the document.
  415        * @param id The document id.
  416        * @param revision The security revision.
  417        * @param length The length of the encryption key.
  418        *
  419        * @return The encryption key.
  420        *
  421        * @throws CryptographyException If there is an error computing the key.
  422        */
  423       public final byte[] computeEncryptedKey(
  424           byte[] password,
  425           byte[] o,
  426           int permissions,
  427           byte[] id,
  428           int revision,
  429           int length )
  430           throws CryptographyException
  431       {
  432           byte[] result = new byte[ length ];
  433           try
  434           {
  435               //PDFReference 1.4 pg 78
  436               //step1
  437               byte[] padded = truncateOrPad( password );
  438   
  439               //step 2
  440               MessageDigest md = MessageDigest.getInstance("MD5");
  441               md.update( padded );
  442   
  443               //step 3
  444               md.update( o );
  445   
  446               //step 4
  447               byte zero = (byte)(permissions >>> 0);
  448               byte one = (byte)(permissions >>> 8);
  449               byte two = (byte)(permissions >>> 16);
  450               byte three = (byte)(permissions >>> 24);
  451   
  452               md.update( zero );
  453               md.update( one );
  454               md.update( two );
  455               md.update( three );
  456   
  457               //step 5
  458               md.update( id );
  459               byte[] digest = md.digest();
  460   
  461               //step 6
  462               if( revision == 3 || revision == 4)
  463               {
  464                   for( int i=0; i<50; i++ )
  465                   {
  466                       md.reset();
  467                       md.update( digest, 0, length );
  468                       digest = md.digest();
  469                   }
  470               }
  471   
  472               //step 7
  473               if( revision == 2 && length != 5 )
  474               {
  475                   throw new CryptographyException(
  476                       "Error: length should be 5 when revision is two actual=" + length );
  477               }
  478               System.arraycopy( digest, 0, result, 0, length );
  479           }
  480           catch( NoSuchAlgorithmException e )
  481           {
  482               throw new CryptographyException( e );
  483           }
  484           return result;
  485       }
  486   
  487       /**
  488        * This algorithm is taked from PDF Reference 1.4 Algorithm 3.3 Page 79.
  489        *
  490        * @param ownerPassword The plain owner password.
  491        * @param userPassword The plain user password.
  492        * @param revision The version of the security.
  493        * @param length The length of the document.
  494        *
  495        * @return The computed owner password.
  496        *
  497        * @throws CryptographyException If there is an error computing O.
  498        * @throws IOException If there is an error computing O.
  499        */
  500       public final byte[] computeOwnerPassword(
  501           byte[] ownerPassword,
  502           byte[] userPassword,
  503           int revision,
  504           int length )
  505           throws CryptographyException, IOException
  506       {
  507           try
  508           {
  509               //STEP 1
  510               byte[] ownerPadded = truncateOrPad( ownerPassword );
  511   
  512               //STEP 2
  513               MessageDigest md = MessageDigest.getInstance( "MD5" );
  514               md.update( ownerPadded );
  515               byte[] digest = md.digest();
  516   
  517               //STEP 3
  518               if( revision == 3 || revision == 4)
  519               {
  520                   for( int i=0; i<50; i++ )
  521                   {
  522                       md.reset();
  523                       md.update( digest, 0, length );
  524                       digest = md.digest();
  525                   }
  526               }
  527               if( revision == 2 && length != 5 )
  528               {
  529                   throw new CryptographyException(
  530                       "Error: Expected length=5 actual=" + length );
  531               }
  532   
  533               //STEP 4
  534               byte[] rc4Key = new byte[ length ];
  535               System.arraycopy( digest, 0, rc4Key, 0, length );
  536   
  537               //STEP 5
  538               byte[] paddedUser = truncateOrPad( userPassword );
  539   
  540   
  541               //STEP 6
  542               rc4.setKey( rc4Key );
  543               ByteArrayOutputStream crypted = new ByteArrayOutputStream();
  544               rc4.write( new ByteArrayInputStream( paddedUser ), crypted );
  545   
  546   
  547               //STEP 7
  548               if( revision == 3 || revision == 4 )
  549               {
  550                   byte[] iterationKey = new byte[ rc4Key.length ];
  551                   for( int i=1; i<20; i++ )
  552                   {
  553                       System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
  554                       for( int j=0; j< iterationKey.length; j++ )
  555                       {
  556                           iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
  557                       }
  558                       rc4.setKey( iterationKey );
  559                       ByteArrayInputStream input = new ByteArrayInputStream( crypted.toByteArray() );
  560                       crypted.reset();
  561                       rc4.write( input, crypted );
  562                   }
  563               }
  564   
  565               //STEP 8
  566               return crypted.toByteArray();
  567           }
  568           catch( NoSuchAlgorithmException e )
  569           {
  570               throw new CryptographyException( e.getMessage() );
  571           }
  572       }
  573   
  574       /**
  575        * This will take the password and truncate or pad it as necessary.
  576        *
  577        * @param password The password to pad or truncate.
  578        *
  579        * @return The padded or truncated password.
  580        */
  581       private final byte[] truncateOrPad( byte[] password )
  582       {
  583           byte[] padded = new byte[ ENCRYPT_PADDING.length ];
  584           int bytesBeforePad = Math.min( password.length, padded.length );
  585           System.arraycopy( password, 0, padded, 0, bytesBeforePad );
  586           System.arraycopy( ENCRYPT_PADDING, 0, padded, bytesBeforePad, ENCRYPT_PADDING.length-bytesBeforePad );
  587           return padded;
  588       }
  589   }

Home » pdfbox-1.1.0-src » org.apache.pdfbox.encryption » [javadoc | source]