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.math.BigInteger; 24 import java.security.MessageDigest; 25 import java.security.NoSuchAlgorithmException; 26 import java.util.HashSet; 27 import java.util.Iterator; 28 import java.util.List; 29 import java.util.Map; 30 import java.util.Set; 31 32 import org.apache.pdfbox.cos.COSArray; 33 import org.apache.pdfbox.cos.COSBase; 34 import org.apache.pdfbox.cos.COSDictionary; 35 import org.apache.pdfbox.cos.COSDocument; 36 import org.apache.pdfbox.cos.COSName; 37 import org.apache.pdfbox.cos.COSObject; 38 import org.apache.pdfbox.cos.COSStream; 39 import org.apache.pdfbox.cos.COSString; 40 import org.apache.pdfbox.exceptions.CryptographyException; 41 import org.apache.pdfbox.exceptions.InvalidPasswordException; 42 import org.apache.pdfbox.pdmodel.PDDocument; 43 import org.apache.pdfbox.pdmodel.encryption.PDStandardEncryption; 44 45 /** 46 * This class will deal with encrypting/decrypting a document. 47 * 48 * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> 49 * @version $Revision: 1.13 $ 50 * 51 * @deprecated use the new security API instead. 52 * 53 * @see org.apache.pdfbox.pdmodel.encryption.StandardSecurityHandler 54 */ 55 public class DocumentEncryption 56 { 57 private PDDocument pdDocument = null; 58 private COSDocument document = null; 59 60 private byte[] encryptionKey = null; 61 private PDFEncryption encryption = new PDFEncryption(); 62 63 private Set objects = new HashSet(); 64 65 /** 66 * A set that contains potential signature dictionaries. This is used 67 * because the Contents entry of the signature is not encrypted. 68 */ 69 private Set potentialSignatures = new HashSet(); 70 71 /** 72 * Constructor. 73 * 74 * @param doc The document to decrypt. 75 */ 76 public DocumentEncryption( PDDocument doc ) 77 { 78 pdDocument = doc; 79 document = doc.getDocument(); 80 } 81 82 /** 83 * Constructor. 84 * 85 * @param doc The document to decrypt. 86 */ 87 public DocumentEncryption( COSDocument doc ) 88 { 89 pdDocument = new PDDocument( doc ); 90 document = doc; 91 } 92 93 /** 94 * This will encrypt the given document, given the owner password and user password. 95 * The encryption method used is the standard filter. 96 * 97 * @throws CryptographyException If an error occurs during encryption. 98 * @throws IOException If there is an error accessing the data. 99 */ 100 public void initForEncryption() 101 throws CryptographyException, IOException 102 { 103 String ownerPassword = pdDocument.getOwnerPasswordForEncryption(); 104 String userPassword = pdDocument.getUserPasswordForEncryption(); 105 if( ownerPassword == null ) 106 { 107 ownerPassword = ""; 108 } 109 if( userPassword == null ) 110 { 111 userPassword = ""; 112 } 113 PDStandardEncryption encParameters = (PDStandardEncryption)pdDocument.getEncryptionDictionary(); 114 int permissionInt = encParameters.getPermissions(); 115 int revision = encParameters.getRevision(); 116 int length = encParameters.getLength()/8; 117 COSArray idArray = document.getDocumentID(); 118 119 //check if the document has an id yet. If it does not then 120 //generate one 121 if( idArray == null || idArray.size() < 2 ) 122 { 123 idArray = new COSArray(); 124 try 125 { 126 MessageDigest md = MessageDigest.getInstance( "MD5" ); 127 BigInteger time = BigInteger.valueOf( System.currentTimeMillis() ); 128 md.update( time.toByteArray() ); 129 md.update( ownerPassword.getBytes() ); 130 md.update( userPassword.getBytes() ); 131 md.update( document.toString().getBytes() ); 132 byte[] id = md.digest( this.toString().getBytes() ); 133 COSString idString = new COSString(); 134 idString.append( id ); 135 idArray.add( idString ); 136 idArray.add( idString ); 137 document.setDocumentID( idArray ); 138 } 139 catch( NoSuchAlgorithmException e ) 140 { 141 throw new CryptographyException( e ); 142 } 143 144 } 145 COSString id = (COSString)idArray.getObject( 0 ); 146 encryption = new PDFEncryption(); 147 148 byte[] o = encryption.computeOwnerPassword( 149 ownerPassword.getBytes("ISO-8859-1"), 150 userPassword.getBytes("ISO-8859-1"), revision, length); 151 152 byte[] u = encryption.computeUserPassword( 153 userPassword.getBytes("ISO-8859-1"), 154 o, permissionInt, id.getBytes(), revision, length); 155 156 encryptionKey = encryption.computeEncryptedKey( 157 userPassword.getBytes("ISO-8859-1"), o, permissionInt, id.getBytes(), revision, length); 158 159 encParameters.setOwnerKey( o ); 160 encParameters.setUserKey( u ); 161 162 document.setEncryptionDictionary( encParameters.getCOSDictionary() ); 163 } 164 165 166 167 /** 168 * This will decrypt the document. 169 * 170 * @param password The password for the document. 171 * 172 * @throws CryptographyException If there is an error decrypting the document. 173 * @throws IOException If there is an error getting the stream data. 174 * @throws InvalidPasswordException If the password is not a user or owner password. 175 */ 176 public void decryptDocument( String password ) 177 throws CryptographyException, IOException, InvalidPasswordException 178 { 179 if( password == null ) 180 { 181 password = ""; 182 } 183 184 PDStandardEncryption encParameters = (PDStandardEncryption)pdDocument.getEncryptionDictionary(); 185 186 187 int permissions = encParameters.getPermissions(); 188 int revision = encParameters.getRevision(); 189 int length = encParameters.getLength()/8; 190 191 COSString id = (COSString)document.getDocumentID().getObject( 0 ); 192 byte[] u = encParameters.getUserKey(); 193 byte[] o = encParameters.getOwnerKey(); 194 195 boolean isUserPassword = 196 encryption.isUserPassword( password.getBytes(), u, 197 o, permissions, id.getBytes(), revision, length ); 198 boolean isOwnerPassword = 199 encryption.isOwnerPassword( password.getBytes(), u, 200 o, permissions, id.getBytes(), revision, length ); 201 202 if( isUserPassword ) 203 { 204 encryptionKey = 205 encryption.computeEncryptedKey( 206 password.getBytes(), o, 207 permissions, id.getBytes(), revision, length ); 208 } 209 else if( isOwnerPassword ) 210 { 211 byte[] computedUserPassword = 212 encryption.getUserPassword( 213 password.getBytes(), 214 o, 215 revision, 216 length ); 217 encryptionKey = 218 encryption.computeEncryptedKey( 219 computedUserPassword, o, 220 permissions, id.getBytes(), revision, length ); 221 } 222 else 223 { 224 throw new InvalidPasswordException( "Error: The supplied password does not match " + 225 "either the owner or user password in the document." ); 226 } 227 228 COSDictionary trailer = document.getTrailer(); 229 COSArray fields = (COSArray)trailer.getObjectFromPath( "Root/AcroForm/Fields" ); 230 231 //We need to collect all the signature dictionaries, for some 232 //reason the 'Contents' entry of signatures is not really encrypted 233 if( fields != null ) 234 { 235 for( int i=0; i<fields.size(); i++ ) 236 { 237 COSDictionary field = (COSDictionary)fields.getObject( i ); 238 addDictionaryAndSubDictionary( potentialSignatures, field ); 239 } 240 } 241 242 List allObjects = document.getObjects(); 243 Iterator objectIter = allObjects.iterator(); 244 while( objectIter.hasNext() ) 245 { 246 decryptObject( (COSObject)objectIter.next() ); 247 } 248 document.setEncryptionDictionary( null ); 249 } 250 251 private void addDictionaryAndSubDictionary( Set set, COSDictionary dic ) 252 { 253 set.add( dic ); 254 COSArray kids = (COSArray)dic.getDictionaryObject( COSName.KIDS ); 255 for( int i=0; kids != null && i<kids.size(); i++ ) 256 { 257 addDictionaryAndSubDictionary( set, (COSDictionary)kids.getObject( i ) ); 258 } 259 COSBase value = dic.getDictionaryObject( COSName.V ); 260 if( value instanceof COSDictionary ) 261 { 262 addDictionaryAndSubDictionary( set, (COSDictionary)value ); 263 } 264 } 265 266 /** 267 * This will decrypt an object in the document. 268 * 269 * @param object The object to decrypt. 270 * 271 * @throws CryptographyException If there is an error decrypting the stream. 272 * @throws IOException If there is an error getting the stream data. 273 */ 274 private void decryptObject( COSObject object ) 275 throws CryptographyException, IOException 276 { 277 long objNum = object.getObjectNumber().intValue(); 278 long genNum = object.getGenerationNumber().intValue(); 279 COSBase base = object.getObject(); 280 decrypt( base, objNum, genNum ); 281 } 282 283 /** 284 * This will dispatch to the correct method. 285 * 286 * @param obj The object to decrypt. 287 * @param objNum The object number. 288 * @param genNum The object generation Number. 289 * 290 * @throws CryptographyException If there is an error decrypting the stream. 291 * @throws IOException If there is an error getting the stream data. 292 */ 293 public void decrypt( Object obj, long objNum, long genNum ) 294 throws CryptographyException, IOException 295 { 296 if( !objects.contains( obj ) ) 297 { 298 objects.add( obj ); 299 300 if( obj instanceof COSString ) 301 { 302 decryptString( (COSString)obj, objNum, genNum ); 303 } 304 else if( obj instanceof COSStream ) 305 { 306 decryptStream( (COSStream)obj, objNum, genNum ); 307 } 308 else if( obj instanceof COSDictionary ) 309 { 310 decryptDictionary( (COSDictionary)obj, objNum, genNum ); 311 } 312 else if( obj instanceof COSArray ) 313 { 314 decryptArray( (COSArray)obj, objNum, genNum ); 315 } 316 } 317 } 318 319 /** 320 * This will decrypt a stream. 321 * 322 * @param stream The stream to decrypt. 323 * @param objNum The object number. 324 * @param genNum The object generation number. 325 * 326 * @throws CryptographyException If there is an error getting the stream. 327 * @throws IOException If there is an error getting the stream data. 328 */ 329 private void decryptStream( COSStream stream, long objNum, long genNum ) 330 throws CryptographyException, IOException 331 { 332 decryptDictionary( stream, objNum, genNum ); 333 InputStream encryptedStream = stream.getFilteredStream(); 334 encryption.encryptData( objNum, 335 genNum, 336 encryptionKey, 337 encryptedStream, 338 stream.createFilteredStream() ); 339 } 340 341 /** 342 * This will decrypt a dictionary. 343 * 344 * @param dictionary The dictionary to decrypt. 345 * @param objNum The object number. 346 * @param genNum The object generation number. 347 * 348 * @throws CryptographyException If there is an error decrypting the document. 349 * @throws IOException If there is an error creating a new string. 350 */ 351 private void decryptDictionary( COSDictionary dictionary, long objNum, long genNum ) 352 throws CryptographyException, IOException 353 { 354 for( Map.Entry<COSName, COSBase> entry : dictionary.entrySet() ) 355 { 356 //if we are a signature dictionary and contain a Contents entry then 357 //we don't decrypt it. 358 if( !(entry.getKey().getName().equals( "Contents" ) && 359 entry.getValue() instanceof COSString && 360 potentialSignatures.contains( dictionary ))) 361 { 362 decrypt( entry.getValue(), objNum, genNum ); 363 } 364 } 365 } 366 367 /** 368 * This will decrypt a string. 369 * 370 * @param string the string to decrypt. 371 * @param objNum The object number. 372 * @param genNum The object generation number. 373 * 374 * @throws CryptographyException If an error occurs during decryption. 375 * @throws IOException If an error occurs writing the new string. 376 */ 377 private void decryptString( COSString string, long objNum, long genNum ) 378 throws CryptographyException, IOException 379 { 380 ByteArrayInputStream data = new ByteArrayInputStream( string.getBytes() ); 381 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 382 encryption.encryptData( objNum, 383 genNum, 384 encryptionKey, 385 data, 386 buffer ); 387 string.reset(); 388 string.append( buffer.toByteArray() ); 389 } 390 391 /** 392 * This will decrypt an array. 393 * 394 * @param array The array to decrypt. 395 * @param objNum The object number. 396 * @param genNum The object generation number. 397 * 398 * @throws CryptographyException If an error occurs during decryption. 399 * @throws IOException If there is an error accessing the data. 400 */ 401 private void decryptArray( COSArray array, long objNum, long genNum ) 402 throws CryptographyException, IOException 403 { 404 for( int i=0; i<array.size(); i++ ) 405 { 406 decrypt( array.get( i ), objNum, genNum ); 407 } 408 } 409 }