public byte[] generalEncryptionAlgorithm(Reference objectReference,
                                             byte[] encryptionKey,
                                             final String algorithmType,
                                             byte[] inputData) {

        if (objectReference == null || encryptionKey == null ||
                inputData == null) {
            // throw security exception
            return null;
        }

        // Algorithm 3.1, version 1-4
        if (encryptionDictionary.getVersion() < 5) {

            // RC4 or AES algorithm detection
            boolean isRc4 = algorithmType.equals(ENCRYPTION_TYPE_V2);

            // optimization, if the encryptionKey and objectReference are the
            // same there is no reason to calculate a new key.
            if (rc4Key == null || this.encryptionKey != encryptionKey ||
                    this.objectReference != objectReference) {

                this.objectReference = objectReference;

                // Step 1 to 3, bytes
                byte[] step3Bytes = resetObjectReference(objectReference, isRc4);

                // Step 4: Use the first (n+5) byes, up to a max of 16 from the MD5
                // hash
                int n = encryptionKey.length;
                rc4Key = new byte[Math.min(n + 5, 16)];
                System.arraycopy(step3Bytes, 0, rc4Key, 0, rc4Key.length);
            }

            // Set up an RC4 cipher and try to decrypt:
            byte[] finalData = null; // return data if all goes well
            try {
                // Use above as key for the RC4 encryption function.
                if (isRc4) {
                    // Use above as key for the RC4 encryption function.
                    SecretKeySpec key = new SecretKeySpec(rc4Key, "RC4");
                    Cipher rc4 = Cipher.getInstance("RC4");
                    rc4.init(Cipher.DECRYPT_MODE, key);

                    // finally add the stream or string data
                    finalData = rc4.doFinal(inputData);
                } else {
                    SecretKeySpec key = new SecretKeySpec(rc4Key, "AES");
                    Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");

                    // calculate 16 byte initialization vector.
                    byte[] initialisationVector = new byte[16];
                    System.arraycopy(inputData, 0, initialisationVector, 0, 16);

                    // trim the input
                    byte[] intermData = new byte[inputData.length - 16];
                    System.arraycopy(inputData, 16, intermData, 0, intermData.length);

                    final IvParameterSpec iVParameterSpec =
                            new IvParameterSpec(initialisationVector);

                    aes.init(Cipher.DECRYPT_MODE, key, iVParameterSpec);

                    // finally add the stream or string data
                    finalData = aes.doFinal(intermData);
                }

            } catch (NoSuchAlgorithmException ex) {
                logger.log(Level.FINE, "NoSuchAlgorithmException.", ex);
            } catch (IllegalBlockSizeException ex) {
                logger.log(Level.FINE, "IllegalBlockSizeException.", ex);
            } catch (BadPaddingException ex) {
                logger.log(Level.FINE, "BadPaddingException.", ex);
            } catch (NoSuchPaddingException ex) {
                logger.log(Level.FINE, "NoSuchPaddingException.", ex);
            } catch (InvalidKeyException ex) {
                logger.log(Level.FINE, "InvalidKeyException.", ex);
            } catch (InvalidAlgorithmParameterException ex) {
                logger.log(Level.FINE, "InvalidAlgorithmParameterException", ex);
            }

            return finalData;
        }
        // Algorithm 3.1a, version 5
        else if (encryptionDictionary.getVersion() == 5) {
            // Use the 32-byte file encryption key for the AES-256 symmetric
            // key algorithm, along with the string or stream data to be encrypted.

            // Use the AES algorithm in Cipher Block Chaining (CBC) mode, which
            // requires an initialization vector. The block size parameter is
            // set to 16 bytes, and the initialization vector is a 16-byte random
            // number that is stored as the first 16 bytes of the encrypted
            // stream or string.
            try {
                byte[] finalData = null;

                SecretKeySpec key = new SecretKeySpec(encryptionKey, "AES");
                Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");

                // calculate 16 byte initialization vector.
                byte[] initialisationVector = new byte[16];
                System.arraycopy(inputData, 0, initialisationVector, 0, 16);

                // trim the input
                byte[] intermData = new byte[inputData.length - 16];
                System.arraycopy(inputData, 16, intermData, 0, intermData.length);

                final IvParameterSpec iVParameterSpec =
                        new IvParameterSpec(initialisationVector);

                aes.init(Cipher.DECRYPT_MODE, key, iVParameterSpec);

                // finally add the stream or string data
                finalData = aes.doFinal(intermData);
                return finalData;

            } catch (NoSuchAlgorithmException ex) {
                logger.log(Level.FINE, "NoSuchAlgorithmException.", ex);
            } catch (IllegalBlockSizeException ex) {
                logger.log(Level.FINE, "IllegalBlockSizeException.", ex);
            } catch (BadPaddingException ex) {
                logger.log(Level.FINE, "BadPaddingException.", ex);
            } catch (NoSuchPaddingException ex) {
                logger.log(Level.FINE, "NoSuchPaddingException.", ex);
            } catch (InvalidKeyException ex) {
                logger.log(Level.FINE, "InvalidKeyException.", ex);
            } catch (InvalidAlgorithmParameterException ex) {
                logger.log(Level.FINE, "InvalidAlgorithmParameterException", ex);
            }
        }
        return null;
    }