1 31 package org.pdfbox.pdmodel.encryption; 32 33 import java.io.ByteArrayInputStream ; 34 import java.io.ByteArrayOutputStream ; 35 import java.io.IOException ; 36 import java.math.BigInteger ; 37 import java.security.MessageDigest ; 38 import java.security.NoSuchAlgorithmException ; 39 40 import org.pdfbox.cos.COSArray; 41 import org.pdfbox.cos.COSString; 42 import org.pdfbox.encryption.ARCFour; 43 import org.pdfbox.exceptions.CryptographyException; 44 import org.pdfbox.pdmodel.PDDocument; 45 46 59 60 public class StandardSecurityHandler extends SecurityHandler 61 { 62 65 public static final String FILTER = "Standard"; 66 67 private static final int DEFAULT_VERSION = 1; 68 69 private static final int DEFAULT_REVISION = 3; 70 71 private int revision = DEFAULT_REVISION; 72 73 private StandardProtectionPolicy policy; 74 75 private ARCFour rc4 = new ARCFour(); 76 77 80 public static final Class PROTECTION_POLICY_CLASS = StandardProtectionPolicy.class; 81 82 85 public static final byte[] ENCRYPT_PADDING = 86 { 87 (byte)0x28, (byte)0xBF, (byte)0x4E, (byte)0x5E, (byte)0x4E, 88 (byte)0x75, (byte)0x8A, (byte)0x41, (byte)0x64, (byte)0x00, 89 (byte)0x4E, (byte)0x56, (byte)0xFF, (byte)0xFA, (byte)0x01, 90 (byte)0x08, (byte)0x2E, (byte)0x2E, (byte)0x00, (byte)0xB6, 91 (byte)0xD0, (byte)0x68, (byte)0x3E, (byte)0x80, (byte)0x2F, 92 (byte)0x0C, (byte)0xA9, (byte)0xFE, (byte)0x64, (byte)0x53, 93 (byte)0x69, (byte)0x7A 94 }; 95 96 99 public StandardSecurityHandler() 100 { 101 } 102 103 108 public StandardSecurityHandler(StandardProtectionPolicy p) 109 { 110 policy = p; 111 keyLength = policy.getEncryptionKeyLength(); 112 } 113 114 115 122 private int computeVersionNumber() 123 { 124 if(keyLength == 40) 125 { 126 return DEFAULT_VERSION; 127 } 128 return 2; 129 } 130 131 138 private int computeRevisionNumber() 139 { 140 if(version == 2 141 && !policy.getPermissions().canFillInForm() 142 && !policy.getPermissions().canExtractForAccessibility() 143 && !policy.getPermissions().canPrintDegraded() ) 144 { 145 return 2; 146 } 147 return 3; 148 } 149 150 159 public void decryptDocument(PDDocument doc, DecryptionMaterial decryptionMaterial) 160 throws CryptographyException, IOException 161 { 162 document = doc; 163 164 PDEncryptionDictionary dictionary = document.getEncryptionDictionary(); 165 if(!(decryptionMaterial instanceof StandardDecryptionMaterial)) 166 { 167 throw new CryptographyException("Provided decryption material is not compatible with the document"); 168 } 169 170 StandardDecryptionMaterial material = (StandardDecryptionMaterial)decryptionMaterial; 171 172 String password = material.getPassword(); 173 if(password == null) 174 { 175 password = ""; 176 } 177 178 int dicPermissions = dictionary.getPermissions(); 179 int dicRevision = dictionary.getRevision(); 180 int dicLength = dictionary.getLength()/8; 181 182 COSString id = (COSString)document.getDocument().getDocumentID().getObject( 0 ); 183 byte[] u = dictionary.getUserKey(); 184 byte[] o = dictionary.getOwnerKey(); 185 186 boolean isUserPassword = 187 isUserPassword( 188 password.getBytes(), 189 u, 190 o, 191 dicPermissions, 192 id.getBytes(), 193 dicRevision, 194 dicLength ); 195 boolean isOwnerPassword = 196 isOwnerPassword( 197 password.getBytes(), 198 u, 199 o, 200 dicPermissions, 201 id.getBytes(), 202 dicRevision, 203 dicLength ); 204 205 if( isUserPassword ) 206 { 207 encryptionKey = 208 computeEncryptedKey( 209 password.getBytes(), 210 o, 211 dicPermissions, 212 id.getBytes(), 213 dicRevision, 214 dicLength ); 215 } 216 else if( isOwnerPassword ) 217 { 218 byte[] computedUserPassword = getUserPassword(password.getBytes(),o,dicRevision,dicLength ); 219 encryptionKey = 220 computeEncryptedKey( 221 computedUserPassword, 222 o, 223 dicPermissions, 224 id.getBytes(), 225 dicRevision, 226 dicLength ); 227 } 228 else 229 { 230 throw new CryptographyException( 231 "Error: The supplied password does not match either the owner or user password in the document." ); 232 } 233 234 this.proceedDecryption(); 235 } 236 237 245 public void prepareDocumentForEncryption(PDDocument doc) throws CryptographyException, IOException 246 { 247 document = doc; 248 PDEncryptionDictionary encryptionDictionary = document.getEncryptionDictionary(); 249 if(encryptionDictionary == null) 250 { 251 encryptionDictionary = new PDEncryptionDictionary(); 252 } 253 version = computeVersionNumber(); 254 revision = computeRevisionNumber(); 255 encryptionDictionary.setFilter(FILTER); 256 encryptionDictionary.setVersion(version); 257 encryptionDictionary.setRevision(revision); 258 encryptionDictionary.setLength(keyLength); 259 260 String ownerPassword = policy.getOwnerPassword(); 261 String userPassword = policy.getUserPassword(); 262 if( ownerPassword == null ) 263 { 264 ownerPassword = ""; 265 } 266 if( userPassword == null ) 267 { 268 userPassword = ""; 269 } 270 271 int permissionInt = policy.getPermissions().getPermissionBytes(); 272 273 encryptionDictionary.setPermissions(permissionInt); 274 275 int length = keyLength/8; 276 277 COSArray idArray = document.getDocument().getDocumentID(); 278 279 if( idArray == null || idArray.size() < 2 ) 282 { 283 idArray = new COSArray(); 284 try 285 { 286 MessageDigest md = MessageDigest.getInstance( "MD5" ); 287 BigInteger time = BigInteger.valueOf( System.currentTimeMillis() ); 288 md.update( time.toByteArray() ); 289 md.update( ownerPassword.getBytes() ); 290 md.update( userPassword.getBytes() ); 291 md.update( document.getDocument().toString().getBytes() ); 292 byte[] id = md.digest( this.toString().getBytes() ); 293 COSString idString = new COSString(); 294 idString.append( id ); 295 idArray.add( idString ); 296 idArray.add( idString ); 297 document.getDocument().setDocumentID( idArray ); 298 } 299 catch( NoSuchAlgorithmException e ) 300 { 301 throw new CryptographyException( e ); 302 } 303 catch(IOException e ) 304 { 305 throw new CryptographyException( e ); 306 } 307 } 308 309 COSString id = (COSString)idArray.getObject( 0 ); 310 311 byte[] o = computeOwnerPassword( 312 ownerPassword.getBytes("ISO-8859-1"), 313 userPassword.getBytes("ISO-8859-1"), revision, length); 314 315 byte[] u = computeUserPassword( 316 userPassword.getBytes("ISO-8859-1"), 317 o, permissionInt, id.getBytes(), revision, length); 318 319 encryptionKey = computeEncryptedKey( 320 userPassword.getBytes("ISO-8859-1"), o, permissionInt, id.getBytes(), revision, length); 321 322 encryptionDictionary.setOwnerKey(o); 323 encryptionDictionary.setUserKey(u); 324 325 document.setEncryptionDictionary( encryptionDictionary ); 326 document.getDocument().setEncryptionDictionary(encryptionDictionary.getCOSDictionary()); 327 328 } 329 330 346 public final boolean isOwnerPassword( 347 byte[] ownerPassword, 348 byte[] u, 349 byte[] o, 350 int permissions, 351 byte[] id, 352 int encRevision, 353 int length) 354 throws CryptographyException, IOException 355 { 356 byte[] userPassword = getUserPassword( ownerPassword, o, encRevision, length ); 357 return isUserPassword( userPassword, u, o, permissions, id, encRevision, length ); 358 } 359 360 373 public final byte[] getUserPassword( 374 byte[] ownerPassword, 375 byte[] o, 376 int encRevision, 377 long length ) 378 throws CryptographyException, IOException 379 { 380 try 381 { 382 ByteArrayOutputStream result = new ByteArrayOutputStream (); 383 384 byte[] ownerPadded = truncateOrPad( ownerPassword ); 386 387 MessageDigest md = MessageDigest.getInstance( "MD5" ); 389 md.update( ownerPadded ); 390 byte[] digest = md.digest(); 391 392 if( encRevision == 3 || encRevision == 4 ) 394 { 395 for( int i=0; i<50; i++ ) 396 { 397 md.reset(); 398 md.update( digest ); 399 digest = md.digest(); 400 } 401 } 402 if( encRevision == 2 && length != 5 ) 403 { 404 throw new CryptographyException( 405 "Error: Expected length=5 actual=" + length ); 406 } 407 408 byte[] rc4Key = new byte[ (int)length ]; 410 System.arraycopy( digest, 0, rc4Key, 0, (int)length ); 411 412 if( encRevision == 2 ) 414 { 415 rc4.setKey( rc4Key ); 416 rc4.write( o, result ); 417 } 418 else if( encRevision == 3 || encRevision == 4) 419 { 420 437 byte[] iterationKey = new byte[ rc4Key.length ]; 438 439 440 byte[] otemp = new byte[ o.length ]; System.arraycopy( o, 0, otemp, 0, o.length ); rc4.write( o, result); 444 for( int i=19; i>=0; i-- ) 445 { 446 System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length ); 447 for( int j=0; j< iterationKey.length; j++ ) 448 { 449 iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i); 450 } 451 rc4.setKey( iterationKey ); 452 result.reset(); rc4.write( otemp, result ); otemp = result.toByteArray(); } 456 } 457 458 459 return result.toByteArray(); 460 461 } 462 catch( NoSuchAlgorithmException e ) 463 { 464 throw new CryptographyException( e ); 465 } 466 } 467 468 482 public final byte[] computeEncryptedKey( 483 byte[] password, 484 byte[] o, 485 int permissions, 486 byte[] id, 487 int encRevision, 488 int length ) 489 throws CryptographyException 490 { 491 byte[] result = new byte[ length ]; 492 try 493 { 494 byte[] padded = truncateOrPad( password ); 497 498 MessageDigest md = MessageDigest.getInstance("MD5"); 500 md.update( padded ); 501 502 md.update( o ); 504 505 byte zero = (byte)(permissions >>> 0); 507 byte one = (byte)(permissions >>> 8); 508 byte two = (byte)(permissions >>> 16); 509 byte three = (byte)(permissions >>> 24); 510 511 md.update( zero ); 512 md.update( one ); 513 md.update( two ); 514 md.update( three ); 515 516 md.update( id ); 518 byte[] digest = md.digest(); 519 520 if( encRevision == 3 || encRevision == 4) 522 { 523 for( int i=0; i<50; i++ ) 524 { 525 md.reset(); 526 md.update( digest, 0, length ); 527 digest = md.digest(); 528 } 529 } 530 531 if( encRevision == 2 && length != 5 ) 533 { 534 throw new CryptographyException( 535 "Error: length should be 5 when revision is two actual=" + length ); 536 } 537 System.arraycopy( digest, 0, result, 0, length ); 538 } 539 catch( NoSuchAlgorithmException e ) 540 { 541 throw new CryptographyException( e ); 542 } 543 return result; 544 } 545 546 561 562 public final byte[] computeUserPassword( 563 byte[] password, 564 byte[] o, 565 int permissions, 566 byte[] id, 567 int encRevision, 568 int length ) 569 throws CryptographyException, IOException 570 { 571 ByteArrayOutputStream result = new ByteArrayOutputStream (); 572 byte[] encryptionKey = computeEncryptedKey( password, o, permissions, id, encRevision, length ); 574 575 if( encRevision == 2 ) 576 { 577 rc4.setKey( encryptionKey ); 579 rc4.write( ENCRYPT_PADDING, result ); 580 } 581 else if( encRevision == 3 || encRevision == 4 ) 582 { 583 try 584 { 585 MessageDigest md = MessageDigest.getInstance("MD5"); 587 md.update( ENCRYPT_PADDING ); 589 590 md.update( id ); 592 result.write( md.digest() ); 593 594 byte[] iterationKey = new byte[ encryptionKey.length ]; 596 for( int i=0; i<20; i++ ) 597 { 598 System.arraycopy( encryptionKey, 0, iterationKey, 0, iterationKey.length ); 599 for( int j=0; j< iterationKey.length; j++ ) 600 { 601 iterationKey[j] = (byte)(iterationKey[j] ^ i); 602 } 603 rc4.setKey( iterationKey ); 604 ByteArrayInputStream input = new ByteArrayInputStream ( result.toByteArray() ); 605 result.reset(); 606 rc4.write( input, result ); 607 } 608 609 byte[] finalResult = new byte[32]; 611 System.arraycopy( result.toByteArray(), 0, finalResult, 0, 16 ); 612 System.arraycopy( ENCRYPT_PADDING, 0, finalResult, 16, 16 ); 613 result.reset(); 614 result.write( finalResult ); 615 } 616 catch( NoSuchAlgorithmException e ) 617 { 618 throw new CryptographyException( e ); 619 } 620 } 621 return result.toByteArray(); 622 } 623 624 637 public final byte[] computeOwnerPassword( 638 byte[] ownerPassword, 639 byte[] userPassword, 640 int encRevision, 641 int length ) 642 throws CryptographyException, IOException 643 { 644 try 645 { 646 byte[] ownerPadded = truncateOrPad( ownerPassword ); 648 649 MessageDigest md = MessageDigest.getInstance( "MD5" ); 651 md.update( ownerPadded ); 652 byte[] digest = md.digest(); 653 654 if( encRevision == 3 || encRevision == 4) 656 { 657 for( int i=0; i<50; i++ ) 658 { 659 md.reset(); 660 md.update( digest, 0, length ); 661 digest = md.digest(); 662 } 663 } 664 if( encRevision == 2 && length != 5 ) 665 { 666 throw new CryptographyException( 667 "Error: Expected length=5 actual=" + length ); 668 } 669 670 byte[] rc4Key = new byte[ length ]; 672 System.arraycopy( digest, 0, rc4Key, 0, length ); 673 674 byte[] paddedUser = truncateOrPad( userPassword ); 676 677 678 rc4.setKey( rc4Key ); 680 ByteArrayOutputStream crypted = new ByteArrayOutputStream (); 681 rc4.write( new ByteArrayInputStream ( paddedUser ), crypted ); 682 683 684 if( encRevision == 3 || encRevision == 4 ) 686 { 687 byte[] iterationKey = new byte[ rc4Key.length ]; 688 for( int i=1; i<20; i++ ) 689 { 690 System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length ); 691 for( int j=0; j< iterationKey.length; j++ ) 692 { 693 iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i); 694 } 695 rc4.setKey( iterationKey ); 696 ByteArrayInputStream input = new ByteArrayInputStream ( crypted.toByteArray() ); 697 crypted.reset(); 698 rc4.write( input, crypted ); 699 } 700 } 701 702 return crypted.toByteArray(); 704 } 705 catch( NoSuchAlgorithmException e ) 706 { 707 throw new CryptographyException( e.getMessage() ); 708 } 709 } 710 711 712 719 private final byte[] truncateOrPad( byte[] password ) 720 { 721 byte[] padded = new byte[ ENCRYPT_PADDING.length ]; 722 int bytesBeforePad = Math.min( password.length, padded.length ); 723 System.arraycopy( password, 0, padded, 0, bytesBeforePad ); 724 System.arraycopy( ENCRYPT_PADDING, 0, padded, bytesBeforePad, ENCRYPT_PADDING.length-bytesBeforePad ); 725 return padded; 726 } 727 728 744 public final boolean isUserPassword( 745 byte[] password, 746 byte[] u, 747 byte[] o, 748 int permissions, 749 byte[] id, 750 int encRevision, 751 int length) 752 throws CryptographyException, IOException 753 { 754 boolean matches = false; 755 byte[] computedValue = computeUserPassword( password, o, permissions, id, encRevision, length ); 757 if( encRevision == 2 ) 758 { 759 matches = arraysEqual( u, computedValue ); 761 } 762 else if( encRevision == 3 || encRevision == 4 ) 763 { 764 matches = arraysEqual( u, computedValue, 16 ); 766 } 767 return matches; 768 } 769 770 private static final boolean arraysEqual( byte[] first, byte[] second, int count ) 771 { 772 boolean equal = first.length >= count && second.length >= count; 773 for( int i=0; i<count && equal; i++ ) 774 { 775 equal = first[i] == second[i]; 776 } 777 return equal; 778 } 779 780 788 private static final boolean arraysEqual( byte[] first, byte[] second ) 789 { 790 boolean equal = first.length == second.length; 791 for( int i=0; i<first.length && equal; i++ ) 792 { 793 equal = first[i] == second[i]; 794 } 795 return equal; 796 } 797 } 798 | Popular Tags |