123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840 |
- /*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- /* $Id$ */
-
- package org.apache.fop.pdf;
-
- import java.io.IOException;
- import java.io.OutputStream;
- import java.io.UnsupportedEncodingException;
- import java.security.InvalidAlgorithmParameterException;
- import java.security.InvalidKeyException;
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import java.security.SecureRandom;
- import java.util.Arrays;
-
- import javax.crypto.BadPaddingException;
- import javax.crypto.Cipher;
- import javax.crypto.CipherOutputStream;
- import javax.crypto.IllegalBlockSizeException;
- import javax.crypto.NoSuchPaddingException;
- import javax.crypto.spec.IvParameterSpec;
- import javax.crypto.spec.SecretKeySpec;
-
- /**
- * An implementation of the Standard Security Handler.
- */
- public final class PDFEncryptionJCE extends PDFObject implements PDFEncryption {
-
- private final MessageDigest digest;
-
- private SecureRandom random;
-
- private byte[] encryptionKey;
-
- private String encryptionDictionary;
-
- private boolean useAlgorithm31a;
-
- private boolean encryptMetadata = true;
-
- private Version pdfVersion = Version.V1_4;
-
- private static byte[] ivZero = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-
- private class EncryptionInitializer {
-
- private final PDFEncryptionParams encryptionParams;
-
- private int encryptionLength;
-
- private int version;
-
- private int revision;
-
- EncryptionInitializer(PDFEncryptionParams params) {
- this.encryptionParams = new PDFEncryptionParams(params);
- }
-
- void init() {
- encryptionLength = encryptionParams.getEncryptionLengthInBits();
- determineEncryptionAlgorithm();
- int permissions = Permission.computePermissions(encryptionParams);
- EncryptionSettings encryptionSettings = new EncryptionSettings(
- encryptionLength, permissions,
- encryptionParams.getUserPassword(), encryptionParams.getOwnerPassword(),
- encryptionParams.encryptMetadata());
- InitializationEngine initializationEngine = createEngine(encryptionSettings);
- initializationEngine.run();
- encryptionDictionary = createEncryptionDictionary(permissions, initializationEngine);
- encryptMetadata = encryptionParams.encryptMetadata();
- }
-
- private InitializationEngine createEngine(EncryptionSettings encryptionSettings) {
- if (revision == 5) {
- return new Rev5Engine(encryptionSettings);
- } else if (revision == 2) {
- return new Rev2Engine(encryptionSettings);
- } else {
- return new Rev3Engine(encryptionSettings);
- }
- }
-
- private void determineEncryptionAlgorithm() {
- if (isVersion5Revision5Algorithm()) {
- version = 5;
- revision = 5;
- pdfVersion = Version.V1_7;
- } else if (isVersion1Revision2Algorithm()) {
- version = 1;
- revision = 2;
- } else {
- version = 2;
- revision = 3;
- }
- }
-
- private boolean isVersion1Revision2Algorithm() {
- return encryptionLength == 40
- && encryptionParams.isAllowFillInForms()
- && encryptionParams.isAllowAccessContent()
- && encryptionParams.isAllowAssembleDocument()
- && encryptionParams.isAllowPrintHq();
- }
-
- private boolean isVersion5Revision5Algorithm() {
- return encryptionLength == 256;
- }
-
- private String createEncryptionDictionary(final int permissions, InitializationEngine engine) {
- String encryptionDict = "<<\n"
- + "/Filter /Standard\n"
- + "/V " + version + "\n"
- + "/R " + revision + "\n"
- + "/Length " + encryptionLength + "\n"
- + "/P " + permissions + "\n"
- + engine.getEncryptionDictionaryPart()
- + ">>";
- return encryptionDict;
- }
-
- }
-
- private static enum Permission {
-
- PRINT(3),
- EDIT_CONTENT(4),
- COPY_CONTENT(5),
- EDIT_ANNOTATIONS(6),
- FILL_IN_FORMS(9),
- ACCESS_CONTENT(10),
- ASSEMBLE_DOCUMENT(11),
- PRINT_HQ(12);
-
- private final int mask;
-
- /**
- * Creates a new permission.
- *
- * @param bit bit position for this permission, 1-based to match the PDF Reference
- */
- private Permission(int bit) {
- mask = 1 << (bit - 1);
- }
-
- private int removeFrom(int permissions) {
- return permissions - mask;
- }
-
- static int computePermissions(PDFEncryptionParams encryptionParams) {
- int permissions = -4;
-
- if (!encryptionParams.isAllowPrint()) {
- permissions = PRINT.removeFrom(permissions);
- }
- if (!encryptionParams.isAllowCopyContent()) {
- permissions = COPY_CONTENT.removeFrom(permissions);
- }
- if (!encryptionParams.isAllowEditContent()) {
- permissions = EDIT_CONTENT.removeFrom(permissions);
- }
- if (!encryptionParams.isAllowEditAnnotations()) {
- permissions = EDIT_ANNOTATIONS.removeFrom(permissions);
- }
- if (!encryptionParams.isAllowFillInForms()) {
- permissions = FILL_IN_FORMS.removeFrom(permissions);
- }
- if (!encryptionParams.isAllowAccessContent()) {
- permissions = ACCESS_CONTENT.removeFrom(permissions);
- }
- if (!encryptionParams.isAllowAssembleDocument()) {
- permissions = ASSEMBLE_DOCUMENT.removeFrom(permissions);
- }
- if (!encryptionParams.isAllowPrintHq()) {
- permissions = PRINT_HQ.removeFrom(permissions);
- }
- return permissions;
- }
- }
-
- private static final class EncryptionSettings {
-
- final int encryptionLength;
-
- final int permissions;
-
- final String userPassword;
-
- final String ownerPassword;
-
- final boolean encryptMetadata;
-
- EncryptionSettings(int encryptionLength, int permissions,
- String userPassword, String ownerPassword, boolean encryptMetadata) {
- this.encryptionLength = encryptionLength;
- this.permissions = permissions;
- this.userPassword = userPassword;
- this.ownerPassword = ownerPassword;
- this.encryptMetadata = encryptMetadata;
- }
-
- }
-
- private abstract class InitializationEngine {
-
- protected final int encryptionLengthInBytes;
-
- protected final int permissions;
-
- private final String userPassword;
-
- private final String ownerPassword;
-
- protected byte[] oValue;
-
- protected byte[] uValue;
-
- protected byte[] preparedUserPassword;
-
- protected byte[] preparedOwnerPassword;
-
- InitializationEngine(EncryptionSettings encryptionSettings) {
- this.encryptionLengthInBytes = encryptionSettings.encryptionLength / 8;
- this.permissions = encryptionSettings.permissions;
- this.userPassword = encryptionSettings.userPassword;
- this.ownerPassword = encryptionSettings.ownerPassword;
- }
-
- void run() {
- preparedUserPassword = preparePassword(userPassword);
- if (ownerPassword == null || ownerPassword.length() == 0) {
- preparedOwnerPassword = preparedUserPassword;
- } else {
- preparedOwnerPassword = preparePassword(ownerPassword);
- }
- }
-
- protected String getEncryptionDictionaryPart() {
- String encryptionDictionaryPart = "/O " + PDFText.toHex(oValue) + "\n"
- + "/U " + PDFText.toHex(uValue) + "\n";
- return encryptionDictionaryPart;
- }
-
- protected abstract void computeOValue();
-
- protected abstract void computeUValue();
-
- protected abstract void createEncryptionKey();
-
- protected abstract byte[] preparePassword(String password);
- }
-
- private abstract class RevBefore5Engine extends InitializationEngine {
-
- /** Padding for passwords. */
- protected final byte[] padding = new byte[] {(byte) 0x28, (byte) 0xBF, (byte) 0x4E, (byte) 0x5E,
- (byte) 0x4E, (byte) 0x75, (byte) 0x8A, (byte) 0x41, (byte) 0x64, (byte) 0x00, (byte) 0x4E,
- (byte) 0x56, (byte) 0xFF, (byte) 0xFA, (byte) 0x01, (byte) 0x08, (byte) 0x2E, (byte) 0x2E,
- (byte) 0x00, (byte) 0xB6, (byte) 0xD0, (byte) 0x68, (byte) 0x3E, (byte) 0x80, (byte) 0x2F,
- (byte) 0x0C, (byte) 0xA9, (byte) 0xFE, (byte) 0x64, (byte) 0x53, (byte) 0x69, (byte) 0x7A};
-
- RevBefore5Engine(EncryptionSettings encryptionSettings) {
- super(encryptionSettings);
- }
-
- /**
- * Applies Algorithm 3.3 Page 79 of the PDF 1.4 Reference.
- *
- */
- protected void computeOValue() {
- // Step 1
- byte[] md5Input = preparedOwnerPassword;
- // Step 2
- digest.reset();
- byte[] hash = digest.digest(md5Input);
- // Step 3
- hash = computeOValueStep3(hash);
- // Step 4
- byte[] key = new byte[encryptionLengthInBytes];
- System.arraycopy(hash, 0, key, 0, encryptionLengthInBytes);
- // Steps 5, 6
- byte[] encryptionResult = encryptWithKey(key, preparedUserPassword);
- // Step 7
- oValue = computeOValueStep7(key, encryptionResult);
- }
-
- /**
- * Applies Algorithm 3.2 Page 78 of the PDF 1.4 Reference.
- */
- protected void createEncryptionKey() {
- // Steps 1, 2
- digest.reset();
- digest.update(preparedUserPassword);
- // Step 3
- digest.update(oValue);
- // Step 4
- digest.update((byte) (permissions >>> 0));
- digest.update((byte) (permissions >>> 8));
- digest.update((byte) (permissions >>> 16));
- digest.update((byte) (permissions >>> 24));
- // Step 5
- digest.update(getDocumentSafely().getFileIDGenerator().getOriginalFileID());
- byte[] hash = digest.digest();
- // Step 6
- hash = createEncryptionKeyStep6(hash);
- // Step 7
- encryptionKey = new byte[encryptionLengthInBytes];
- System.arraycopy(hash, 0, encryptionKey, 0, encryptionLengthInBytes);
- }
-
- /**
- * Adds padding to the password as directed in page 78 of the PDF 1.4 Reference.
- *
- * @param password the password
- * @return the password with additional padding if necessary
- */
- protected byte[] preparePassword(String password) {
- int finalLength = 32;
- byte[] preparedPassword = new byte[finalLength];
- try {
- byte[] passwordBytes = password.getBytes("UTF-8");
- if (passwordBytes.length >= finalLength) {
- System.arraycopy(passwordBytes, 0, preparedPassword, 0, finalLength);
- } else {
- System.arraycopy(passwordBytes, 0, preparedPassword, 0, passwordBytes.length);
- System.arraycopy(padding, 0, preparedPassword, passwordBytes.length, finalLength
- - passwordBytes.length);
- }
- return preparedPassword;
- } catch (UnsupportedEncodingException e) {
- throw new UnsupportedOperationException(e);
- }
- }
-
- void run() {
- super.run();
- computeOValue();
- createEncryptionKey();
- computeUValue();
- }
-
- protected abstract byte[] computeOValueStep3(byte[] hash);
-
- protected abstract byte[] computeOValueStep7(byte[] key, byte[] encryptionResult);
-
- protected abstract byte[] createEncryptionKeyStep6(byte[] hash);
-
- }
-
- private class Rev2Engine extends RevBefore5Engine {
-
- Rev2Engine(EncryptionSettings encryptionSettings) {
- super(encryptionSettings);
- }
-
- @Override
- protected byte[] computeOValueStep3(byte[] hash) {
- return hash;
- }
-
- @Override
- protected byte[] computeOValueStep7(byte[] key, byte[] encryptionResult) {
- return encryptionResult;
- }
-
- @Override
- protected byte[] createEncryptionKeyStep6(byte[] hash) {
- return hash;
- }
-
- @Override
- protected void computeUValue() {
- uValue = encryptWithKey(encryptionKey, padding);
- }
-
- }
-
- private class Rev3Engine extends RevBefore5Engine {
-
- Rev3Engine(EncryptionSettings encryptionSettings) {
- super(encryptionSettings);
- }
-
- @Override
- protected byte[] computeOValueStep3(byte[] hash) {
- for (int i = 0; i < 50; i++) {
- hash = digest.digest(hash);
- }
- return hash;
- }
-
- @Override
- protected byte[] computeOValueStep7(byte[] key, byte[] encryptionResult) {
- return xorKeyAndEncrypt19Times(key, encryptionResult);
- }
-
- @Override
- protected byte[] createEncryptionKeyStep6(byte[] hash) {
- for (int i = 0; i < 50; i++) {
- digest.update(hash, 0, encryptionLengthInBytes);
- hash = digest.digest();
- }
- return hash;
- }
-
- @Override
- protected void computeUValue() {
- // Step 1 is encryptionKey
- // Step 2
- digest.reset();
- digest.update(padding);
- // Step 3
- digest.update(getDocumentSafely().getFileIDGenerator().getOriginalFileID());
- // Step 4
- byte[] encryptionResult = encryptWithKey(encryptionKey, digest.digest());
- // Step 5
- encryptionResult = xorKeyAndEncrypt19Times(encryptionKey, encryptionResult);
- // Step 6
- uValue = new byte[32];
- System.arraycopy(encryptionResult, 0, uValue, 0, 16);
- // Add the arbitrary padding
- Arrays.fill(uValue, 16, 32, (byte) 0);
- }
-
- private byte[] xorKeyAndEncrypt19Times(byte[] key, byte[] input) {
- byte[] result = input;
- byte[] encryptionKey = new byte[key.length];
- for (int i = 1; i <= 19; i++) {
- for (int j = 0; j < key.length; j++) {
- encryptionKey[j] = (byte) (key[j] ^ i);
- }
- result = encryptWithKey(encryptionKey, result);
- }
- return result;
- }
-
- }
-
- private class Rev5Engine extends InitializationEngine {
-
- // private SecureRandom random = new SecureRandom();
- private byte[] userValidationSalt = new byte[8];
- private byte[] userKeySalt = new byte[8];
- private byte[] ownerValidationSalt = new byte[8];
- private byte[] ownerKeySalt = new byte[8];
- private byte[] ueValue;
- private byte[] oeValue;
- private final boolean encryptMetadata;
-
- Rev5Engine(EncryptionSettings encryptionSettings) {
- super(encryptionSettings);
- this.encryptMetadata = encryptionSettings.encryptMetadata;
- }
-
- void run() {
- super.run();
- random = new SecureRandom();
- createEncryptionKey();
- computeUValue();
- computeOValue();
- computeUEValue();
- computeOEValue();
- }
-
- protected String getEncryptionDictionaryPart() {
- String encryptionDictionaryPart = super.getEncryptionDictionaryPart();
- encryptionDictionaryPart += "/OE " + PDFText.toHex(oeValue) + "\n"
- + "/UE " + PDFText.toHex(ueValue) + "\n"
- + "/Perms " + PDFText.toHex(computePermsValue(permissions)) + "\n"
- + "/EncryptMetadata " + encryptMetadata + "\n"
- // note: I think Length below should be 256 but Acrobat 9 uses 32...
- + "/CF <</StdCF <</AuthEvent /DocOpen /CFM /AESV3 /Length 32>>>>\n"
- + "/StmF /StdCF /StrF /StdCF\n";
- return encryptionDictionaryPart;
- }
-
- /**
- * Algorithm 3.8-1 (page 20, Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3)
- */
- @Override
- protected void computeUValue() {
- byte[] userBytes = new byte[16];
- random.nextBytes(userBytes);
- System.arraycopy(userBytes, 0, userValidationSalt, 0, 8);
- System.arraycopy(userBytes, 8, userKeySalt, 0, 8);
- digest.reset();
- byte[] prepared = preparedUserPassword;
- byte[] concatenated = new byte[prepared.length + 8];
- System.arraycopy(prepared, 0, concatenated, 0, prepared.length);
- System.arraycopy(userValidationSalt, 0, concatenated, prepared.length, 8);
- digest.update(concatenated);
- byte[] sha256 = digest.digest();
- uValue = new byte[48];
- System.arraycopy(sha256, 0, uValue, 0, 32);
- System.arraycopy(userValidationSalt, 0, uValue, 32, 8);
- System.arraycopy(userKeySalt, 0, uValue, 40, 8);
- }
-
- /**
- * Algorithm 3.9-1 (page 20, Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3)
- */
- @Override
- protected void computeOValue() {
- byte[] ownerBytes = new byte[16];
- random.nextBytes(ownerBytes);
- System.arraycopy(ownerBytes, 0, ownerValidationSalt, 0, 8);
- System.arraycopy(ownerBytes, 8, ownerKeySalt, 0, 8);
- digest.reset();
- byte[] prepared = preparedOwnerPassword;
- byte[] concatenated = new byte[prepared.length + 56];
- System.arraycopy(prepared, 0, concatenated, 0, prepared.length);
- System.arraycopy(ownerValidationSalt, 0, concatenated, prepared.length, 8);
- System.arraycopy(uValue, 0, concatenated, prepared.length + 8, 48);
- digest.update(concatenated);
- byte[] sha256 = digest.digest();
- oValue = new byte[48];
- System.arraycopy(sha256, 0, oValue, 0, 32);
- System.arraycopy(ownerValidationSalt, 0, oValue, 32, 8);
- System.arraycopy(ownerKeySalt, 0, oValue, 40, 8);
- }
-
- /**
- * See Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3, page 20, paragraph 5.
- */
- protected void createEncryptionKey() {
- encryptionKey = new byte[encryptionLengthInBytes];
- random.nextBytes(encryptionKey);
- }
-
- /**
- * Algorithm 3.2a-1 (page 19, Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3)
- */
- protected byte[] preparePassword(String password) {
- byte[] passwordBytes;
- byte[] preparedPassword;
- try {
- // the password needs to be normalized first but we are bypassing that step for now
- passwordBytes = password.getBytes("UTF-8");
- if (passwordBytes.length > 127) {
- preparedPassword = new byte[127];
- System.arraycopy(passwordBytes, 0, preparedPassword, 0, 127);
- } else {
- preparedPassword = new byte[passwordBytes.length];
- System.arraycopy(passwordBytes, 0, preparedPassword, 0, passwordBytes.length);
- }
- return preparedPassword;
- } catch (UnsupportedEncodingException e) {
- throw new UnsupportedOperationException(e.getMessage());
- }
- }
-
- /**
- * Algorithm 3.8-2 (page 20, Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3)
- */
- private void computeUEValue() {
- digest.reset();
- byte[] prepared = preparedUserPassword;
- byte[] concatenated = new byte[prepared.length + 8];
- System.arraycopy(prepared, 0, concatenated, 0, prepared.length);
- System.arraycopy(userKeySalt, 0, concatenated, prepared.length, 8);
- digest.update(concatenated);
- byte[] ueEncryptionKey = digest.digest();
- ueValue = encryptWithKey(ueEncryptionKey, encryptionKey, true, ivZero);
- }
-
- /**
- * Algorithm 3.9-2 (page 20, Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3)
- */
- private void computeOEValue() {
- digest.reset();
- byte[] prepared = preparedOwnerPassword;
- byte[] concatenated = new byte[prepared.length + 56];
- System.arraycopy(prepared, 0, concatenated, 0, prepared.length);
- System.arraycopy(ownerKeySalt, 0, concatenated, prepared.length, 8);
- System.arraycopy(uValue, 0, concatenated, prepared.length + 8, 48);
- digest.update(concatenated);
- byte[] oeEncryptionKey = digest.digest();
- oeValue = encryptWithKey(oeEncryptionKey, encryptionKey, true, ivZero);
- }
-
- /**
- * Algorithm 3.10 (page 20, Adobe Supplement to the ISO 32000, BaseVersion: 1.7, ExtensionLevel: 3)
- */
- public byte[] computePermsValue(int permissions) {
- byte[] perms = new byte[16];
- long extendedPermissions = 0xffffffff00000000L | permissions;
- for (int k = 0; k < 8; k++) {
- perms[k] = (byte) (extendedPermissions & 0xff);
- extendedPermissions >>= 8;
- }
- if (encryptMetadata) {
- perms[8] = 'T';
- } else {
- perms[8] = 'F';
- }
- perms[9] = 'a';
- perms[10] = 'd';
- perms[11] = 'b';
- byte[] randomBytes = new byte[4];
- random.nextBytes(randomBytes);
- System.arraycopy(randomBytes, 0, perms, 12, 4);
- byte[] encryptedPerms = encryptWithKey(encryptionKey, perms, true, ivZero);
- return encryptedPerms;
- }
- }
-
- private class EncryptionFilter extends PDFFilter {
-
- private PDFObjectNumber streamNumber;
-
- private int streamGeneration;
-
- EncryptionFilter(PDFObjectNumber streamNumber, int streamGeneration) {
- this.streamNumber = streamNumber;
- this.streamGeneration = streamGeneration;
- }
-
- /**
- * Returns a PDF string representation of this filter.
- *
- * @return the empty string
- */
- public String getName() {
- return "";
- }
-
- /**
- * Returns a parameter dictionary for this filter.
- *
- * @return null, this filter has no parameters
- */
- public PDFObject getDecodeParms() {
- return null;
- }
-
- /** {@inheritDoc} */
- public OutputStream applyFilter(OutputStream out) throws IOException {
- if (useAlgorithm31a) {
- byte[] iv = new byte[16];
- random.nextBytes(iv);
- Cipher cipher = initCipher(encryptionKey, false, iv);
- out.write(iv);
- out.flush();
- return new CipherOutputStream(out, cipher);
- } else {
- byte[] key = createEncryptionKey(streamNumber.getNumber(), streamGeneration);
- Cipher cipher = initCipher(key);
- return new CipherOutputStream(out, cipher);
- }
- }
-
- }
-
- private PDFEncryptionJCE(PDFObjectNumber objectNumber, PDFEncryptionParams params, PDFDocument pdf) {
- setObjectNumber(objectNumber);
- try {
- if (params.getEncryptionLengthInBits() == 256) {
- digest = MessageDigest.getInstance("SHA-256");
- } else {
- digest = MessageDigest.getInstance("MD5");
- }
- } catch (NoSuchAlgorithmException e) {
- throw new UnsupportedOperationException(e.getMessage());
- }
- setDocument(pdf);
- EncryptionInitializer encryptionInitializer = new EncryptionInitializer(params);
- encryptionInitializer.init();
- useAlgorithm31a = encryptionInitializer.isVersion5Revision5Algorithm();
- }
-
- /**
- * Creates and returns an encryption object.
- *
- * @param objectNumber the object number for the encryption dictionary
- * @param params the encryption parameters
- * @param pdf the PDF document to be encrypted
- * @return the newly created encryption object
- */
- public static PDFEncryption make(
- PDFObjectNumber objectNumber, PDFEncryptionParams params, PDFDocument pdf) {
- return new PDFEncryptionJCE(objectNumber, params, pdf);
- }
-
- /** {@inheritDoc} */
- public byte[] encrypt(byte[] data, PDFObject refObj) {
- PDFObject o = refObj;
- while (o != null && !o.hasObjectNumber()) {
- o = o.getParent();
- }
- if (o == null && !useAlgorithm31a) {
- throw new IllegalStateException("No object number could be obtained for a PDF object");
- }
- if (useAlgorithm31a) {
- byte[] iv = new byte[16];
- random.nextBytes(iv);
- byte[] encryptedData = encryptWithKey(encryptionKey, data, false, iv);
- byte[] storedData = new byte[encryptedData.length + 16];
- System.arraycopy(iv, 0, storedData, 0, 16);
- System.arraycopy(encryptedData, 0, storedData, 16, encryptedData.length);
- return storedData;
- } else {
- byte[] key = createEncryptionKey(o.getObjectNumber().getNumber(), o.getGeneration());
- return encryptWithKey(key, data);
- }
- }
-
- /** {@inheritDoc} */
- public void applyFilter(AbstractPDFStream stream) {
- if (!encryptMetadata && stream instanceof PDFMetadata) {
- return;
- }
- stream.getFilterList().addFilter(
- new EncryptionFilter(stream.getObjectNumber(), stream.getGeneration()));
- }
-
- /**
- * Prepares the encryption dictionary for output to a PDF file.
- *
- * @return the encryption dictionary as a byte array
- */
- public byte[] toPDF() {
- assert encryptionDictionary != null;
- return encode(this.encryptionDictionary);
- }
-
- /** {@inheritDoc} */
- public String getTrailerEntry() {
- return "/Encrypt " + getObjectNumber() + " " + getGeneration() + " R\n";
- }
-
- private static byte[] encryptWithKey(byte[] key, byte[] data) {
- try {
- final Cipher c = initCipher(key);
- return c.doFinal(data);
- } catch (IllegalBlockSizeException e) {
- throw new IllegalStateException(e.getMessage());
- } catch (BadPaddingException e) {
- throw new IllegalStateException(e.getMessage());
- }
- }
-
- private static byte[] encryptWithKey(byte[] key, byte[] data, boolean noPadding, byte[] iv) {
- try {
- final Cipher c = initCipher(key, noPadding, iv);
- return c.doFinal(data);
- } catch (IllegalBlockSizeException e) {
- throw new IllegalStateException(e.getMessage());
- } catch (BadPaddingException e) {
- throw new IllegalStateException(e.getMessage());
- }
- }
-
- private static Cipher initCipher(byte[] key) {
- try {
- SecretKeySpec keyspec = new SecretKeySpec(key, "RC4");
- Cipher cipher = Cipher.getInstance("RC4");
- cipher.init(Cipher.ENCRYPT_MODE, keyspec);
- return cipher;
- } catch (InvalidKeyException e) {
- throw new IllegalStateException(e);
- } catch (NoSuchAlgorithmException e) {
- throw new UnsupportedOperationException(e);
- } catch (NoSuchPaddingException e) {
- throw new UnsupportedOperationException(e);
- }
- }
-
- private static Cipher initCipher(byte[] key, boolean noPadding, byte[] iv) {
- try {
- SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
- IvParameterSpec ivspec = new IvParameterSpec(iv);
- Cipher cipher = noPadding ? Cipher.getInstance("AES/CBC/NoPadding") : Cipher
- .getInstance("AES/CBC/PKCS5Padding");
- cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivspec);
- return cipher;
- } catch (InvalidKeyException e) {
- throw new IllegalStateException(e);
- } catch (NoSuchAlgorithmException e) {
- throw new UnsupportedOperationException(e);
- } catch (NoSuchPaddingException e) {
- throw new UnsupportedOperationException(e);
- } catch (InvalidAlgorithmParameterException e) {
- throw new UnsupportedOperationException(e);
- }
- }
-
- /**
- * Applies Algorithm 3.1 from the PDF 1.4 Reference.
- *
- * @param objectNumber the object number
- * @param generationNumber the generation number
- * @return the key to use for encryption
- */
- private byte[] createEncryptionKey(int objectNumber, int generationNumber) {
- // Step 1 passed in
- // Step 2
- byte[] md5Input = prepareMD5Input(objectNumber, generationNumber);
- // Step 3
- digest.reset();
- byte[] hash = digest.digest(md5Input);
- // Step 4
- int keyLength = Math.min(16, md5Input.length);
- byte[] key = new byte[keyLength];
- System.arraycopy(hash, 0, key, 0, keyLength);
- return key;
- }
-
- private byte[] prepareMD5Input(int objectNumber, int generationNumber) {
- byte[] md5Input = new byte[encryptionKey.length + 5];
- System.arraycopy(encryptionKey, 0, md5Input, 0, encryptionKey.length);
- int i = encryptionKey.length;
- md5Input[i++] = (byte) (objectNumber >>> 0);
- md5Input[i++] = (byte) (objectNumber >>> 8);
- md5Input[i++] = (byte) (objectNumber >>> 16);
- md5Input[i++] = (byte) (generationNumber >>> 0);
- md5Input[i++] = (byte) (generationNumber >>> 8);
- return md5Input;
- }
-
- /** {@inheritDoc} */
- public Version getPDFVersion() {
- return pdfVersion;
- }
-
- }
|