You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PDFEncryptionJCE.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  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. /* $Id$ */
  18. package org.apache.fop.pdf;
  19. // Java
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.OutputStream;
  23. import java.security.InvalidKeyException;
  24. import java.security.MessageDigest;
  25. import java.security.NoSuchAlgorithmException;
  26. import java.util.Random;
  27. import javax.crypto.BadPaddingException;
  28. import javax.crypto.Cipher;
  29. import javax.crypto.CipherOutputStream;
  30. import javax.crypto.IllegalBlockSizeException;
  31. import javax.crypto.NoSuchPaddingException;
  32. import javax.crypto.spec.SecretKeySpec;
  33. /**
  34. * class representing a /Filter /Standard object.
  35. *
  36. */
  37. public class PDFEncryptionJCE extends PDFObject implements PDFEncryption {
  38. private class EncryptionFilter extends PDFFilter {
  39. private PDFEncryptionJCE encryption;
  40. private int number;
  41. private int generation;
  42. /**
  43. * The constructor for the internal PDFEncryptionJCE filter
  44. * @param encryption The encryption object to use
  45. * @param number The number of the object to be encrypted
  46. * @param generation The generation of the object to be encrypted
  47. */
  48. public EncryptionFilter(PDFEncryptionJCE encryption,
  49. int number, int generation) {
  50. super();
  51. this.encryption = encryption;
  52. this.number = number;
  53. this.generation = generation;
  54. log.debug("new encryption filter for number "
  55. + number + " and generation " + generation);
  56. }
  57. /**
  58. * Return a PDF string representation of the filter. In this
  59. * case no filter name is passed.
  60. * @return The filter name, blank in this case
  61. */
  62. public String getName() {
  63. return "";
  64. }
  65. /**
  66. * Return a parameter dictionary for this filter, or null
  67. * @return The parameter dictionary. In this case, null.
  68. */
  69. public PDFObject getDecodeParms() {
  70. return null;
  71. }
  72. /**
  73. * Encode the given data with the filter
  74. * @param data The data to be encrypted
  75. * @return The encrypted data
  76. */
  77. public byte[] encode(byte[] data) {
  78. return encryption.encryptData(data, number, generation);
  79. }
  80. /**
  81. * {@inheritDoc}
  82. */
  83. public void encode(InputStream in, OutputStream out, int length)
  84. throws IOException {
  85. byte[] buffer = new byte[length];
  86. in.read(buffer);
  87. buffer = encode(buffer);
  88. out.write(buffer);
  89. }
  90. /**
  91. * {@inheritDoc}
  92. */
  93. public OutputStream applyFilter(OutputStream out) throws IOException {
  94. return new CipherOutputStream(out,
  95. encryption.initCipher(number, generation));
  96. }
  97. }
  98. private static final char [] PAD
  99. = {0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
  100. 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
  101. 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
  102. 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A};
  103. /** Value of PRINT permission */
  104. public static final int PERMISSION_PRINT = 4;
  105. /** Value of content editting permission */
  106. public static final int PERMISSION_EDIT_CONTENT = 8;
  107. /** Value of content extraction permission */
  108. public static final int PERMISSION_COPY_CONTENT = 16;
  109. /** Value of annotation editting permission */
  110. public static final int PERMISSION_EDIT_ANNOTATIONS = 32;
  111. // Encryption tools
  112. private MessageDigest digest = null;
  113. //private Cipher cipher = null;
  114. private Random random = new Random();
  115. // Control attributes
  116. private PDFEncryptionParams params;
  117. // Output attributes
  118. private byte[] fileID = null;
  119. private byte[] encryptionKey = null;
  120. private String dictionary = null;
  121. /**
  122. * Create a /Filter /Standard object.
  123. *
  124. * @param objnum the object's number
  125. */
  126. public PDFEncryptionJCE(int objnum) {
  127. /* generic creation of object */
  128. super();
  129. setObjectNumber(objnum);
  130. try {
  131. digest = MessageDigest.getInstance("MD5");
  132. //cipher = Cipher.getInstance("RC4");
  133. } catch (NoSuchAlgorithmException e) {
  134. throw new UnsupportedOperationException(e.getMessage());
  135. /*} catch (NoSuchPaddingException e) {
  136. throw new UnsupportedOperationException(e.getMessage());*/
  137. }
  138. }
  139. /**
  140. * Local factory method.
  141. * @param objnum PDF object number for the encryption object
  142. * @param params PDF encryption parameters
  143. * @return PDFEncryption the newly created PDFEncryption object
  144. */
  145. public static PDFEncryption make(int objnum, PDFEncryptionParams params) {
  146. PDFEncryptionJCE impl = new PDFEncryptionJCE(objnum);
  147. impl.setParams(params);
  148. impl.init();
  149. return impl;
  150. }
  151. /**
  152. * Returns the encryption parameters.
  153. * @return the encryption parameters
  154. */
  155. public PDFEncryptionParams getParams() {
  156. return this.params;
  157. }
  158. /**
  159. * Sets the encryption parameters.
  160. * @param params The parameterss to set
  161. */
  162. public void setParams(PDFEncryptionParams params) {
  163. this.params = params;
  164. }
  165. // Internal procedures
  166. private byte[] prepPassword(String password) {
  167. byte[] obuffer = new byte[32];
  168. byte[] pbuffer = password.getBytes();
  169. int i = 0;
  170. int j = 0;
  171. while (i < obuffer.length && i < pbuffer.length) {
  172. obuffer[i] = pbuffer[i];
  173. i++;
  174. }
  175. while (i < obuffer.length) {
  176. obuffer[i++] = (byte) PAD[j++];
  177. }
  178. return obuffer;
  179. }
  180. /**
  181. * Returns the document file ID
  182. * @return The file ID
  183. */
  184. public byte[] getFileID() {
  185. if (fileID == null) {
  186. fileID = new byte[16];
  187. random.nextBytes(fileID);
  188. }
  189. return fileID;
  190. }
  191. /**
  192. * This method returns the indexed file ID
  193. * @param index The index to access the file ID
  194. * @return The file ID
  195. */
  196. public String getFileID(int index) {
  197. if (index == 1) {
  198. return PDFText.toHex(getFileID());
  199. }
  200. byte[] id = new byte[16];
  201. random.nextBytes(id);
  202. return PDFText.toHex(id);
  203. }
  204. private byte[] encryptWithKey(byte[] data, byte[] key) {
  205. try {
  206. final Cipher c = initCipher(key);
  207. return c.doFinal(data);
  208. } catch (IllegalBlockSizeException e) {
  209. throw new IllegalStateException(e.getMessage());
  210. } catch (BadPaddingException e) {
  211. throw new IllegalStateException(e.getMessage());
  212. }
  213. }
  214. private Cipher initCipher(byte[] key) {
  215. try {
  216. Cipher c = Cipher.getInstance("RC4");
  217. SecretKeySpec keyspec = new SecretKeySpec(key, "RC4");
  218. c.init(Cipher.ENCRYPT_MODE, keyspec);
  219. return c;
  220. } catch (InvalidKeyException e) {
  221. throw new IllegalStateException(e.getMessage());
  222. } catch (NoSuchAlgorithmException e) {
  223. throw new UnsupportedOperationException(e.getMessage());
  224. } catch (NoSuchPaddingException e) {
  225. throw new UnsupportedOperationException(e.getMessage());
  226. }
  227. }
  228. private Cipher initCipher(int number, int generation) {
  229. byte[] hash = calcHash(number, generation);
  230. int size = hash.length;
  231. hash = digest.digest(hash);
  232. byte[] key = calcKey(hash, size);
  233. return initCipher(key);
  234. }
  235. private byte[] encryptWithHash(byte[] data, byte[] hash, int size) {
  236. hash = digest.digest(hash);
  237. byte[] key = calcKey(hash, size);
  238. return encryptWithKey(data, key);
  239. }
  240. private byte[] calcKey(byte[] hash, int size) {
  241. byte[] key = new byte[size];
  242. for (int i = 0; i < size; i++) {
  243. key[i] = hash[i];
  244. }
  245. return key;
  246. }
  247. /**
  248. * This method initializes the encryption algorithms and values
  249. */
  250. public void init() {
  251. // Generate the owner value
  252. byte[] oValue;
  253. if (params.getOwnerPassword().length() > 0) {
  254. oValue = encryptWithHash(
  255. prepPassword(params.getUserPassword()),
  256. prepPassword(params.getOwnerPassword()), 5);
  257. } else {
  258. oValue = encryptWithHash(
  259. prepPassword(params.getUserPassword()),
  260. prepPassword(params.getUserPassword()), 5);
  261. }
  262. // Generate permissions value
  263. int permissions = -4;
  264. if (!params.isAllowPrint()) {
  265. permissions -= PERMISSION_PRINT;
  266. }
  267. if (!params.isAllowCopyContent()) {
  268. permissions -= PERMISSION_COPY_CONTENT;
  269. }
  270. if (!params.isAllowEditContent()) {
  271. permissions -= PERMISSION_EDIT_CONTENT;
  272. }
  273. if (!params.isAllowEditAnnotations()) {
  274. permissions -= PERMISSION_EDIT_ANNOTATIONS;
  275. }
  276. // Create the encrption key
  277. digest.update(prepPassword(params.getUserPassword()));
  278. digest.update(oValue);
  279. digest.update((byte) (permissions >>> 0));
  280. digest.update((byte) (permissions >>> 8));
  281. digest.update((byte) (permissions >>> 16));
  282. digest.update((byte) (permissions >>> 24));
  283. digest.update(getFileID());
  284. byte [] hash = digest.digest();
  285. this.encryptionKey = new byte[5];
  286. for (int i = 0; i < 5; i++) {
  287. this.encryptionKey[i] = hash[i];
  288. }
  289. // Create the user value
  290. byte[] uValue = encryptWithKey(prepPassword(""), this.encryptionKey);
  291. // Create the dictionary
  292. this.dictionary = getObjectID()
  293. + "<< /Filter /Standard\n"
  294. + "/V 1\n"
  295. + "/R 2\n"
  296. + "/Length 40\n"
  297. + "/P " + permissions + "\n"
  298. + "/O " + PDFText.toHex(oValue) + "\n"
  299. + "/U " + PDFText.toHex(uValue) + "\n"
  300. + ">>\n"
  301. + "endobj\n";
  302. }
  303. /**
  304. * This method encrypts the passed data using the generated keys.
  305. * @param data The data to be encrypted
  306. * @param number The block number
  307. * @param generation The block generation
  308. * @return The encrypted data
  309. */
  310. public byte[] encryptData(byte[] data, int number, int generation) {
  311. if (this.encryptionKey == null) {
  312. throw new IllegalStateException("PDF Encryption has not been initialized");
  313. }
  314. byte[] hash = calcHash(number, generation);
  315. return encryptWithHash(data, hash, hash.length);
  316. }
  317. /** {@inheritDoc} */
  318. public byte[] encrypt(byte[] data, PDFObject refObj) {
  319. PDFObject o = refObj;
  320. while (o != null && !o.hasObjectNumber()) {
  321. o = o.getParent();
  322. }
  323. if (o == null) {
  324. throw new IllegalStateException("No object number could be obtained for a PDF object");
  325. }
  326. return encryptData(data, o.getObjectNumber(), o.getGeneration());
  327. }
  328. private byte[] calcHash(int number, int generation) {
  329. byte[] hash = new byte[this.encryptionKey.length + 5];
  330. int i = 0;
  331. while (i < this.encryptionKey.length) {
  332. hash[i] = this.encryptionKey[i]; i++;
  333. }
  334. hash[i++] = (byte) (number >>> 0);
  335. hash[i++] = (byte) (number >>> 8);
  336. hash[i++] = (byte) (number >>> 16);
  337. hash[i++] = (byte) (generation >>> 0);
  338. hash[i++] = (byte) (generation >>> 8);
  339. return hash;
  340. }
  341. /**
  342. * Creates PDFFilter for the encryption object
  343. * @param number The object number
  344. * @param generation The objects generation
  345. * @return The resulting filter
  346. */
  347. public PDFFilter makeFilter(int number, int generation) {
  348. return new EncryptionFilter(this, number, generation);
  349. }
  350. /**
  351. * Adds a PDFFilter to the PDFStream object
  352. * @param stream the stream to add an encryption filter to
  353. */
  354. public void applyFilter(AbstractPDFStream stream) {
  355. stream.getFilterList().addFilter(
  356. this.makeFilter(stream.getObjectNumber(), stream.getGeneration()));
  357. }
  358. /**
  359. * Represent the object in PDF
  360. *
  361. * @return the PDF
  362. */
  363. public byte[] toPDF() {
  364. if (this.dictionary == null) {
  365. throw new IllegalStateException("PDF Encryption has not been initialized");
  366. }
  367. return encode(this.dictionary);
  368. }
  369. /**
  370. * {@inheritDoc}
  371. */
  372. public String getTrailerEntry() {
  373. return "/Encrypt " + getObjectNumber() + " "
  374. + getGeneration() + " R\n"
  375. + "/ID[" + getFileID(1) + getFileID(2) + "]\n";
  376. }
  377. }