private byte[] calculateGeneralEncryptionKey(
            byte[] userPassword, byte[] firstDocIdValue, int keyBitLength,
            int revision, byte[] oValue, int pValue, boolean encryptMetadata)
            throws GeneralSecurityException {

        // Algorithm 3.2: Computing an encryption key

        // Step 1: Pad or truncate the password string to exactly 32 bytes...
        final byte[] paddedPassword = padPassword(userPassword);

        // Step 2: Initialize the MD5 hash function and pass the result of step
        // 1 as input to this function.
        MessageDigest md5 = createMD5Digest();
        md5.reset();
        md5.update(paddedPassword);

        // Step 3: Pass the value of the encryption dictionaryâs O entry to the
        // MD5 hash function. (Algorithm 3.3 shows how the O value is computed.)
        md5.update(oValue);

        // Step 4: Treat the value of the P entry as an unsigned 4-byte integer
        // and pass these bytes to the MD5 hash function, low-order byte first
        md5.update((byte) (pValue & 0xFF));
        md5.update((byte) ((pValue >> 8) & 0xFF));
        md5.update((byte) ((pValue >> 16) & 0xFF));
        md5.update((byte) (pValue >> 24));

        // Step 5: Pass the first element of the fileâs file identifier array
        // (the value of the ID entry in the documentâs trailer dictionary; see
        // Table 3.13 on page 97) to the MD5 hash function. (See implementation
        // note 26 in Appendix H.)
        if (firstDocIdValue != null) {
            md5.update(firstDocIdValue);
        }

        // Step 6: (Revision 4 or greater) If document metadata is not being
        // encrypted, pass 4 bytes with the value 0xFFFFFFFF to the MD5 hash
        // function
        if (revision >= 4 && !encryptMetadata) {
            for (int i = 0; i < 4; ++i) {
                md5.update((byte) 0xFF);
            }
        }

        // Step 7: finish the hash
        byte[] hash = md5.digest();

        final int keyLen = revision == 2 ? 5 : (keyBitLength / 8);
        final byte[] key = new byte[keyLen];

        // Step 8: (Revision 3 or greater) Do the following 50 times: Take the
        // output from the previous MD5 hash and pass the first n bytes of the
        // output as input into a new MD5 hash, where n is the number of bytes
        // of the encryption key as defined by the value of the encryption
        // dictionaryâs Length entry
        if (revision >= 3) {
            for (int i = 0; i < 50; ++i) {
                md5.update(hash, 0, key.length);
                digestTo(md5, hash);
            }
        }

        // Set the encryption key to the first n bytes of the output from the
        // final MD5 hash, where n is always 5 for revision 2 but, for revision
        // 3 or greater, depends on the value of the encryption dictionaryâs
        // Length entry.
        System.arraycopy(hash, 0, key, 0, key.length);
        return key;
    }