All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jpedal.io.security.DecryptionFactory Maven / Gradle / Ivy

/*
 * ===========================================
 * Java Pdf Extraction Decoding Access Library
 * ===========================================
 *
 * Project Info:  http://www.idrsolutions.com
 * Help section for developers at http://www.idrsolutions.com/support/
 *
 * (C) Copyright 1997-2017 IDRsolutions and Contributors.
 *
 * This file is part of JPedal/JPDF2HTML5
 *
     This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


 *
 * ---------------
 * DecryptionFactory.java
 * ---------------
 */
package org.jpedal.io.security;

import java.io.*;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.jpedal.constants.PDFflags;
import org.jpedal.exception.PdfSecurityException;
import org.jpedal.io.ObjectStore;
import org.jpedal.io.PdfFileReader;
import org.jpedal.io.PdfObjectFactory;
import org.jpedal.objects.raw.EncryptionObject;
import org.jpedal.objects.raw.PdfArrayIterator;
import org.jpedal.objects.raw.PdfDictionary;
import org.jpedal.objects.raw.PdfKeyPairsIterator;
import org.jpedal.objects.raw.PdfObject;
import org.jpedal.utils.LogWriter;
import org.jpedal.utils.ObjectCloneFactory;

/**
 * Provide AES/RSA decryption support
 */
public class DecryptionFactory {

    private Map cachedObjects = new HashMap();

    /**
     * flag to show if extraction allowed
     */
    private boolean extractionIsAllowed = true;

    /**
     * flag to show provider read
     */
    private boolean isInitialised;

    private boolean isMetaDataEncypted = true;

    /**
     * flag if password supplied
     */
    private boolean isPasswordSupplied;

    private boolean stringsEncoded;

    /**
     * flag to show data encrytped
     */
    private boolean isEncrypted;

    /**
     * key used for encryption
     */
    private byte[] encryptionKey;

    /**
     * revision used for encryption
     */
    private int rev;

    /**
     * P value in encryption
     */
    private int P;

    /**
     * O value in encryption
     */
    private byte[] O;

    /**
     * U value in encryption
     */
    private byte[] U;

    /**
     * additional 5 values
     */
    private byte[] OE, Perms, UE;

    //SecOP java ME - removed to remove additional package secop1_0.jar in java ME
    /**
     * cipher used for decryption
     */
    Cipher cipher;

    //show if AES encryption
    private boolean isAES;

    private PdfObject StmFObj, StrFObj;

    @SuppressWarnings("CanBeFinal")
    private static boolean alwaysReinitCipher;

    static {
        final String flag = System.getProperty("org.jpedal.cipher.reinit");
        if (flag != null && flag.equalsIgnoreCase("true")) {
            alwaysReinitCipher = true;
        }
    }

    /**
     * encryption padding
     */
    private final String[] pad = {"28", "BF", "4E", "5E", "4E", "75", "8A", "41", "64", "00", "4E", "56", "FF", "FA", "01", "08",
            "2E", "2E", "00", "B6", "D0", "68", "3E", "80", "2F", "0C", "A9", "FE", "64", "53", "69", "7A"};

    private boolean isAESIdentity;

    /**
     * length of encryption key used
     */
    private int keyLength = 5;

    /**
     * flag to show if user can view file
     */
    private boolean isFileViewable = true;

    //tell user status on password
    private int passwordStatus;

    /**
     * holds file ID
     */
    private final byte[] ID;


    /**
     * encryption password
     */
    private byte[] encryptionPassword;

    private Certificate certificate;

    private Key key;

    private BaseDecryption decryptionMethods;

    public DecryptionFactory(final byte[] ID, final byte[] encryptionPassword) {
        this.ID = ID;
        this.encryptionPassword = encryptionPassword;
    }

    /**
     * version for using public certificates
     *
     * @param id
     * @param certificate
     * @param key
     */
    public DecryptionFactory(final byte[] id, final Certificate certificate, final PrivateKey key) {
        this.ID = id;
        this.certificate = certificate;
        this.key = key;
    }

    /**
     * see if valid for password
     */
    private boolean testPassword(final byte[] valueToMatch) throws PdfSecurityException {

        int count = 32;

        final byte[] rawValue = new byte[32];
        byte[] keyValue;

        for (int i = 0; i < 32; i++) {
            rawValue[i] = (byte) Integer.parseInt(pad[i], 16);
        }

        byte[] encrypted = ObjectCloneFactory.cloneArray(rawValue);

        if (rev == 2) {
            encryptionKey = calculateKey(O, P, ID);
            encrypted = decrypt(encrypted, "", true, null, false, false);

        } else if (rev >= 3) {

            //use StmF values in preference
            final int keyLength = this.keyLength;

//            if(rev==4 && StmFObj!=null){
//                final int lenKey=StmFObj.getInt(PdfDictionary.Length);
//                if(lenKey!=-1) {
//                    keyLength = lenKey;
//                    if(keyLength>32){
//                        keyLength = keyLength >>3;
//                    }
//                }
//            }

            count = 16;
            encryptionKey = calculateKey(O, P, ID);
            final byte[] originalKey = ObjectCloneFactory.cloneArray(encryptionKey);

            MessageDigest md = null;
            try {
                md = MessageDigest.getInstance("MD5");
            } catch (final Exception e) {
                LogWriter.writeLog("Exception " + e + " with digest");
            }

            md.update(encrypted);

            //feed in ID
            keyValue = md.digest(ID);

            keyValue = decrypt(keyValue, "", true, null, true, false);

            final byte[] nextKey = new byte[keyLength];

            for (int i = 1; i <= 19; i++) {

                for (int j = 0; j < keyLength; j++) {
                    nextKey[j] = (byte) (originalKey[j] ^ i);
                }

                encryptionKey = nextKey;

                keyValue = decrypt(keyValue, "", true, null, true, false);

            }

            encryptionKey = originalKey;

            encrypted = new byte[32];
            System.arraycopy(keyValue, 0, encrypted, 0, 16);
            System.arraycopy(rawValue, 0, encrypted, 16, 16);

        }

        return compareKeys(valueToMatch, encrypted, count);

    }

    private static boolean compareKeys(final byte[] U, final byte[] encrypted, final int count) {

        boolean match = true;
        for (int i = 0; i < count; i++) {
            if (U[i] != encrypted[i]) {
                match = false;
                i = U.length;
            }
        }
        return match;
    }

    /**
     * set the key value
     */
    private void computeEncryptionKey() throws PdfSecurityException {

        final MessageDigest md;

        final byte[] key = getPaddedKey(encryptionPassword, encryptionPassword);

        try {

            // Obtain a message digest object.
            md = MessageDigest.getInstance("MD5");
            encryptionKey = md.digest(key);

            /*rev 3 extra security*/
            if (rev >= 3) {
                for (int ii = 0; ii < 50; ii++) {
                    encryptionKey = md.digest(encryptionKey);
                }
            }

        } catch (final Exception e) {
            throw new PdfSecurityException("Exception " + e + " generating encryption key");
        }
    }

    /**
     * see if valid for password
     */
    private boolean testOwnerPassword() throws PdfSecurityException {

        final byte[] originalPassword = encryptionPassword;

        byte[] userPasswd = new byte[keyLength];
        final byte[] inputValue = ObjectCloneFactory.cloneArray(O);


        computeEncryptionKey();

        final byte[] originalKey = ObjectCloneFactory.cloneArray(encryptionKey);

        if (rev == 2) {
            userPasswd = decrypt(ObjectCloneFactory.cloneArray(O), "", false, null, false, false);
        } else if (rev >= 3) {

            //use StmF values in preference
            final int keyLength = this.keyLength;
//            if(rev==4 && StmFObj!=null){
//                final int lenKey=StmFObj.getInt(PdfDictionary.Length);
//                if(lenKey!=-1) {
//                    keyLength = lenKey;
//                    if(keyLength>32){
//                        keyLength = keyLength >>3;
//                    }
//                }
//            }

            userPasswd = inputValue;
            final byte[] nextKey = new byte[keyLength];


            for (int i = 19; i >= 0; i--) {

                for (int j = 0; j < keyLength; j++) {
                    nextKey[j] = (byte) (originalKey[j] ^ i);
                }

                encryptionKey = nextKey;
                userPasswd = decrypt(userPasswd, "", false, null, true, false);

            }
        }

        //this value is the user password if correct
        //so test
        encryptionPassword = userPasswd;

        computeEncryptionKey();

        final boolean isMatch = testPassword(O);

        //put back to original if not in fact correct
        if (!isMatch) {
            encryptionPassword = originalPassword;
            computeEncryptionKey();
        }

        return isMatch;
    }

    /**
     * test password and set access settings
     */
    private void verifyAccess() throws PdfSecurityException {

        //assume false
        isPasswordSupplied = false;
        extractionIsAllowed = false;

        passwordStatus = PDFflags.NO_VALID_PASSWORD;

        /*workout if user or owner password valid*/
        boolean isOwnerPassword = false, isUserPassword = false;
        if (rev < 5) {
            isOwnerPassword = testOwnerPassword();
            isUserPassword = testPassword(U);
        } else { //v5 method very different so own routines to handle
            try {

                isOwnerPassword = compareKeys(O, getV5Key(true, 32), 32);

                if (isOwnerPassword) {
                    encryptionKey = v5Decrypt(OE, getV5Key(true, 32));
                } else { //try user
                    isUserPassword = compareKeys(U, getV5Key(false, 32), 32);
                    if (isUserPassword) //if not set throws error below
                    {
                        encryptionKey = v5Decrypt(UE, getV5Key(false, 40));
                    }

                }

            } catch (final NoSuchAlgorithmException e) {
                LogWriter.writeLog("Exception: " + e.getMessage());
            }
        }

        if (isOwnerPassword) {
            passwordStatus = PDFflags.VALID_OWNER_PASSWORD;
        }

        if (isUserPassword) {
            passwordStatus += PDFflags.VALID_USER_PASSWORD;
        }


        if (!isOwnerPassword || (isOwnerPassword && isUserPassword)) {

            /*test if user first*/
            if (isUserPassword) {

                //tell if not default value
                if (encryptionPassword != null && encryptionPassword.length > 0) {
                    LogWriter.writeLog("Correct user password supplied ");
                }

                isFileViewable = true;
                isPasswordSupplied = true;
                if ((P & 16) == 16) {
                    extractionIsAllowed = true;
                }

            } else {
                throw new PdfSecurityException("No valid password supplied");
            }

        } else {
            LogWriter.writeLog("Correct owner password supplied");

            isFileViewable = true;
            isPasswordSupplied = true;
            extractionIsAllowed = true;
        }
    }

    /**
     * workout key from OE or UE
     */
    private byte[] v5Decrypt(final byte[] rawValue, final byte[] key) throws PdfSecurityException {
        return decryptionMethods.v5Decrypt(rawValue, key);

    }

    private byte[] getV5Key(final boolean isOwner, final int offset) throws NoSuchAlgorithmException {

        //set password and ensure not null
        byte[] password = this.encryptionPassword;
        if (password == null) {
            password = new byte[0];
        }

        //ignore anything over 128
        int passwordLength = password.length;
        if (passwordLength > 127) {
            passwordLength = 127;
        }

        final MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(password, 0, passwordLength);

        if (isOwner) {
            md.update(O, offset, 8);
            md.update(U, 0, 48);
        } else {
            md.update(U, offset, 8);
        }

        return md.digest();
    }

    /**
     * routine to create a padded key
     */
    private byte[] getPaddedKey(final byte[] password, final byte[] encryptionPassword) {

        /*get 32 bytes for  the key*/
        final byte[] key = new byte[32];
        int passwordLength = 0;

        if (password != null) {
            passwordLength = password.length;
            if (passwordLength > 32) {
                passwordLength = 32;
            }
        }

        if (encryptionPassword != null) {
            System.arraycopy(encryptionPassword, 0, key, 0, passwordLength);
        }

        for (int ii = passwordLength; ii < 32; ii++) {

            key[ii] = (byte) Integer.parseInt(pad[ii - passwordLength], 16);

        }


        return key;
    }

    /**
     * calculate the key
     */
    private byte[] calculateKey(final byte[] O, final int P, final byte[] ID) throws PdfSecurityException {

        /*calculate key to use*/
        final byte[] key = getPaddedKey(encryptionPassword, encryptionPassword);
        final byte[] keyValue;

        /*feed into Md5 function*/
        try {

            // Obtain a message digest object.
            final MessageDigest md = MessageDigest.getInstance("MD5");

            //add in padded key
            md.update(key);

            //write in O value
            md.update(O);

            //P value
            md.update(new byte[]{(byte) ((P) & 0xff), (byte) ((P >> 8) & 0xff), (byte) ((P >> 16) & 0xff), (byte) ((P >> 24) & 0xff)});

            if (ID != null) {
                md.update(ID);
            }

            if (rev == 4 && !isMetaDataEncypted) {
                md.update(new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255});
            }

            final byte[] digest = new byte[keyLength];
            System.arraycopy(md.digest(), 0, digest, 0, keyLength);

            //for rev 3
            if (rev >= 3) {
                for (int i = 0; i < 50; ++i) {
                    System.arraycopy(md.digest(digest), 0, digest, 0, keyLength);
                }
            }

            keyValue = new byte[keyLength];
            System.arraycopy(digest, 0, keyValue, 0, keyLength);

        } catch (final Exception e) {

            LogWriter.writeLog("Exception: " + e.getMessage());

            throw new PdfSecurityException("Exception " + e + " generating encryption key");
        }

        /*put significant bytes into key*/
        final byte[] returnKey = new byte[keyLength];
        System.arraycopy(keyValue, 0, returnKey, 0, keyLength);

        return returnKey;
    }

    /**
     * extract  metadata for  encryption object
     */
    public void readEncryptionObject(final PdfObject encyptionObj, final PdfFileReader pdfFileReader) throws PdfSecurityException {

        //reset flags
        stringsEncoded = false;
        isMetaDataEncypted = true;
        StmFObj = null;
        StrFObj = null;
        isAES = false;

        if (!isInitialised) {
            isInitialised = true;
            SetSecurity.init();
        }

        if (SetSecurity.useBouncyCastle) {
            decryptionMethods = new BouncyCastleDecryption();
        } else {
            decryptionMethods = new JCADecryption();
        }

        //check type of filter and type and see if supported
        final int v = encyptionObj.getInt(PdfDictionary.V);

        //get filter value
        final PdfArrayIterator filters = encyptionObj.getMixedArray(PdfDictionary.Filter);
        int firstValue = PdfDictionary.Standard;
        if (filters != null && filters.hasMoreTokens()) {
            firstValue = filters.getNextValueAsConstant(false);
        }

        //throw exception if we have an unsupported encryption method
        if (v == 3) {
            throw new PdfSecurityException("Unsupported Custom Adobe Encryption method");
        } else if ((v > 4) && (firstValue != PdfDictionary.Standard)) {
            throw new PdfSecurityException("Unsupported Encryption method");
        }

        final int newLength = encyptionObj.getInt(PdfDictionary.Length) >> 3;
        if (newLength != -1) {
            this.keyLength = newLength;
        }

        //get rest of the values (which are not optional)
        rev = encyptionObj.getInt(PdfDictionary.R);
        P = encyptionObj.getInt(PdfDictionary.P);
        O = encyptionObj.getTextStreamValueAsByte(PdfDictionary.O);
        U = encyptionObj.getTextStreamValueAsByte(PdfDictionary.U);

        //used for v=5
        OE = encyptionObj.getTextStreamValueAsByte(PdfDictionary.OE);
        UE = encyptionObj.getTextStreamValueAsByte(PdfDictionary.UE);
        Perms = encyptionObj.getTextStreamValueAsByte(PdfDictionary.Perms);

        //get additional AES values
        if (v >= 4) {

            isAES = true;

            String CFkey;

            final PdfObject CF = encyptionObj.getDictionary(PdfDictionary.CF);

            //EFF=encyptionObj.getName(PdfDictionary.EFF);
            //CFM=encyptionObj.getName(PdfDictionary.CFM);

            if (v == 4) {
                isMetaDataEncypted = encyptionObj.getBoolean(PdfDictionary.EncryptMetadata);
            }

            //now set any specific crypt values for StrF (strings) and StmF (streams)
            isAESIdentity = false;
            String key = encyptionObj.getName(PdfDictionary.StrF);

            if (key != null) {

                isAESIdentity = key.equals("Identity");

                stringsEncoded = true;

                final PdfKeyPairsIterator keyPairs = CF.getKeyPairsIterator();

                while (keyPairs.hasMorePairs()) {

                    CFkey = keyPairs.getNextKeyAsString();

                    if (CFkey.equals(key)) {
                        StrFObj = PdfObjectFactory.getPDFObjectObjectFromRefOrDirect(new EncryptionObject(encyptionObj.getObjectRefAsString()), pdfFileReader, keyPairs.getNextValueAsBytes(), PdfDictionary.CF);
                    }

                    //roll on
                    keyPairs.nextPair();
                }
            }

            key = encyptionObj.getName(PdfDictionary.StmF);

            if (key != null) {

                isAESIdentity = key.equals("Identity");

                final PdfKeyPairsIterator keyPairs = CF.getKeyPairsIterator();

                while (keyPairs.hasMorePairs()) {

                    CFkey = keyPairs.getNextKeyAsString();

                    if (CFkey.equals(key)) {
                        StmFObj = PdfObjectFactory.getPDFObjectObjectFromRefOrDirect(new EncryptionObject(encyptionObj.getObjectRefAsString()), pdfFileReader, keyPairs.getNextValueAsBytes(), PdfDictionary.CF);
                    }

                    //roll on
                    keyPairs.nextPair();
                }
            }
        }

        isEncrypted = true;
        isFileViewable = false;

        LogWriter.writeLog("File has encryption settings");

        //test if encrypted with password (not certificate)
        if (firstValue == PdfDictionary.Standard) {
            try {
                verifyAccess();
            } catch (final PdfSecurityException e) {
                LogWriter.writeLog("File requires password " + e);
            }
        } else if (certificate != null) {

            /*
             * set flags and assume it will work correctly
             * (no validation at this point - error will be thrown in decrypt if not)
             */
            isFileViewable = true;
            isPasswordSupplied = true;
            extractionIsAllowed = true;

            passwordStatus = PDFflags.VALID_OWNER_PASSWORD;

        }

        //v5 stores this in Perms object and needs to be done after verify access
        if (rev == 5) {
            /*
            * now decode the permissions
            */
            Perms = v5Decrypt(Perms, encryptionKey);

            //see if metadata encrypted 
            isMetaDataEncypted = Perms[8] == 'T';

            //P set in Perms for v5
            P = (Perms[0] & 255) | ((Perms[1] & 255) << 8) | ((Perms[2] & 255) << 16) | ((Perms[2] & 255) << 24);
        }
    }

    /**
     * setup password value isung certificate passed in by User
     */
    private void setPasswordFromCertificate(final PdfObject AESObj) {

        /*
         * if recipients set, use that for calculating key
         */
        final byte[][] recipients = (AESObj.getStringArray(PdfDictionary.Recipients));

        if (recipients != null) {

            final byte[] envelopedData = extractCertificateData(recipients, certificate, key);

            /*
             * use match to create the key
             */
            if (envelopedData != null) {

                try {
                    final MessageDigest md = MessageDigest.getInstance("SHA-1");
                    md.update(envelopedData, 0, 20);
                    for (final byte[] recipient : recipients) {
                        md.update(recipient);
                    }

                    if (!isMetaDataEncypted) {
                        md.update(new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255});
                    }

                    encryptionKey = md.digest();
                } catch (final Exception e) {
                    LogWriter.writeLog("Exception: " + e.getMessage());
                }
            }
        }
    }

    /**
     * cycle through all possible values to find match (only tested with Bouncy castle)
     *
     * @param certificate
     * @param key
     */
    public byte[] extractCertificateData(final byte[][] recipients, final Certificate certificate, final Key key) {
        return decryptionMethods.readCertificate(recipients, certificate, key);
    }


    /**
     * reads the line/s from file which make up an object
     * includes move
     */
    public byte[] decrypt(byte[] data, final String ref, final boolean isEncryption,
                          final String cacheName, final boolean alwaysUseRC4,
                          final boolean isString) throws PdfSecurityException {

        //boolean debug=false; //ref.equals("100 0 R");

        if (getBooleanValue(PDFflags.IS_FILE_ENCRYPTED) || isEncryption) {

            BufferedOutputStream streamCache = null;
            BufferedInputStream bis = null;
            //int streamLength=0;

            boolean isAES = false;

            byte[] AESData = null;

            if (cacheName != null) { //this version is used if we cache large object to disk
                //rename file
                try {

                    //we may need bytes for key
                    if (data == null) {
                        AESData = new byte[16];
                        final FileInputStream fis = new FileInputStream(cacheName);
                        fis.read(AESData);
                        fis.close();
                    }

                    //streamLength = (int) new File(cacheName).length();

                    final File tempFile2 = File.createTempFile("jpedal", ".raw", new File(ObjectStore.temp_dir));

                    cachedObjects.put(tempFile2.getAbsolutePath(), "x");
                    //System.out.println(">>>"+tempFile2.getAbsolutePath());
                    ObjectStore.copy(cacheName, tempFile2.getAbsolutePath());

                    final File rawFile = new File(cacheName);
                    rawFile.delete();

                    //decrypt
                    streamCache = new BufferedOutputStream(new FileOutputStream(cacheName));
                    bis = new BufferedInputStream(new FileInputStream(tempFile2));

                } catch (final IOException e1) {
                    LogWriter.writeLog("Exception " + e1 + " in decrypt");
                }
            }

            //default values for rsa
            final int keyLength = this.keyLength;
            String algorithm = "RC4", keyType = "RC4";
            //SecOP java ME - removed to remove additional package secop1_0.jar in java ME
            IvParameterSpec ivSpec = null;

            //select for stream or string
            final PdfObject AESObj;
            if (!isString) {
                AESObj = StmFObj;
            } else {
                AESObj = StrFObj;
            }

            /*
             * reset each time as can change
             * (we can add flag later if slow)
             */
            if (certificate != null) {
                setPasswordFromCertificate(AESObj);

                //ensure value set so code below works
                AESObj.setIntNumber(PdfDictionary.Length, 16);
            }

            //AES identity
            if (!alwaysUseRC4 && AESObj == null && isAESIdentity) {
                return data;
            }

            //use RC4 as default but override if needed
            if (AESObj != null) {

                //use CF values in preference

//                final int AESLength=AESObj.getInt(PdfDictionary.Length);
//                if(AESLength!=-1) {
//                    keyLength = AESLength;
//                    if(keyLength>32){
//                        keyLength = AESLength >>3;
//                    }
//                }

                final String cryptName = AESObj.getName(PdfDictionary.CFM);

                if (cryptName != null && !alwaysUseRC4 && ((cryptName.equals("AESV2") || (cryptName.equals("AESV3"))))) {

                    cipher = null; //force reset as may be rsa

                    algorithm = "AES/CBC/PKCS5Padding";
                    keyType = "AES";

                    isAES = true;

                    //setup CBC
                    final byte[] iv = new byte[16];
                    if (AESData != null) {
                        System.arraycopy(AESData, 0, iv, 0, 16);
                    } else {
                        if (data.length >= 16) {
                            System.arraycopy(data, 0, iv, 0, 16);
                        } else {
                            return data;
                        }
                    }

                    //SecOP java ME - removed to remove additional package secop1_0.jar in java ME
                    ivSpec = new IvParameterSpec(iv);

                    //and knock off iv data in memory or cache
                    if (data == null) {
                        try {
                            bis.skip(16);
                        } catch (final IOException e) {
                            LogWriter.writeLog("Exception: " + e.getMessage());
                        }
                    } else {
                        final int origLen = data.length;
                        final int newLen = origLen - 16;
                        byte[] newData = new byte[newLen];
                        System.arraycopy(data, 16, newData, 0, newLen);
                        data = newData;

                        //make sure data correct size
                        final int diff = (data.length & 15);
                        int newLength = data.length;
                        if (diff > 0) {
                            newLength = newLength + 16 - diff;

                            newData = new byte[newLength];

                            System.arraycopy(data, 0, newData, 0, data.length);
                            data = newData;
                        }

                        if (rev == 5) {
                            try {
                                final byte[] finalKey = new byte[32];
                                System.arraycopy(encryptionKey, 0, finalKey, 0, finalKey.length);
                                return decodeAES(finalKey, data, iv);
                            } catch (final Exception e) {
                                throw new PdfSecurityException("Exception " + e + " decrypting content in AES revision 5");
                            }
                        }
                    }
                }
            }

            byte[] currentKey = new byte[keyLength];

            if (!ref.isEmpty()) {
                currentKey = new byte[keyLength + 5];
            }

            System.arraycopy(encryptionKey, 0, currentKey, 0, keyLength);

            try {

                final byte[] finalKey;
                if (rev == 5) {
                    finalKey = new byte[32];
                    System.arraycopy(currentKey, 0, finalKey, 0, finalKey.length);
                    // finalKey=currentKey;
                } else {
                    //add in Object ref id if any
                    if (!ref.isEmpty()) {
                        final int pointer = ref.indexOf(' ');
                        final int pointer2 = ref.indexOf(' ', pointer + 1);

                        final int obj = Integer.parseInt(ref.substring(0, pointer));
                        final int gen = Integer.parseInt(ref.substring(pointer + 1, pointer2));

                        currentKey[keyLength] = ((byte) (obj & 0xff));
                        currentKey[keyLength + 1] = ((byte) ((obj >> 8) & 0xff));
                        currentKey[keyLength + 2] = ((byte) ((obj >> 16) & 0xff));
                        currentKey[keyLength + 3] = ((byte) (gen & 0xff));
                        currentKey[keyLength + 4] = ((byte) ((gen >> 8) & 0xff));
                    }

                    finalKey = new byte[Math.min(currentKey.length, 16)];

                    if (!ref.isEmpty()) {
                        final MessageDigest currentDigest = MessageDigest.getInstance("MD5");
                        currentDigest.update(currentKey);

                        //add in salt
                        if (isAES && keyLength >= 16) {
                            final byte[] salt = {(byte) 0x73, (byte) 0x41, (byte) 0x6c, (byte) 0x54};

                            currentDigest.update(salt);
                        }
                        System.arraycopy(currentDigest.digest(), 0, finalKey, 0, finalKey.length);
                    } else {
                        System.arraycopy(currentKey, 0, finalKey, 0, finalKey.length);
                    }
                }

                //SecOP java ME - removed to remove additional package secop1_0.jar in java ME
                /* only initialise once - seems to take a long time*/
                if (cipher == null) {
                    cipher = Cipher.getInstance(algorithm);
                }

                final SecretKey testKey = new SecretKeySpec(finalKey, keyType);

                if (isEncryption) {
                    cipher.init(Cipher.ENCRYPT_MODE, testKey);
                } else {
                    if (ivSpec == null) {
                        cipher.init(Cipher.DECRYPT_MODE, testKey);
                    } else //aes
                    {
                        cipher.init(Cipher.DECRYPT_MODE, testKey, ivSpec);
                    }
                }

                //if data on disk read a byte at a time and write back

                if (streamCache != null) {
                    final CipherInputStream cis = new CipherInputStream(bis, cipher);
                    int nextByte;
                    while (true) {
                        nextByte = cis.read();
                        if (nextByte == -1) {
                            break;
                        }
                        streamCache.write(nextByte);
                    }
                    cis.close();
                    streamCache.close();
                    bis.close();

                }

                if (data != null) {
                    data = cipher.doFinal(data);
                }

            } catch (final Exception e) {
                throw new PdfSecurityException("Exception " + e + " decrypting content");
            }
        }

        //SecOP java ME - removed to remove additional package secop1_0.jar in java ME
        if (alwaysReinitCipher) {
            cipher = null;
        }

        return data;
    }

    /**
     * show if file can be displayed
     */
    public boolean getBooleanValue(final int key) {

        switch (key) {
            case PDFflags.IS_FILE_VIEWABLE:
                return isFileViewable;

            case PDFflags.IS_FILE_ENCRYPTED:
                return isEncrypted;

            case PDFflags.IS_METADATA_ENCRYPTED:
                return isMetaDataEncypted;

            case PDFflags.IS_EXTRACTION_ALLOWED:
                return extractionIsAllowed;

            case PDFflags.IS_PASSWORD_SUPPLIED:
                return isPasswordSupplied;
        }


        return false;
    }

    public boolean isAES() {
        return isAES;
    }

    public byte[] decryptString(byte[] newString, final String objectRef) throws PdfSecurityException {

        try {
            if ((!isAES || stringsEncoded || isMetaDataEncypted)) {
                newString = decrypt(newString, objectRef, false, null, false, true);
            }
        } catch (final Exception e) {
            LogWriter.writeLog("Unable to decrypt string in Object " + objectRef + ' ' + new String(newString) + ' ' + e);
        }

        return newString;

    }

    public int getPDFflag(final Integer flag) {

        if (flag.equals(PDFflags.USER_ACCESS_PERMISSIONS)) {
            return P;
        } else if (flag.equals(PDFflags.VALID_PASSWORD_SUPPLIED)) {
            return passwordStatus;
        } else {
            return -1;
        }
    }

    public void reset(final byte[] encryptionPassword) {

        this.encryptionPassword = encryptionPassword;

        //SecOP java ME - removed to remove additional package secop1_0.jar in java ME
        //reset
        cipher = null;
    }

    public void flush() {

        if (cachedObjects != null) {
            for (final String o : cachedObjects.keySet()) {
                final String fileName = o;
                final File file = new File(fileName);
                //System.out.println("PdfFileReader - deleting file "+fileName);
                file.delete();
            }
        }
    }

    public void dispose() {
        this.cachedObjects = null;

    }

    /**
     * show if U or O value present
     *
     * @return
     */
    public boolean hasPassword() {
        return O != null || U != null;
    }

    /**
     * decode AES ecnoded data with IV parameters
     *
     * @param encKey
     * @param encData a data gained from deducting IV bytes in beginning (encData = data - ivBytes)
     * @param ivData
     * @return
     * @throws Exception
     */
    private byte[] decodeAES(final byte[] encKey, final byte[] encData, final byte[] ivData) throws Exception {
        return decryptionMethods.decodeAES(encKey, encData, ivData);
    }

    public void setCipherNull() {
        cipher = null;
    }

    public byte[] getEncHash() {
        return encryptionPassword;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy