001: /**
002: * Copyright (c) 2003-2005, 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.encryption;
031:
032: import java.io.ByteArrayInputStream;
033: import java.io.ByteArrayOutputStream;
034: import java.io.IOException;
035: import java.io.InputStream;
036: import java.io.OutputStream;
037:
038: import java.security.MessageDigest;
039: import java.security.NoSuchAlgorithmException;
040:
041: import org.pdfbox.exceptions.CryptographyException;
042:
043: /**
044: * This class will deal with PDF encryption algorithms.
045: *
046: * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a>
047: * @version $Revision: 1.15 $
048: *
049: * @deprecated use the new security layer instead
050: *
051: * @see org.pdfbox.pdmodel.encryption.StandardSecurityHandler
052: */
053: public final class PDFEncryption {
054: private ARCFour rc4 = new ARCFour();
055: /**
056: * The encryption padding defined in the PDF 1.4 Spec algorithm 3.2.
057: */
058: public static final byte[] ENCRYPT_PADDING = { (byte) 0x28,
059: (byte) 0xBF, (byte) 0x4E, (byte) 0x5E, (byte) 0x4E,
060: (byte) 0x75, (byte) 0x8A, (byte) 0x41, (byte) 0x64,
061: (byte) 0x00, (byte) 0x4E, (byte) 0x56, (byte) 0xFF,
062: (byte) 0xFA, (byte) 0x01, (byte) 0x08, (byte) 0x2E,
063: (byte) 0x2E, (byte) 0x00, (byte) 0xB6, (byte) 0xD0,
064: (byte) 0x68, (byte) 0x3E, (byte) 0x80, (byte) 0x2F,
065: (byte) 0x0C, (byte) 0xA9, (byte) 0xFE, (byte) 0x64,
066: (byte) 0x53, (byte) 0x69, (byte) 0x7A };
067:
068: /**
069: * This will encrypt a piece of data.
070: *
071: * @param objectNumber The id for the object.
072: * @param genNumber The generation id for the object.
073: * @param key The key used to encrypt the data.
074: * @param data The data to encrypt/decrypt.
075: * @param output The stream to write to.
076: *
077: * @throws CryptographyException If there is an error encrypting the data.
078: * @throws IOException If there is an io error.
079: */
080: public final void encryptData(long objectNumber, long genNumber,
081: byte[] key, InputStream data, OutputStream output)
082: throws CryptographyException, IOException {
083: byte[] newKey = new byte[key.length + 5];
084: System.arraycopy(key, 0, newKey, 0, key.length);
085: //PDF 1.4 reference pg 73
086: //step 1
087: //we have the reference
088:
089: //step 2
090: newKey[newKey.length - 5] = (byte) (objectNumber & 0xff);
091: newKey[newKey.length - 4] = (byte) ((objectNumber >> 8) & 0xff);
092: newKey[newKey.length - 3] = (byte) ((objectNumber >> 16) & 0xff);
093: newKey[newKey.length - 2] = (byte) (genNumber & 0xff);
094: newKey[newKey.length - 1] = (byte) ((genNumber >> 8) & 0xff);
095:
096: //step 3
097: byte[] digestedKey = null;
098: try {
099: MessageDigest md = MessageDigest.getInstance("MD5");
100: digestedKey = md.digest(newKey);
101: } catch (NoSuchAlgorithmException e) {
102: throw new CryptographyException(e);
103: }
104:
105: //step 4
106: int length = Math.min(newKey.length, 16);
107: byte[] finalKey = new byte[length];
108: System.arraycopy(digestedKey, 0, finalKey, 0, length);
109:
110: rc4.setKey(finalKey);
111: rc4.write(data, output);
112: output.flush();
113: }
114:
115: /**
116: * This will get the user password from the owner password and the documents o value.
117: *
118: * @param ownerPassword The plaintext owner password.
119: * @param o The document's o entry.
120: * @param revision The document revision number.
121: * @param length The length of the encryption.
122: *
123: * @return The plaintext padded user password.
124: *
125: * @throws CryptographyException If there is an error getting the user password.
126: * @throws IOException If there is an error reading data.
127: */
128: public final byte[] getUserPassword(byte[] ownerPassword, byte[] o,
129: int revision, long length) throws CryptographyException,
130: IOException {
131: try {
132: ByteArrayOutputStream result = new ByteArrayOutputStream();
133:
134: //3.3 STEP 1
135: byte[] ownerPadded = truncateOrPad(ownerPassword);
136:
137: //3.3 STEP 2
138: MessageDigest md = MessageDigest.getInstance("MD5");
139: md.update(ownerPadded);
140: byte[] digest = md.digest();
141:
142: //3.3 STEP 3
143: if (revision == 3 || revision == 4) {
144: for (int i = 0; i < 50; i++) {
145: md.reset();
146: md.update(digest);
147: digest = md.digest();
148: }
149: }
150: if (revision == 2 && length != 5) {
151: throw new CryptographyException(
152: "Error: Expected length=5 actual=" + length);
153: }
154:
155: //3.3 STEP 4
156: byte[] rc4Key = new byte[(int) length];
157: System.arraycopy(digest, 0, rc4Key, 0, (int) length);
158:
159: //3.7 step 2
160: if (revision == 2) {
161: rc4.setKey(rc4Key);
162: rc4.write(o, result);
163: } else if (revision == 3 || revision == 4) {
164: /**
165: byte[] iterationKey = new byte[ rc4Key.length ];
166: byte[] dataToEncrypt = o;
167: for( int i=19; i>=0; i-- )
168: {
169: System.arraycopy( rc4Key, 0, iterationKey, 0, rc4Key.length );
170: for( int j=0; j< iterationKey.length; j++ )
171: {
172: iterationKey[j] = (byte)(iterationKey[j] ^ (byte)i);
173: }
174: rc4.setKey( iterationKey );
175: rc4.write( dataToEncrypt, result );
176: dataToEncrypt = result.toByteArray();
177: result.reset();
178: }
179: result.write( dataToEncrypt, 0, dataToEncrypt.length );
180: */
181: byte[] iterationKey = new byte[rc4Key.length];
182:
183: byte[] otemp = new byte[o.length]; //sm
184: System.arraycopy(o, 0, otemp, 0, o.length); //sm
185: rc4.write(o, result);//sm
186:
187: for (int i = 19; i >= 0; i--) {
188: System.arraycopy(rc4Key, 0, iterationKey, 0,
189: rc4Key.length);
190: for (int j = 0; j < iterationKey.length; j++) {
191: iterationKey[j] = (byte) (iterationKey[j] ^ (byte) i);
192: }
193: rc4.setKey(iterationKey);
194: result.reset(); //sm
195: rc4.write(otemp, result); //sm
196: otemp = result.toByteArray(); //sm
197: }
198: }
199:
200: return result.toByteArray();
201:
202: } catch (NoSuchAlgorithmException e) {
203: throw new CryptographyException(e);
204: }
205: }
206:
207: /**
208: * This will tell if this is the owner password or not.
209: *
210: * @param ownerPassword The plaintext owner password.
211: * @param u The U value from the PDF Document.
212: * @param o The owner password hash.
213: * @param permissions The document permissions.
214: * @param id The document id.
215: * @param revision The revision of the encryption.
216: * @param length The length of the encryption key.
217: *
218: * @return true if the owner password matches the one from the document.
219: *
220: * @throws CryptographyException If there is an error while executing crypt functions.
221: * @throws IOException If there is an error while checking owner password.
222: */
223: public final boolean isOwnerPassword(byte[] ownerPassword,
224: byte[] u, byte[] o, int permissions, byte[] id,
225: int revision, int length) throws CryptographyException,
226: IOException {
227: byte[] userPassword = getUserPassword(ownerPassword, o,
228: revision, length);
229: return isUserPassword(userPassword, u, o, permissions, id,
230: revision, length);
231: }
232:
233: /**
234: * This will tell if this is a valid user password.
235: *
236: * Algorithm 3.6 pg 80
237: *
238: * @param password The password to test.
239: * @param u The U value from the PDF Document.
240: * @param o The owner password hash.
241: * @param permissions The document permissions.
242: * @param id The document id.
243: * @param revision The revision of the encryption.
244: * @param length The length of the encryption key.
245: *
246: * @return true If this is the correct user password.
247: *
248: * @throws CryptographyException If there is an error computing the value.
249: * @throws IOException If there is an IO error while computing the owners password.
250: */
251: public final boolean isUserPassword(byte[] password, byte[] u,
252: byte[] o, int permissions, byte[] id, int revision,
253: int length) throws CryptographyException, IOException {
254: boolean matches = false;
255: //STEP 1
256: byte[] computedValue = computeUserPassword(password, o,
257: permissions, id, revision, length);
258: if (revision == 2) {
259: //STEP 2
260: matches = arraysEqual(u, computedValue);
261: } else if (revision == 3 || revision == 4) {
262: //STEP 2
263: matches = arraysEqual(u, computedValue, 16);
264: }
265: return matches;
266: }
267:
268: /**
269: * This will compare two byte[] for equality for count number of bytes.
270: *
271: * @param first The first byte array.
272: * @param second The second byte array.
273: * @param count The number of bytes to compare.
274: *
275: * @return true If the arrays contain the exact same data.
276: */
277: private final boolean arraysEqual(byte[] first, byte[] second,
278: int count) {
279: boolean equal = first.length >= count && second.length >= count;
280: for (int i = 0; i < count && equal; i++) {
281: equal = first[i] == second[i];
282: }
283: return equal;
284: }
285:
286: /**
287: * This will compare two byte[] for equality.
288: *
289: * @param first The first byte array.
290: * @param second The second byte array.
291: *
292: * @return true If the arrays contain the exact same data.
293: */
294: private final boolean arraysEqual(byte[] first, byte[] second) {
295: boolean equal = first.length == second.length;
296: for (int i = 0; i < first.length && equal; i++) {
297: equal = first[i] == second[i];
298: }
299: return equal;
300: }
301:
302: /**
303: * This will compute the user password hash.
304: *
305: * @param password The plain text password.
306: * @param o The owner password hash.
307: * @param permissions The document permissions.
308: * @param id The document id.
309: * @param revision The revision of the encryption.
310: * @param length The length of the encryption key.
311: *
312: * @return The user password.
313: *
314: * @throws CryptographyException If there is an error computing the user password.
315: * @throws IOException If there is an IO error.
316: */
317: public final byte[] computeUserPassword(byte[] password, byte[] o,
318: int permissions, byte[] id, int revision, int length)
319: throws CryptographyException, IOException {
320: ByteArrayOutputStream result = new ByteArrayOutputStream();
321: //STEP 1
322: byte[] encryptionKey = computeEncryptedKey(password, o,
323: permissions, id, revision, length);
324:
325: if (revision == 2) {
326: //STEP 2
327: rc4.setKey(encryptionKey);
328: rc4.write(ENCRYPT_PADDING, result);
329: } else if (revision == 3 || revision == 4) {
330: try {
331: //STEP 2
332: MessageDigest md = MessageDigest.getInstance("MD5");
333: //md.update( truncateOrPad( password ) );
334: md.update(ENCRYPT_PADDING);
335:
336: //STEP 3
337: md.update(id);
338: result.write(md.digest());
339:
340: //STEP 4 and 5
341: byte[] iterationKey = new byte[encryptionKey.length];
342: for (int i = 0; i < 20; i++) {
343: System.arraycopy(encryptionKey, 0, iterationKey, 0,
344: iterationKey.length);
345: for (int j = 0; j < iterationKey.length; j++) {
346: iterationKey[j] = (byte) (iterationKey[j] ^ i);
347: }
348: rc4.setKey(iterationKey);
349: ByteArrayInputStream input = new ByteArrayInputStream(
350: result.toByteArray());
351: result.reset();
352: rc4.write(input, result);
353: }
354:
355: //step 6
356: byte[] finalResult = new byte[32];
357: System.arraycopy(result.toByteArray(), 0, finalResult,
358: 0, 16);
359: System.arraycopy(ENCRYPT_PADDING, 0, finalResult, 16,
360: 16);
361: result.reset();
362: result.write(finalResult);
363: } catch (NoSuchAlgorithmException e) {
364: throw new CryptographyException(e);
365: }
366: }
367: return result.toByteArray();
368: }
369:
370: /**
371: * This will compute the encrypted key.
372: *
373: * @param password The password used to compute the encrypted key.
374: * @param o The owner password hash.
375: * @param permissions The permissions for the document.
376: * @param id The document id.
377: * @param revision The security revision.
378: * @param length The length of the encryption key.
379: *
380: * @return The encryption key.
381: *
382: * @throws CryptographyException If there is an error computing the key.
383: */
384: public final byte[] computeEncryptedKey(byte[] password, byte[] o,
385: int permissions, byte[] id, int revision, int length)
386: throws CryptographyException {
387: byte[] result = new byte[length];
388: try {
389: //PDFReference 1.4 pg 78
390: //step1
391: byte[] padded = truncateOrPad(password);
392:
393: //step 2
394: MessageDigest md = MessageDigest.getInstance("MD5");
395: md.update(padded);
396:
397: //step 3
398: md.update(o);
399:
400: //step 4
401: byte zero = (byte) (permissions >>> 0);
402: byte one = (byte) (permissions >>> 8);
403: byte two = (byte) (permissions >>> 16);
404: byte three = (byte) (permissions >>> 24);
405:
406: md.update(zero);
407: md.update(one);
408: md.update(two);
409: md.update(three);
410:
411: //step 5
412: md.update(id);
413: byte[] digest = md.digest();
414:
415: //step 6
416: if (revision == 3 || revision == 4) {
417: for (int i = 0; i < 50; i++) {
418: md.reset();
419: md.update(digest, 0, length);
420: digest = md.digest();
421: }
422: }
423:
424: //step 7
425: if (revision == 2 && length != 5) {
426: throw new CryptographyException(
427: "Error: length should be 5 when revision is two actual="
428: + length);
429: }
430: System.arraycopy(digest, 0, result, 0, length);
431: } catch (NoSuchAlgorithmException e) {
432: throw new CryptographyException(e);
433: }
434: return result;
435: }
436:
437: /**
438: * This algorithm is taked from PDF Reference 1.4 Algorithm 3.3 Page 79.
439: *
440: * @param ownerPassword The plain owner password.
441: * @param userPassword The plain user password.
442: * @param revision The version of the security.
443: * @param length The length of the document.
444: *
445: * @return The computed owner password.
446: *
447: * @throws CryptographyException If there is an error computing O.
448: * @throws IOException If there is an error computing O.
449: */
450: public final byte[] computeOwnerPassword(byte[] ownerPassword,
451: byte[] userPassword, int revision, int length)
452: throws CryptographyException, IOException {
453: try {
454: //STEP 1
455: byte[] ownerPadded = truncateOrPad(ownerPassword);
456:
457: //STEP 2
458: MessageDigest md = MessageDigest.getInstance("MD5");
459: md.update(ownerPadded);
460: byte[] digest = md.digest();
461:
462: //STEP 3
463: if (revision == 3 || revision == 4) {
464: for (int i = 0; i < 50; i++) {
465: md.reset();
466: md.update(digest, 0, length);
467: digest = md.digest();
468: }
469: }
470: if (revision == 2 && length != 5) {
471: throw new CryptographyException(
472: "Error: Expected length=5 actual=" + length);
473: }
474:
475: //STEP 4
476: byte[] rc4Key = new byte[length];
477: System.arraycopy(digest, 0, rc4Key, 0, length);
478:
479: //STEP 5
480: byte[] paddedUser = truncateOrPad(userPassword);
481:
482: //STEP 6
483: rc4.setKey(rc4Key);
484: ByteArrayOutputStream crypted = new ByteArrayOutputStream();
485: rc4.write(new ByteArrayInputStream(paddedUser), crypted);
486:
487: //STEP 7
488: if (revision == 3 || revision == 4) {
489: byte[] iterationKey = new byte[rc4Key.length];
490: for (int i = 1; i < 20; i++) {
491: System.arraycopy(rc4Key, 0, iterationKey, 0,
492: rc4Key.length);
493: for (int j = 0; j < iterationKey.length; j++) {
494: iterationKey[j] = (byte) (iterationKey[j] ^ (byte) i);
495: }
496: rc4.setKey(iterationKey);
497: ByteArrayInputStream input = new ByteArrayInputStream(
498: crypted.toByteArray());
499: crypted.reset();
500: rc4.write(input, crypted);
501: }
502: }
503:
504: //STEP 8
505: return crypted.toByteArray();
506: } catch (NoSuchAlgorithmException e) {
507: throw new CryptographyException(e.getMessage());
508: }
509: }
510:
511: /**
512: * This will take the password and truncate or pad it as necessary.
513: *
514: * @param password The password to pad or truncate.
515: *
516: * @return The padded or truncated password.
517: */
518: private final byte[] truncateOrPad(byte[] password) {
519: byte[] padded = new byte[ENCRYPT_PADDING.length];
520: int bytesBeforePad = Math.min(password.length, padded.length);
521: System.arraycopy(password, 0, padded, 0, bytesBeforePad);
522: System.arraycopy(ENCRYPT_PADDING, 0, padded, bytesBeforePad,
523: ENCRYPT_PADDING.length - bytesBeforePad);
524: return padded;
525: }
526: }
|