public InputStream generalEncryptionInputStream(
            Reference objectReference,
            byte[] encryptionKey,
            final String algorithmType,
            InputStream input) {
        if (objectReference == null || encryptionKey == null || input == 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:
            try {
                // Use above as key for the RC4 encryption function.
                if (isRc4) {
                    SecretKeySpec key = new SecretKeySpec(rc4Key, "RC4");
                    Cipher rc4 = Cipher.getInstance("RC4");
                    rc4.init(Cipher.DECRYPT_MODE, key);

                    // finally add the stream or string data
                    CipherInputStream cin = new CipherInputStream(input, rc4);
                    return cin;
                }
                // use above a key for the AES encryption function.
                else {
                    SecretKeySpec key = new SecretKeySpec(rc4Key, "AES");
                    Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");

                    // calculate 16 byte initialization vector.
                    byte[] initialisationVector = new byte[16];
                    input.read(initialisationVector);

                    final IvParameterSpec iVParameterSpec =
                            new IvParameterSpec(initialisationVector);

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

                    // finally add the stream or string data
                    CipherInputStream cin = new CipherInputStream(input, aes);
                    return cin;
                }
            } catch (NoSuchAlgorithmException ex) {
                logger.log(Level.FINE, "NoSuchAlgorithmException.", 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);
            } catch (IOException ex) {
                logger.log(Level.FINE, "InvalidAlgorithmParameterException", ex);
            }
        }
        // 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 {
                // use above a key for the AES encryption function.
                SecretKeySpec key = new SecretKeySpec(encryptionKey, "AES");
                Cipher aes = Cipher.getInstance("AES/CBC/PKCS5Padding");

                // calculate 16 byte initialization vector.
                byte[] initialisationVector = new byte[16];
                input.read(initialisationVector);

                final IvParameterSpec iVParameterSpec =
                        new IvParameterSpec(initialisationVector);

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

                // finally add the stream or string data
                CipherInputStream cin = new CipherInputStream(input, aes);
                return cin;

            } catch (NoSuchAlgorithmException ex) {
                logger.log(Level.FINE, "NoSuchAlgorithmException.", 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);
            } catch (IOException ex) {
                logger.log(Level.FINE, "InvalidAlgorithmParameterException", ex);
            }
        }
        return null;
    }