001: /**
002: * Copyright (c) 2003-2006, www.pdfbox.org
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions are met:
007: *
008: * 1. Redistributions of source code must retain the above copyright notice,
009: * this list of conditions and the following disclaimer.
010: * 2. Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: * 3. Neither the name of pdfbox; nor the names of its
014: * contributors may be used to endorse or promote products derived from this
015: * software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
018: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
019: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
021: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: *
028: * http://www.pdfbox.org
029: *
030: */package org.pdfbox.pdmodel.encryption;
031:
032: import java.io.ByteArrayInputStream;
033: import java.io.ByteArrayOutputStream;
034: import java.io.IOException;
035: import java.math.BigInteger;
036: import java.security.MessageDigest;
037: import java.security.NoSuchAlgorithmException;
038:
039: import org.pdfbox.cos.COSArray;
040: import org.pdfbox.cos.COSString;
041: import org.pdfbox.encryption.ARCFour;
042: import org.pdfbox.exceptions.CryptographyException;
043: import org.pdfbox.pdmodel.PDDocument;
044:
045: /**
046: *
047: * The class implements the standard security handler as decribed
048: * in the PDF specifications. This security handler protects document
049: * with password.
050: *
051: * @see StandardProtectionPolicy to see how to protect document with this security handler.
052: *
053: * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
054: * @author Benoit Guillon (benoit.guillon@snv.jussieu.fr)
055: *
056: * @version $Revision: 1.3 $
057: */
058:
059: public class StandardSecurityHandler extends SecurityHandler {
060: /**
061: * Type of security handler.
062: */
063: public static final String FILTER = "Standard";
064:
065: private static final int DEFAULT_VERSION = 1;
066:
067: private static final int DEFAULT_REVISION = 3;
068:
069: private int revision = DEFAULT_REVISION;
070:
071: private StandardProtectionPolicy policy;
072:
073: private ARCFour rc4 = new ARCFour();
074:
075: /**
076: * Protection policy class for this handler.
077: */
078: public static final Class PROTECTION_POLICY_CLASS = StandardProtectionPolicy.class;
079:
080: /**
081: * Standard padding for encryption.
082: */
083: public static final byte[] ENCRYPT_PADDING = { (byte) 0x28,
084: (byte) 0xBF, (byte) 0x4E, (byte) 0x5E, (byte) 0x4E,
085: (byte) 0x75, (byte) 0x8A, (byte) 0x41, (byte) 0x64,
086: (byte) 0x00, (byte) 0x4E, (byte) 0x56, (byte) 0xFF,
087: (byte) 0xFA, (byte) 0x01, (byte) 0x08, (byte) 0x2E,
088: (byte) 0x2E, (byte) 0x00, (byte) 0xB6, (byte) 0xD0,
089: (byte) 0x68, (byte) 0x3E, (byte) 0x80, (byte) 0x2F,
090: (byte) 0x0C, (byte) 0xA9, (byte) 0xFE, (byte) 0x64,
091: (byte) 0x53, (byte) 0x69, (byte) 0x7A };
092:
093: /**
094: * Constructor.
095: */
096: public StandardSecurityHandler() {
097: }
098:
099: /**
100: * Constructor used for encryption.
101: *
102: * @param p The protection policy.
103: */
104: public StandardSecurityHandler(StandardProtectionPolicy p) {
105: policy = p;
106: keyLength = policy.getEncryptionKeyLength();
107: }
108:
109: /**
110: * Computes the version number of the StandardSecurityHandler
111: * regarding the encryption key length.
112: * See PDF Spec 1.6 p 93
113: *
114: * @return The computed cersion number.
115: */
116: private int computeVersionNumber() {
117: if (keyLength == 40) {
118: return DEFAULT_VERSION;
119: }
120: return 2;
121: }
122:
123: /**
124: * Computes the revision version of the StandardSecurityHandler to
125: * use regarding the version number and the permissions bits set.
126: * See PDF Spec 1.6 p98
127: *
128: * @return The computed revision number.
129: */
130: private int computeRevisionNumber() {
131: if (version == 2
132: && !policy.getPermissions().canFillInForm()
133: && !policy.getPermissions()
134: .canExtractForAccessibility()
135: && !policy.getPermissions().canPrintDegraded()) {
136: return 2;
137: }
138: return 3;
139: }
140:
141: /**
142: * Decrypt the document.
143: *
144: * @param doc The document to be decrypted.
145: * @param decryptionMaterial Information used to decrypt the document.
146: *
147: * @throws IOException If there is an error accessing data.
148: * @throws CryptographyException If there is an error with decryption.
149: */
150: public void decryptDocument(PDDocument doc,
151: DecryptionMaterial decryptionMaterial)
152: throws CryptographyException, IOException {
153: document = doc;
154:
155: PDEncryptionDictionary dictionary = document
156: .getEncryptionDictionary();
157: if (!(decryptionMaterial instanceof StandardDecryptionMaterial)) {
158: throw new CryptographyException(
159: "Provided decryption material is not compatible with the document");
160: }
161:
162: StandardDecryptionMaterial material = (StandardDecryptionMaterial) decryptionMaterial;
163:
164: String password = material.getPassword();
165: if (password == null) {
166: password = "";
167: }
168:
169: int dicPermissions = dictionary.getPermissions();
170: int dicRevision = dictionary.getRevision();
171: int dicLength = dictionary.getLength() / 8;
172:
173: COSString id = (COSString) document.getDocument()
174: .getDocumentID().getObject(0);
175: byte[] u = dictionary.getUserKey();
176: byte[] o = dictionary.getOwnerKey();
177:
178: boolean isUserPassword = isUserPassword(password.getBytes(), u,
179: o, dicPermissions, id.getBytes(), dicRevision,
180: dicLength);
181: boolean isOwnerPassword = isOwnerPassword(password.getBytes(),
182: u, o, dicPermissions, id.getBytes(), dicRevision,
183: dicLength);
184:
185: if (isUserPassword) {
186: encryptionKey = computeEncryptedKey(password.getBytes(), o,
187: dicPermissions, id.getBytes(), dicRevision,
188: dicLength);
189: } else if (isOwnerPassword) {
190: byte[] computedUserPassword = getUserPassword(password
191: .getBytes(), o, dicRevision, dicLength);
192: encryptionKey = computeEncryptedKey(computedUserPassword,
193: o, dicPermissions, id.getBytes(), dicRevision,
194: dicLength);
195: } else {
196: throw new CryptographyException(
197: "Error: The supplied password does not match either the owner or user password in the document.");
198: }
199:
200: this .proceedDecryption();
201: }
202:
203: /**
204: * Prepare document for encryption.
205: *
206: * @param doc The documeent to encrypt.
207: *
208: * @throws IOException If there is an error accessing data.
209: * @throws CryptographyException If there is an error with decryption.
210: */
211: public void prepareDocumentForEncryption(PDDocument doc)
212: throws CryptographyException, IOException {
213: document = doc;
214: PDEncryptionDictionary encryptionDictionary = document
215: .getEncryptionDictionary();
216: if (encryptionDictionary == null) {
217: encryptionDictionary = new PDEncryptionDictionary();
218: }
219: version = computeVersionNumber();
220: revision = computeRevisionNumber();
221: encryptionDictionary.setFilter(FILTER);
222: encryptionDictionary.setVersion(version);
223: encryptionDictionary.setRevision(revision);
224: encryptionDictionary.setLength(keyLength);
225:
226: String ownerPassword = policy.getOwnerPassword();
227: String userPassword = policy.getUserPassword();
228: if (ownerPassword == null) {
229: ownerPassword = "";
230: }
231: if (userPassword == null) {
232: userPassword = "";
233: }
234:
235: int permissionInt = policy.getPermissions()
236: .getPermissionBytes();
237:
238: encryptionDictionary.setPermissions(permissionInt);
239:
240: int length = keyLength / 8;
241:
242: COSArray idArray = document.getDocument().getDocumentID();
243:
244: //check if the document has an id yet. If it does not then
245: //generate one
246: if (idArray == null || idArray.size() < 2) {
247: idArray = new COSArray();
248: try {
249: MessageDigest md = MessageDigest.getInstance("MD5");
250: BigInteger time = BigInteger.valueOf(System
251: .currentTimeMillis());
252: md.update(time.toByteArray());
253: md.update(ownerPassword.getBytes());
254: md.update(userPassword.getBytes());
255: md.update(document.getDocument().toString().getBytes());
256: byte[] id = md.digest(this .toString().getBytes());
257: COSString idString = new COSString();
258: idString.append(id);
259: idArray.add(idString);
260: idArray.add(idString);
261: document.getDocument().setDocumentID(idArray);
262: } catch (NoSuchAlgorithmException e) {
263: throw new CryptographyException(e);
264: } catch (IOException e) {
265: throw new CryptographyException(e);
266: }
267: }
268:
269: COSString id = (COSString) idArray.getObject(0);
270:
271: byte[] o = computeOwnerPassword(ownerPassword
272: .getBytes("ISO-8859-1"), userPassword
273: .getBytes("ISO-8859-1"), revision, length);
274:
275: byte[] u = computeUserPassword(userPassword
276: .getBytes("ISO-8859-1"), o, permissionInt, id
277: .getBytes(), revision, length);
278:
279: encryptionKey = computeEncryptedKey(userPassword
280: .getBytes("ISO-8859-1"), o, permissionInt, id
281: .getBytes(), revision, length);
282:
283: encryptionDictionary.setOwnerKey(o);
284: encryptionDictionary.setUserKey(u);
285:
286: document.setEncryptionDictionary(encryptionDictionary);
287: document.getDocument().setEncryptionDictionary(
288: encryptionDictionary.getCOSDictionary());
289:
290: }
291:
292: /**
293: * Check for owner password.
294: *
295: * @param ownerPassword The owner password.
296: * @param u The u entry of the encryption dictionary.
297: * @param o The o entry of the encryption dictionary.
298: * @param permissions The set of permissions on the document.
299: * @param id The document id.
300: * @param encRevision The encryption algorithm revision.
301: * @param length The encryption key length.
302: *
303: * @return True If the ownerPassword param is the owner password.
304: *
305: * @throws CryptographyException If there is an error during encryption.
306: * @throws IOException If there is an error accessing data.
307: */
308: public final boolean isOwnerPassword(byte[] ownerPassword,
309: byte[] u, byte[] o, int permissions, byte[] id,
310: int encRevision, int length) throws CryptographyException,
311: IOException {
312: byte[] userPassword = getUserPassword(ownerPassword, o,
313: encRevision, length);
314: return isUserPassword(userPassword, u, o, permissions, id,
315: encRevision, length);
316: }
317:
318: /**
319: * Get the user password based on the owner password.
320: *
321: * @param ownerPassword The plaintext owner password.
322: * @param o The o entry of the encryption dictionary.
323: * @param encRevision The encryption revision number.
324: * @param length The key length.
325: *
326: * @return The u entry of the encryption dictionary.
327: *
328: * @throws CryptographyException If there is an error generating the user password.
329: * @throws IOException If there is an error accessing data while generating the user password.
330: */
331: public final byte[] getUserPassword(byte[] ownerPassword, byte[] o,
332: int encRevision, long length) throws CryptographyException,
333: IOException {
334: try {
335: ByteArrayOutputStream result = new ByteArrayOutputStream();
336:
337: //3.3 STEP 1
338: byte[] ownerPadded = truncateOrPad(ownerPassword);
339:
340: //3.3 STEP 2
341: MessageDigest md = MessageDigest.getInstance("MD5");
342: md.update(ownerPadded);
343: byte[] digest = md.digest();
344:
345: //3.3 STEP 3
346: if (encRevision == 3 || encRevision == 4) {
347: for (int i = 0; i < 50; i++) {
348: md.reset();
349: md.update(digest);
350: digest = md.digest();
351: }
352: }
353: if (encRevision == 2 && length != 5) {
354: throw new CryptographyException(
355: "Error: Expected length=5 actual=" + length);
356: }
357:
358: //3.3 STEP 4
359: byte[] rc4Key = new byte[(int) length];
360: System.arraycopy(digest, 0, rc4Key, 0, (int) length);
361:
362: //3.7 step 2
363: if (encRevision == 2) {
364: rc4.setKey(rc4Key);
365: rc4.write(o, result);
366: } else if (encRevision == 3 || encRevision == 4) {
367: /**
368: byte[] iterationKey = new byte[ rc4Key.length ];
369: byte[] dataToEncrypt = o;
370: for( int i=19; i>=0; i-- )
371: {
372: System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
373: for( int j=0; j< iterationKey.length; j++ )
374: {
375: iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
376: }
377: rc4.setKey( iterationKey );
378: rc4.write( dataToEncrypt, result );
379: dataToEncrypt = result.toByteArray();
380: result.reset();
381: }
382: result.write( dataToEncrypt, 0, dataToEncrypt.length );
383: */
384: byte[] iterationKey = new byte[rc4Key.length];
385:
386: byte[] otemp = new byte[o.length]; //sm
387: System.arraycopy(o, 0, otemp, 0, o.length); //sm
388: rc4.write(o, result);//sm
389:
390: for (int i = 19; i >= 0; i--) {
391: System.arraycopy(rc4Key, 0, iterationKey, 0,
392: rc4Key.length);
393: for (int j = 0; j < iterationKey.length; j++) {
394: iterationKey[j] = (byte) (iterationKey[j] ^ (byte) i);
395: }
396: rc4.setKey(iterationKey);
397: result.reset(); //sm
398: rc4.write(otemp, result); //sm
399: otemp = result.toByteArray(); //sm
400: }
401: }
402:
403: return result.toByteArray();
404:
405: } catch (NoSuchAlgorithmException e) {
406: throw new CryptographyException(e);
407: }
408: }
409:
410: /**
411: * Compute the encryption key.
412: *
413: * @param password The password to compute the encrypted key.
414: * @param o The o entry of the encryption dictionary.
415: * @param permissions The permissions for the document.
416: * @param id The document id.
417: * @param encRevision The revision of the encryption algorithm.
418: * @param length The length of the encryption key.
419: *
420: * @return The encrypted key bytes.
421: *
422: * @throws CryptographyException If there is an error with encryption.
423: */
424: public final byte[] computeEncryptedKey(byte[] password, byte[] o,
425: int permissions, byte[] id, int encRevision, int length)
426: throws CryptographyException {
427: byte[] result = new byte[length];
428: try {
429: //PDFReference 1.4 pg 78
430: //step1
431: byte[] padded = truncateOrPad(password);
432:
433: //step 2
434: MessageDigest md = MessageDigest.getInstance("MD5");
435: md.update(padded);
436:
437: //step 3
438: md.update(o);
439:
440: //step 4
441: byte zero = (byte) (permissions >>> 0);
442: byte one = (byte) (permissions >>> 8);
443: byte two = (byte) (permissions >>> 16);
444: byte three = (byte) (permissions >>> 24);
445:
446: md.update(zero);
447: md.update(one);
448: md.update(two);
449: md.update(three);
450:
451: //step 5
452: md.update(id);
453: byte[] digest = md.digest();
454:
455: //step 6
456: if (encRevision == 3 || encRevision == 4) {
457: for (int i = 0; i < 50; i++) {
458: md.reset();
459: md.update(digest, 0, length);
460: digest = md.digest();
461: }
462: }
463:
464: //step 7
465: if (encRevision == 2 && length != 5) {
466: throw new CryptographyException(
467: "Error: length should be 5 when revision is two actual="
468: + length);
469: }
470: System.arraycopy(digest, 0, result, 0, length);
471: } catch (NoSuchAlgorithmException e) {
472: throw new CryptographyException(e);
473: }
474: return result;
475: }
476:
477: /**
478: * This will compute the user password hash.
479: *
480: * @param password The plain text password.
481: * @param o The owner password hash.
482: * @param permissions The document permissions.
483: * @param id The document id.
484: * @param encRevision The revision of the encryption.
485: * @param length The length of the encryption key.
486: *
487: * @return The user password.
488: *
489: * @throws CryptographyException If there is an error computing the user password.
490: * @throws IOException If there is an IO error.
491: */
492:
493: public final byte[] computeUserPassword(byte[] password, byte[] o,
494: int permissions, byte[] id, int encRevision, int length)
495: throws CryptographyException, IOException {
496: ByteArrayOutputStream result = new ByteArrayOutputStream();
497: //STEP 1
498: byte[] encryptionKey = computeEncryptedKey(password, o,
499: permissions, id, encRevision, length);
500:
501: if (encRevision == 2) {
502: //STEP 2
503: rc4.setKey(encryptionKey);
504: rc4.write(ENCRYPT_PADDING, result);
505: } else if (encRevision == 3 || encRevision == 4) {
506: try {
507: //STEP 2
508: MessageDigest md = MessageDigest.getInstance("MD5");
509: //md.update( truncateOrPad( password ) );
510: md.update(ENCRYPT_PADDING);
511:
512: //STEP 3
513: md.update(id);
514: result.write(md.digest());
515:
516: //STEP 4 and 5
517: byte[] iterationKey = new byte[encryptionKey.length];
518: for (int i = 0; i < 20; i++) {
519: System.arraycopy(encryptionKey, 0, iterationKey, 0,
520: iterationKey.length);
521: for (int j = 0; j < iterationKey.length; j++) {
522: iterationKey[j] = (byte) (iterationKey[j] ^ i);
523: }
524: rc4.setKey(iterationKey);
525: ByteArrayInputStream input = new ByteArrayInputStream(
526: result.toByteArray());
527: result.reset();
528: rc4.write(input, result);
529: }
530:
531: //step 6
532: byte[] finalResult = new byte[32];
533: System.arraycopy(result.toByteArray(), 0, finalResult,
534: 0, 16);
535: System.arraycopy(ENCRYPT_PADDING, 0, finalResult, 16,
536: 16);
537: result.reset();
538: result.write(finalResult);
539: } catch (NoSuchAlgorithmException e) {
540: throw new CryptographyException(e);
541: }
542: }
543: return result.toByteArray();
544: }
545:
546: /**
547: * Compute the owner entry in the encryption dictionary.
548: *
549: * @param ownerPassword The plaintext owner password.
550: * @param userPassword The plaintext user password.
551: * @param encRevision The revision number of the encryption algorithm.
552: * @param length The length of the encryption key.
553: *
554: * @return The o entry of the encryption dictionary.
555: *
556: * @throws CryptographyException If there is an error with encryption.
557: * @throws IOException If there is an error accessing data.
558: */
559: public final byte[] computeOwnerPassword(byte[] ownerPassword,
560: byte[] userPassword, int encRevision, int length)
561: throws CryptographyException, IOException {
562: try {
563: //STEP 1
564: byte[] ownerPadded = truncateOrPad(ownerPassword);
565:
566: //STEP 2
567: MessageDigest md = MessageDigest.getInstance("MD5");
568: md.update(ownerPadded);
569: byte[] digest = md.digest();
570:
571: //STEP 3
572: if (encRevision == 3 || encRevision == 4) {
573: for (int i = 0; i < 50; i++) {
574: md.reset();
575: md.update(digest, 0, length);
576: digest = md.digest();
577: }
578: }
579: if (encRevision == 2 && length != 5) {
580: throw new CryptographyException(
581: "Error: Expected length=5 actual=" + length);
582: }
583:
584: //STEP 4
585: byte[] rc4Key = new byte[length];
586: System.arraycopy(digest, 0, rc4Key, 0, length);
587:
588: //STEP 5
589: byte[] paddedUser = truncateOrPad(userPassword);
590:
591: //STEP 6
592: rc4.setKey(rc4Key);
593: ByteArrayOutputStream crypted = new ByteArrayOutputStream();
594: rc4.write(new ByteArrayInputStream(paddedUser), crypted);
595:
596: //STEP 7
597: if (encRevision == 3 || encRevision == 4) {
598: byte[] iterationKey = new byte[rc4Key.length];
599: for (int i = 1; i < 20; i++) {
600: System.arraycopy(rc4Key, 0, iterationKey, 0,
601: rc4Key.length);
602: for (int j = 0; j < iterationKey.length; j++) {
603: iterationKey[j] = (byte) (iterationKey[j] ^ (byte) i);
604: }
605: rc4.setKey(iterationKey);
606: ByteArrayInputStream input = new ByteArrayInputStream(
607: crypted.toByteArray());
608: crypted.reset();
609: rc4.write(input, crypted);
610: }
611: }
612:
613: //STEP 8
614: return crypted.toByteArray();
615: } catch (NoSuchAlgorithmException e) {
616: throw new CryptographyException(e.getMessage());
617: }
618: }
619:
620: /**
621: * This will take the password and truncate or pad it as necessary.
622: *
623: * @param password The password to pad or truncate.
624: *
625: * @return The padded or truncated password.
626: */
627: private final byte[] truncateOrPad(byte[] password) {
628: byte[] padded = new byte[ENCRYPT_PADDING.length];
629: int bytesBeforePad = Math.min(password.length, padded.length);
630: System.arraycopy(password, 0, padded, 0, bytesBeforePad);
631: System.arraycopy(ENCRYPT_PADDING, 0, padded, bytesBeforePad,
632: ENCRYPT_PADDING.length - bytesBeforePad);
633: return padded;
634: }
635:
636: /**
637: * Check if a plaintext password is the user password.
638: *
639: * @param password The plaintext password.
640: * @param u The u entry of the encryption dictionary.
641: * @param o The o entry of the encryption dictionary.
642: * @param permissions The permissions set in the the PDF.
643: * @param id The document id used for encryption.
644: * @param encRevision The revision of the encryption algorithm.
645: * @param length The length of the encryption key.
646: *
647: * @return true If the plaintext password is the user password.
648: *
649: * @throws CryptographyException If there is an error during encryption.
650: * @throws IOException If there is an error accessing data.
651: */
652: public final boolean isUserPassword(byte[] password, byte[] u,
653: byte[] o, int permissions, byte[] id, int encRevision,
654: int length) throws CryptographyException, IOException {
655: boolean matches = false;
656: //STEP 1
657: byte[] computedValue = computeUserPassword(password, o,
658: permissions, id, encRevision, length);
659: if (encRevision == 2) {
660: //STEP 2
661: matches = arraysEqual(u, computedValue);
662: } else if (encRevision == 3 || encRevision == 4) {
663: //STEP 2
664: matches = arraysEqual(u, computedValue, 16);
665: }
666: return matches;
667: }
668:
669: private static final boolean arraysEqual(byte[] first,
670: byte[] second, int count) {
671: boolean equal = first.length >= count && second.length >= count;
672: for (int i = 0; i < count && equal; i++) {
673: equal = first[i] == second[i];
674: }
675: return equal;
676: }
677:
678: /**
679: * This will compare two byte[] for equality.
680: *
681: * @param first The first byte array.
682: * @param second The second byte array.
683: *
684: * @return true If the arrays contain the exact same data.
685: */
686: private static final boolean arraysEqual(byte[] first, byte[] second) {
687: boolean equal = first.length == second.length;
688: for (int i = 0; i < first.length && equal; i++) {
689: equal = first[i] == second[i];
690: }
691: return equal;
692: }
693: }
|