/*
 * 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: PDFEncryptionJCE.java 1661887 2015-02-24 11:23:44Z ssteiner $ */

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.
/** * 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.
Params:
  • bit – bit position for this permission, 1-based to match the PDF Reference
/** * 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.
/** 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.
/** * 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.
/** * 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.
Params:
  • password – the password
Returns:the password with additional padding if necessary
/** * 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)
/** * 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)
/** * 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.
/** * 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)
/** * 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)
/** * 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)
/** * 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)
/** * 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.
Returns:the empty string
/** * Returns a PDF string representation of this filter. * * @return the empty string */
public String getName() { return ""; }
Returns a parameter dictionary for this filter.
Returns:null, this filter has no parameters
/** * Returns a parameter dictionary for this filter. * * @return null, this filter has no parameters */
public PDFObject getDecodeParms() { return null; }
{@inheritDoc}
/** {@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.
Params:
  • objectNumber – the object number for the encryption dictionary
  • params – the encryption parameters
  • pdf – the PDF document to be encrypted
Returns:the newly created encryption object
/** * 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}
/** {@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}
/** {@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
/** * 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}
/** {@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.
Params:
  • objectNumber – the object number
  • generationNumber – the generation number
Returns:the key to use for encryption
/** * 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}
/** {@inheritDoc} */
public Version getPDFVersion() { return pdfVersion; } }