diff -ur pdfbox-1.8.4/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java pdfbox-1.8.4-aes256/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java
--- pdfbox-1.8.4/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java	2014-01-27 18:59:12.000000000 +0100
+++ pdfbox-1.8.4-aes256/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java	2014-01-31 13:21:21.000000000 +0100
@@ -65,6 +65,10 @@
     /**
      * A common COSName value.
      */
+    public static final COSName AESV3 = new COSName("AESV3");
+    /**
+     * A common COSName value.
+     */
     public static final COSName AIS = new COSName( "AIS" );
     /**
      * A common COSName value.
@@ -1013,6 +1017,11 @@
     /** the COSName for the optional content properties. */
     public static final COSName OCPROPERTIES = new COSName("OCProperties");
 
+    /**
+     * A common COSName value.
+     */
+    public static final COSName OE = new COSName("OE");
+
     /** the COSName for the "OFF" value. */
     public static final COSName OFF = new COSName("OFF");
     /** the COSName for the "ON" value. */
@@ -1134,6 +1143,10 @@
     /**
      * A common COSName value.
      */
+    public static final COSName PERMS = new COSName("Perms");
+    /**
+     * A common COSName value.
+     */
     public static final COSName PG = new COSName("Pg");
     /**
      * A common COSName value.
@@ -1441,6 +1454,10 @@
     /**
      * A common COSName value.
      */
+    public static final COSName UE = new COSName("UE");
+    /**
+     * A common COSName value.
+     */
     public static final COSName UF = new COSName( "UF" );
     /** the COSName for the "Unchanged" value. */
     public static final COSName UNCHANGED = new COSName("Unchanged");
diff -ur pdfbox-1.8.4/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/PDCryptFilterDictionary.java pdfbox-1.8.4-aes256/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/PDCryptFilterDictionary.java
--- pdfbox-1.8.4/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/PDCryptFilterDictionary.java	2014-01-27 18:59:12.000000000 +0100
+++ pdfbox-1.8.4-aes256/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/PDCryptFilterDictionary.java	2014-01-31 13:21:21.000000000 +0100
@@ -88,7 +88,7 @@
 
      /**
      * This will set the crypt filter method. 
-     * Allowed values are: NONE, V2, AESV2
+     * Allowed values are: NONE, V2, AESV2, AESV3
      *
      * @param cfm name of the crypt filter method.
      *
@@ -101,7 +101,7 @@
 
     /**
      * This will return the crypt filter method. 
-     * Allowed values are: NONE, V2, AESV2
+     * Allowed values are: NONE, V2, AESV2, AESV3
      *
      * @return the name of the crypt filter method.
      *
diff -ur pdfbox-1.8.4/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/PDEncryptionDictionary.java pdfbox-1.8.4-aes256/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/PDEncryptionDictionary.java
--- pdfbox-1.8.4/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/PDEncryptionDictionary.java	2014-01-27 18:59:12.000000000 +0100
+++ pdfbox-1.8.4-aes256/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/PDEncryptionDictionary.java	2014-01-31 13:21:21.000000000 +0100
@@ -284,6 +284,70 @@
     }
 
     /**
+    * This will set the OE entry in the standard encryption dictionary.
+    *
+    * @param oe A 32 byte array or null if there is no owner encryption key.
+    *
+    * @throws IOException If there is an error setting the data.
+    */
+   public void setOwnerEncryptionKey(byte[] oe) throws IOException
+   {
+       COSString ownerEncryptionKey = new COSString();
+       ownerEncryptionKey.append(oe);
+       encryptionDictionary.setItem( COSName.OE, ownerEncryptionKey );
+   }
+
+   /**
+    * This will get the OE entry in the standard encryption dictionary.
+    *
+    * @return A 32 byte array or null if there is no owner encryption key.
+    *
+    * @throws IOException If there is an error accessing the data.
+    */
+   public byte[] getOwnerEncryptionKey() throws IOException
+   {
+       byte[] oe = null;
+       COSString ownerEncryptionKey = (COSString)encryptionDictionary.getDictionaryObject( COSName.OE );
+       if( ownerEncryptionKey != null )
+       {
+           oe = ownerEncryptionKey.getBytes();
+       }
+       return oe;
+   }
+
+   /**
+   * This will set the UE entry in the standard encryption dictionary.
+   *
+   * @param ue A 32 byte array or null if there is no user encryption key.
+   *
+   * @throws IOException If there is an error setting the data.
+   */
+  public void setUserEncryptionKey(byte[] ue) throws IOException
+  {
+      COSString userEncryptionKey = new COSString();
+      userEncryptionKey.append(ue);
+      encryptionDictionary.setItem( COSName.UE, userEncryptionKey );
+  }
+
+  /**
+   * This will get the UE entry in the standard encryption dictionary.
+   *
+   * @return A 32 byte array or null if there is no user encryption key.
+   *
+   * @throws IOException If there is an error accessing the data.
+   */
+  public byte[] getUserEncryptionKey() throws IOException
+  {
+      byte[] ue = null;
+      COSString userEncryptionKey = (COSString)encryptionDictionary.getDictionaryObject( COSName.UE );
+      if( userEncryptionKey != null )
+      {
+          ue = userEncryptionKey.getBytes();
+      }
+      return ue;
+  }
+
+    /**
      * This will set the permissions bit mask.
      *
      * @param permissions The new permissions bit mask
@@ -397,6 +461,34 @@
     }
     
     /**
+     * Sets the crypt filter with the given name.
+     * 
+     * @param cryptFilterName the name of the crypt filter
+     * @param cryptFilterDictionary the crypt filter to set
+     */
+    public void setCryptFilterDictionary(COSName cryptFilterName, PDCryptFilterDictionary cryptFilterDictionary)
+    {
+        COSDictionary cfDictionary = (COSDictionary)encryptionDictionary.getDictionaryObject( COSName.CF );
+        if (cfDictionary == null)
+        {
+            cfDictionary = new COSDictionary();
+            encryptionDictionary.setItem(COSName.CF, cfDictionary);
+        }
+        
+        cfDictionary.setItem(cryptFilterName, cryptFilterDictionary.getCOSDictionary());
+    }
+    
+    /**
+     * Sets the standard crypt filter.
+     * 
+     * @param cryptFilterDictionary the standard crypt filter to set
+     */
+    public void setStdCryptFilterDictionary(PDCryptFilterDictionary cryptFilterDictionary)
+    {
+        setCryptFilterDictionary(COSName.STD_CF, cryptFilterDictionary);
+    }
+    
+    /**
      * Returns the name of the filter which is used for de/encrypting streams.
      * Default value is "Identity".
      * 
@@ -411,6 +503,16 @@
         }
         return stmF;
     }
+    
+    /**
+     * Sets the name of the filter which is used for de/encrypting streams.
+     * 
+     * @param streamFilterName the name of the filter
+     */
+    public void setStreamFilterName(COSName streamFilterName)
+    {
+        encryptionDictionary.setItem(COSName.STM_F, streamFilterName);
+    }
 
     /**
      * Returns the name of the filter which is used for de/encrypting strings.
@@ -427,6 +529,48 @@
         }
         return strF;
     }
+    
+    /**
+     * Sets the name of the filter which is used for de/encrypting strings.
+     * 
+     * @param stringFilterName the name of the filter
+     */
+    public void setStringFilterName(COSName stringFilterName)
+    {
+        encryptionDictionary.setItem(COSName.STR_F, stringFilterName);
+    }
+
+    /**
+     * Set the Perms entry in the encryption dictionary.
+     *
+     * @param perms A 16 byte array.
+     *
+     * @throws IOException If there is an error setting the data.
+     */
+    public void setPerms(byte[] perms) throws IOException
+    {
+        COSString user = new COSString();
+        user.append( perms );
+        encryptionDictionary.setItem( COSName.PERMS, user );
+    }
+
+    /**
+     * Get the Perms entry in the encryption dictionary.
+     *
+     * @return A 16 byte array or null if there is no Perms entry.
+     *
+     * @throws IOException If there is an error accessing the data.
+     */
+    public byte[] getPerms() throws IOException
+    {
+        byte[] perms = null;
+        COSString cos_perms = (COSString)encryptionDictionary.getDictionaryObject( COSName.PERMS );
+        if( cos_perms != null )
+        {
+            perms = cos_perms.getBytes();
+        }
+        return perms;
+    }
 
 }
 
diff -ur pdfbox-1.8.4/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/ProtectionPolicy.java pdfbox-1.8.4-aes256/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/ProtectionPolicy.java
--- pdfbox-1.8.4/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/ProtectionPolicy.java	2014-01-27 18:59:12.000000000 +0100
+++ pdfbox-1.8.4-aes256/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/ProtectionPolicy.java	2014-01-31 13:21:21.000000000 +0100
@@ -41,13 +41,13 @@
      * The default value is 40 bits, which provides a low security level
      * but is compatible with old versions of Acrobat Reader.
      *
-     * @param l the length in bits (must be 40 or 128)
+     * @param l the length in bits (must be 40, 128 or 256)
      */
     public void setEncryptionKeyLength(int l)
     {
-        if(l!=40 && l!=128)
+        if(l!=40 && l!=128 && l!=256)
         {
-            throw new RuntimeException("Invalid key length '" + l + "' value must be 40 or 128!");
+            throw new RuntimeException("Invalid key length '" + l + "' value must be 40, 128 or 256!");
         }
         encryptionKeyLength = l;
     }
diff -ur pdfbox-1.8.4/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java pdfbox-1.8.4-aes256/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java
--- pdfbox-1.8.4/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java	2014-01-27 18:59:12.000000000 +0100
+++ pdfbox-1.8.4-aes256/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/PublicKeySecurityHandler.java	2014-01-31 13:21:21.000000000 +0100
@@ -258,7 +258,9 @@
      */
     public void prepareDocumentForEncryption(PDDocument doc) throws CryptographyException
     {
-
+    	if (this.keyLength == 256)
+    		throw new CryptographyException("256 bit key length is not supported yet for public key security");
+    	
         try
         {
             Security.addProvider(new BouncyCastleProvider());
diff -ur pdfbox-1.8.4/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java pdfbox-1.8.4-aes256/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java
--- pdfbox-1.8.4/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java	2014-01-27 18:59:12.000000000 +0100
+++ pdfbox-1.8.4-aes256/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/SecurityHandler.java	2014-01-31 13:21:21.000000000 +0100
@@ -22,10 +22,12 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.security.GeneralSecurityException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -50,6 +52,7 @@
 import org.apache.pdfbox.encryption.ARCFour;
 import org.apache.pdfbox.exceptions.CryptographyException;
 import org.apache.pdfbox.exceptions.WrappedIOException;
+import org.apache.pdfbox.io.IOUtils;
 import org.apache.pdfbox.pdmodel.PDDocument;
 
 /**
@@ -247,100 +250,144 @@
      */
     public void encryptData(long objectNumber, long genNumber, InputStream data, OutputStream output, boolean decrypt)
             throws CryptographyException, IOException
-    {
-        if (aes && !decrypt)
-        {
-            throw new IllegalArgumentException("AES encryption is not yet implemented.");
-        }
-
-        byte[] newKey = new byte[encryptionKey.length + 5];
-        System.arraycopy(encryptionKey, 0, newKey, 0, encryptionKey.length);
-        // PDF 1.4 reference pg 73
-        // step 1
-        // we have the reference
-
-        // step 2
-        newKey[newKey.length - 5] = (byte) (objectNumber & 0xff);
-        newKey[newKey.length - 4] = (byte) ((objectNumber >> 8) & 0xff);
-        newKey[newKey.length - 3] = (byte) ((objectNumber >> 16) & 0xff);
-        newKey[newKey.length - 2] = (byte) (genNumber & 0xff);
-        newKey[newKey.length - 1] = (byte) ((genNumber >> 8) & 0xff);
-
-        // step 3
-        byte[] digestedKey = null;
-        try
+    {      
+        // Determine whether we're using Algorithm 1 (for RC4 and AES-128), or 1.A (for AES-256) */
+        if (aes && encryptionKey.length == 32)
         {
-            MessageDigest md = MessageDigest.getInstance("MD5");
-            md.update(newKey);
-            if (aes)
+            byte[] iv = new byte[16];
+            
+            if (decrypt)
             {
-                md.update(AES_SALT);
+                // read IV from stream
+                data.read(iv);
             }
-            digestedKey = md.digest();
-        }
-        catch (NoSuchAlgorithmException e)
-        {
-            throw new CryptographyException(e);
-        }
-
-        // step 4
-        int length = Math.min(newKey.length, 16);
-        byte[] finalKey = new byte[length];
-        System.arraycopy(digestedKey, 0, finalKey, 0, length);
-
-        if (aes)
-        {
-            byte[] iv = new byte[16];
-
-            data.read(iv);
-
+            else
+            {
+                // generate random IV and write to stream
+                SecureRandom rnd = new SecureRandom();
+                rnd.nextBytes(iv);
+                output.write(iv);
+            }
+            
             try
             {
-                Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
-
-                SecretKey aesKey = new SecretKeySpec(finalKey, "AES");
-
-                IvParameterSpec ips = new IvParameterSpec(iv);
-
-                decryptCipher.init(decrypt ? Cipher.DECRYPT_MODE : Cipher.ENCRYPT_MODE, aesKey, ips);
-
-                CipherInputStream cipherStream = new CipherInputStream(data, decryptCipher);
-
+                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+                SecretKeySpec keyspec = new SecretKeySpec(encryptionKey, "AES");
+                IvParameterSpec ivspec = new IvParameterSpec(iv);
+                cipher.init(decrypt ? Cipher.DECRYPT_MODE : Cipher.ENCRYPT_MODE, keyspec, ivspec);
+                
+                CipherInputStream cis = new CipherInputStream(data, cipher);
                 try
                 {
-                    byte[] buffer = new byte[4096];
-                    for (int n = 0; -1 != (n = cipherStream.read(buffer));)
-                    {
-                        output.write(buffer, 0, n);
-                    }
+                    IOUtils.copy(cis, output);
                 }
                 finally
                 {
-                    cipherStream.close();
+                    cis.close();
                 }
             }
-            catch (InvalidKeyException e)
+            catch (GeneralSecurityException e)
             {
-                throw new WrappedIOException(e);
+                throw new CryptographyException(e);
             }
-            catch (InvalidAlgorithmParameterException e)
+        }
+        else
+        {
+            
+            if (aes && !decrypt)
             {
-                throw new WrappedIOException(e);
+                throw new IllegalArgumentException("AES encryption with key length other than 256 bits is not yet implemented.");
+            }
+    
+            byte[] newKey = new byte[encryptionKey.length + 5];
+            System.arraycopy(encryptionKey, 0, newKey, 0, encryptionKey.length);
+            // PDF 1.4 reference pg 73
+            // step 1
+            // we have the reference
+    
+            // step 2
+            newKey[newKey.length - 5] = (byte) (objectNumber & 0xff);
+            newKey[newKey.length - 4] = (byte) ((objectNumber >> 8) & 0xff);
+            newKey[newKey.length - 3] = (byte) ((objectNumber >> 16) & 0xff);
+            newKey[newKey.length - 2] = (byte) (genNumber & 0xff);
+            newKey[newKey.length - 1] = (byte) ((genNumber >> 8) & 0xff);
+    
+            // step 3
+            byte[] digestedKey = null;
+            try
+            {
+                MessageDigest md = MessageDigest.getInstance("MD5");
+                md.update(newKey);
+                if (aes)
+                {
+                    md.update(AES_SALT);
+                }
+                digestedKey = md.digest();
             }
             catch (NoSuchAlgorithmException e)
             {
-                throw new WrappedIOException(e);
+                throw new CryptographyException(e);
+            }
+    
+            // step 4
+            int length = Math.min(newKey.length, 16);
+            byte[] finalKey = new byte[length];
+            System.arraycopy(digestedKey, 0, finalKey, 0, length);
+    
+            if (aes)
+            {
+                byte[] iv = new byte[16];
+    
+                data.read(iv);
+    
+                try
+                {
+                    Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
+    
+                    SecretKey aesKey = new SecretKeySpec(finalKey, "AES");
+    
+                    IvParameterSpec ips = new IvParameterSpec(iv);
+    
+                    decryptCipher.init(decrypt ? Cipher.DECRYPT_MODE : Cipher.ENCRYPT_MODE, aesKey, ips);
+    
+                    CipherInputStream cipherStream = new CipherInputStream(data, decryptCipher);
+    
+                    try
+                    {
+                        byte[] buffer = new byte[4096];
+                        for (int n = 0; -1 != (n = cipherStream.read(buffer));)
+                        {
+                            output.write(buffer, 0, n);
+                        }
+                    }
+                    finally
+                    {
+                        cipherStream.close();
+                    }
+                }
+                catch (InvalidKeyException e)
+                {
+                    throw new WrappedIOException(e);
+                }
+                catch (InvalidAlgorithmParameterException e)
+                {
+                    throw new WrappedIOException(e);
+                }
+                catch (NoSuchAlgorithmException e)
+                {
+                    throw new WrappedIOException(e);
+                }
+                catch (NoSuchPaddingException e)
+                {
+                    throw new WrappedIOException(e);
+                }
             }
-            catch (NoSuchPaddingException e)
+            else
             {
-                throw new WrappedIOException(e);
+                rc4.setKey(finalKey);
+                rc4.write(data, output);
             }
         }
-        else
-        {
-            rc4.setKey(finalKey);
-            rc4.write(data, output);
-        }
 
         output.flush();
     }
diff -ur pdfbox-1.8.4/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java pdfbox-1.8.4-aes256/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java
--- pdfbox-1.8.4/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java	2014-01-27 18:59:12.000000000 +0100
+++ pdfbox-1.8.4-aes256/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/encryption/StandardSecurityHandler.java	2014-01-31 13:21:21.000000000 +0100
@@ -20,10 +20,16 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.math.BigInteger;
+import java.security.GeneralSecurityException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
 import java.util.Arrays;
 
+import javax.crypto.Cipher;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
 import org.apache.pdfbox.cos.COSArray;
 import org.apache.pdfbox.cos.COSDocument;
 import org.apache.pdfbox.cos.COSName;
@@ -80,6 +86,11 @@
         (byte)0x0C, (byte)0xA9, (byte)0xFE, (byte)0x64, (byte)0x53,
         (byte)0x69, (byte)0x7A
     };
+    
+    /**
+     * Hashes used for Algorithm 2.B, depending on remainder from E modulo 3
+     */
+    private static final String[] HASHES_2B = new String[] {"SHA-256", "SHA-384", "SHA-512"};
 
     /**
      * Constructor.
@@ -113,6 +124,11 @@
         {
             return DEFAULT_VERSION;
         }
+        else if(keyLength == 256)
+        {
+            return 5;
+        }
+        
         return 2;
     }
 
@@ -129,6 +145,10 @@
         {
             return 2;
         }
+        if (version == 5)
+        {
+            return 6;    // note about revision 5: "Shall not be used. This value was used by a deprecated Adobe extension."
+        }
         if ( version == 2 || version == 3 || policy.getPermissions().hasAnyRevision3PermissionSet())
         {
             return 3;
@@ -210,10 +230,19 @@
         
         byte[] u = encDictionary.getUserKey();
         byte[] o = encDictionary.getOwnerKey();
+        byte[] ue = null, oe = null;
 
+        String passwordCharset = "ISO-8859-1";
+        if (dicRevision == 6)
+        {
+            passwordCharset = "UTF-8";
+            ue = encDictionary.getUserEncryptionKey();
+            oe = encDictionary.getOwnerEncryptionKey();
+        }
+        
         boolean isUserPassword =
             isUserPassword(
-                password.getBytes("ISO-8859-1"),
+                password.getBytes(passwordCharset),
                 u,
                 o,
                 dicPermissions,
@@ -223,7 +252,7 @@
                 encryptMetadata);
         boolean isOwnerPassword =
             isOwnerPassword(
-                password.getBytes("ISO-8859-1"),
+                password.getBytes(passwordCharset),
                 u,
                 o,
                 dicPermissions,
@@ -232,38 +261,72 @@
                 dicLength,
                 encryptMetadata);
 
-        if( isUserPassword )
+        // Test if this is the owner password first, as the owner password may be equal to the user
+        // password, in which case it makes sense to use the owner permissions.
+        if( isOwnerPassword )
         {
-            currentAccessPermission = new AccessPermission( dicPermissions );
+            currentAccessPermission = AccessPermission.getOwnerAccessPermission();
+            
+            byte[] computedPassword;
+            if (dicRevision == 6)
+                computedPassword = password.getBytes(passwordCharset);
+            else
+                computedPassword = getUserPassword(password.getBytes(passwordCharset),o,dicRevision,dicLength );
+            
             encryptionKey =
                 computeEncryptedKey(
-                    password.getBytes("ISO-8859-1"),
-                    o,
+                    computedPassword,
+                    o, u, oe, ue,
                     dicPermissions,
                     documentIDBytes,
                     dicRevision,
                     dicLength,
-                    encryptMetadata );
+                    encryptMetadata, true);
         }
-        else if( isOwnerPassword )
+        else if( isUserPassword )
         {
-            currentAccessPermission = AccessPermission.getOwnerAccessPermission();
-            byte[] computedUserPassword = getUserPassword(password.getBytes("ISO-8859-1"),o,dicRevision,dicLength );
+            currentAccessPermission = new AccessPermission( dicPermissions );
             encryptionKey =
                 computeEncryptedKey(
-                    computedUserPassword,
-                    o,
+                    password.getBytes(passwordCharset),
+                    o, u, oe, ue,
                     dicPermissions,
                     documentIDBytes,
                     dicRevision,
                     dicLength,
-                    encryptMetadata);
+                    encryptMetadata, false);
         }
         else
         {
             throw new CryptographyException(
                 "Error: The supplied password does not match either the owner or user password in the document." );
         }
+        
+        if (dicRevision == 6)
+        {
+            // Algorithm 13: validate permissions ("Perms" field)
+            try
+            {
+                Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
+                cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(encryptionKey, "AES"));
+                byte[] perms = cipher.doFinal(encDictionary.getPerms());
+                
+                if (perms[9] != 'a' || perms[10] != 'd' || perms[11] != 'b')
+                    throw new CryptographyException("Verification of permissions failed (constant)");
+                
+                int perms_p = (perms[0] & 0xFF) | ((perms[1] & 0xFF) << 8) | ((perms[2] & 0xFF) << 16) | ((perms[3] & 0xFF) << 24);
+                
+                if (perms_p != dicPermissions)
+                    throw new CryptographyException("Verification of permissions failed (" + perms_p + " != " + dicPermissions + ")");
+                
+                if ((encryptMetadata && perms[8] != 'T') || (!encryptMetadata && perms[8] != 'F'))
+                    throw new CryptographyException("Verification of permissions failed (EncryptMetadata)");
+            }
+            catch (GeneralSecurityException e)
+            {
+                throw new CryptographyException(e);
+            }
+        }
 
         // detect whether AES encryption is used. This assumes that the encryption algo is 
         // stored in the PDCryptFilterDictionary
@@ -274,7 +337,7 @@
             COSName cryptFilterMethod = stdCryptFilterDictionary.getCryptFilterMethod();
             if (cryptFilterMethod != null) 
             {
-                setAES("AESV2".equalsIgnoreCase(cryptFilterMethod.getName()));
+                setAES("AESV2".equalsIgnoreCase(cryptFilterMethod.getName()) || "AESV3".equalsIgnoreCase(cryptFilterMethod.getName()));
             }
         }
     }
@@ -312,61 +375,150 @@
         {
             userPassword = "";
         }
+        
+        // If no owner password is set, use the user password instead.
+        if (ownerPassword.length() == 0)
+            ownerPassword = userPassword;
 
         int permissionInt = policy.getPermissions().getPermissionBytes();
 
         encryptionDictionary.setPermissions(permissionInt);
 
         int length = keyLength/8;
-
-        COSArray idArray = document.getDocument().getDocumentID();
-
-        //check if the document has an id yet.  If it does not then
-        //generate one
-        if( idArray == null || idArray.size() < 2 )
+        
+        if (revision == 6)
         {
-            idArray = new COSArray();
             try
             {
-                MessageDigest md = MessageDigest.getInstance( "MD5" );
-                BigInteger time = BigInteger.valueOf( System.currentTimeMillis() );
-                md.update( time.toByteArray() );
-                md.update( ownerPassword.getBytes("ISO-8859-1") );
-                md.update( userPassword.getBytes("ISO-8859-1") );
-                md.update( document.getDocument().toString().getBytes() );
-                byte[] id = md.digest( this.toString().getBytes("ISO-8859-1") );
-                COSString idString = new COSString();
-                idString.append( id );
-                idArray.add( idString );
-                idArray.add( idString );
-                document.getDocument().setDocumentID( idArray );
+                SecureRandom rnd = new SecureRandom();
+                Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
+
+                // make a random 256-bit file encryption key
+                encryptionKey = new byte[32];
+                rnd.nextBytes(encryptionKey);
+                
+                // Algorithm 8a: Compute U
+                byte[] userPasswordBytes = truncate127(userPassword.getBytes("UTF-8"));
+                byte[] userValidationSalt = new byte[8];
+                byte[] userKeySalt = new byte[8];
+                rnd.nextBytes(userValidationSalt);
+                rnd.nextBytes(userKeySalt);
+                byte[] hash_u = computeHash2B(concat(userPasswordBytes, userValidationSalt), userPasswordBytes, null);
+                byte[] u = concat(hash_u, userValidationSalt, userKeySalt);
+                
+                // Algorithm 8b: Compute UE
+                byte[] hash_ue = computeHash2B(concat(userPasswordBytes, userKeySalt), userPasswordBytes, null);
+                cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(hash_ue, "AES"), new IvParameterSpec(new byte[16]));
+                byte[] ue = cipher.doFinal(encryptionKey);
+                
+                // Algorithm 9a: Compute O
+                byte[] ownerPasswordBytes = truncate127(ownerPassword.getBytes("UTF-8"));
+                byte[] ownerValidationSalt = new byte[8];
+                byte[] ownerKeySalt = new byte[8];
+                rnd.nextBytes(ownerValidationSalt);
+                rnd.nextBytes(ownerKeySalt);
+                byte[] hash_o = computeHash2B(concat(ownerPasswordBytes, ownerValidationSalt, u), ownerPasswordBytes, u);
+                byte[] o = concat(hash_o, ownerValidationSalt, ownerKeySalt);
+                
+                // Algorithm 9b: Compute OE
+                byte[] hash_oe = computeHash2B(concat(ownerPasswordBytes, ownerKeySalt, u), ownerPasswordBytes, u);
+                cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(hash_oe, "AES"), new IvParameterSpec(new byte[16]));
+                byte[] oe = cipher.doFinal(encryptionKey);
+                
+                // Set keys and other required constants in encryption dictionary
+                encryptionDictionary.setUserKey(u);
+                encryptionDictionary.setUserEncryptionKey(ue);
+                encryptionDictionary.setOwnerKey(o);
+                encryptionDictionary.setOwnerEncryptionKey(oe);
+                
+                PDCryptFilterDictionary cryptFilterDictionary = new PDCryptFilterDictionary();
+                cryptFilterDictionary.setCryptFilterMethod(COSName.AESV3);
+                cryptFilterDictionary.setLength(keyLength);
+                encryptionDictionary.setStdCryptFilterDictionary(cryptFilterDictionary);
+                encryptionDictionary.setStreamFilterName(COSName.STD_CF);
+                encryptionDictionary.setStringFilterName(COSName.STD_CF);
+                setAES(true);
+                
+                // Algorithm 10: compute "Perms" value
+                byte[] perms = new byte[16];
+                perms[0] = (byte)permissionInt;
+                perms[1] = (byte)(permissionInt >>> 8);
+                perms[2] = (byte)(permissionInt >>> 16);
+                perms[3] = (byte)(permissionInt >>> 24);
+                perms[4] = perms[5] = perms[6] = perms[7] = (byte)0xFF;
+                perms[8] = 'T';    // we always use EncryptMetadata == true
+                perms[9] = 'a';
+                perms[10] = 'd';
+                perms[11] = 'b';
+                for (int i = 12; i <= 15; i++)
+                    perms[i] = (byte)rnd.nextInt();
+                
+                cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptionKey, "AES"), new IvParameterSpec(new byte[16]));
+                byte[] perms_enc = cipher.doFinal(perms);
+                
+                encryptionDictionary.setPerms(perms_enc);
             }
-            catch( NoSuchAlgorithmException e )
+            catch (GeneralSecurityException e)
             {
-                throw new CryptographyException( e );
+                throw new CryptographyException(e);
             }
-            catch(IOException e )
+        }
+        else
+        {
+    
+            COSArray idArray = document.getDocument().getDocumentID();
+    
+            //check if the document has an id yet.  If it does not then
+            //generate one
+            if( idArray == null || idArray.size() < 2 )
             {
-                throw new CryptographyException( e );
+                idArray = new COSArray();
+                try
+                {
+                    MessageDigest md = MessageDigest.getInstance( "MD5" );
+                    BigInteger time = BigInteger.valueOf( System.currentTimeMillis() );
+                    md.update( time.toByteArray() );
+                    md.update( ownerPassword.getBytes("ISO-8859-1") );
+                    md.update( userPassword.getBytes("ISO-8859-1") );
+                    md.update( document.getDocument().toString().getBytes() );
+                    byte[] id = md.digest( this.toString().getBytes("ISO-8859-1") );
+                    COSString idString = new COSString();
+                    idString.append( id );
+                    idArray.add( idString );
+                    idArray.add( idString );
+                    document.getDocument().setDocumentID( idArray );
+                }
+                catch( NoSuchAlgorithmException e )
+                {
+                    throw new CryptographyException( e );
+                }
+                catch(IOException e )
+                {
+                    throw new CryptographyException( e );
+                }
             }
+    
+            COSString id = (COSString)idArray.getObject( 0 );
+    
+            String passwordCharset = "ISO-8859-1";
+            if (revision == 6)
+                passwordCharset = "UTF-8";
+            
+            byte[] o = computeOwnerPassword(
+                ownerPassword.getBytes(passwordCharset),
+                userPassword.getBytes(passwordCharset), revision, length);
+    
+            byte[] u = computeUserPassword(
+                userPassword.getBytes(passwordCharset),
+                o, permissionInt, id.getBytes(), revision, length, true);
+    
+            encryptionKey = computeEncryptedKey(
+                userPassword.getBytes(passwordCharset), o, null, null, null, permissionInt, id.getBytes(), revision, length, true, false);
+    
+            encryptionDictionary.setOwnerKey(o);
+            encryptionDictionary.setUserKey(u);
         }
 
-        COSString id = (COSString)idArray.getObject( 0 );
-
-        byte[] o = computeOwnerPassword(
-            ownerPassword.getBytes("ISO-8859-1"),
-            userPassword.getBytes("ISO-8859-1"), revision, length);
-
-        byte[] u = computeUserPassword(
-            userPassword.getBytes("ISO-8859-1"),
-            o, permissionInt, id.getBytes(), revision, length, true);
-
-        encryptionKey = computeEncryptedKey(
-            userPassword.getBytes("ISO-8859-1"), o, permissionInt, id.getBytes(), revision, length, true);
-
-        encryptionDictionary.setOwnerKey(o);
-        encryptionDictionary.setUserKey(u);
-
         document.setEncryptionDictionary( encryptionDictionary );
         document.getDocument().setEncryptionDictionary(encryptionDictionary.getCOSDictionary());
 
@@ -399,9 +551,30 @@
             int length,
             boolean encryptMetadata)
             throws CryptographyException, IOException
-    {
-        byte[] userPassword = getUserPassword( ownerPassword, o, encRevision, length );
-        return isUserPassword( userPassword, u, o, permissions, id, encRevision, length, encryptMetadata );
+    {        
+        if (encRevision == 6)
+        {            
+            ownerPassword = truncate127(ownerPassword);
+            
+            byte[] o_hash = new byte[32];
+            byte[] o_validationSalt = new byte[8];
+            System.arraycopy(o, 0, o_hash, 0, 32);
+            System.arraycopy(o, 32, o_validationSalt, 0, 8);
+            
+            byte[] hash = computeHash2A(ownerPassword, o_validationSalt, u);
+            
+            return Arrays.equals(hash, o_hash);            
+        }
+        else if (encRevision == 5)
+        {
+            // Shall not be used. This value was used by a deprecated Adobe extension.
+            throw new IOException("Unsupported Encryption Revision " + encRevision);
+        }
+        else
+        {
+            byte[] userPassword = getUserPassword( ownerPassword, o, encRevision, length );
+            return isUserPassword( userPassword, u, o, permissions, id, encRevision, length, encryptMetadata );
+        }
     }
 
     /**
@@ -494,12 +667,16 @@
      * Compute the encryption key.
      *
      * @param password The password to compute the encrypted key.
-     * @param o The o entry of the encryption dictionary.
+     * @param o The O entry of the encryption dictionary.
+     * @param u The U entry of the encryption dictionary.
+     * @param oe The OE entry of the encryption dictionary.
+     * @param oe The UE entry of the encryption dictionary.
      * @param permissions The permissions for the document.
      * @param id The document id.
      * @param encRevision The revision of the encryption algorithm.
      * @param length The length of the encryption key.
      * @param encryptMetadata The encryption metadata
+     * @param isOwnerPassword whether the password given is the owner password (necessary for revision 6)
      *
      * @return The encrypted key bytes.
      *
@@ -508,73 +685,113 @@
     public final byte[] computeEncryptedKey(
             byte[] password,
             byte[] o,
+            byte[] u,
+            byte[] oe,
+            byte[] ue,
             int permissions,
             byte[] id,
             int encRevision,
             int length,
-            boolean encryptMetadata)
+            boolean encryptMetadata,
+            boolean isOwnerPassword)
             throws CryptographyException
         {
             byte[] result = new byte[ length ];
-            try
+            
+            if (encRevision == 6)
             {
-                //PDFReference 1.4 pg 78
-                //step1
-                byte[] padded = truncateOrPad( password );
-
-                //step 2
-                MessageDigest md = MessageDigest.getInstance("MD5");
-                md.update( padded );
-
-                //step 3
-                md.update( o );
-
-                //step 4
-                byte zero = (byte)(permissions >>> 0);
-                byte one = (byte)(permissions >>> 8);
-                byte two = (byte)(permissions >>> 16);
-                byte three = (byte)(permissions >>> 24);
-
-                md.update( zero );
-                md.update( one );
-                md.update( two );
-                md.update( three );
-
-                //step 5
-                md.update( id );
-                
-                //(Security handlers of revision 4 or greater) If document metadata is not being encrypted, 
-                //pass 4 bytes with the value 0xFFFFFFFF to the MD5 hash function.
-                //see 7.6.3.3 Algorithm 2 Step f of PDF 32000-1:2008
-                if( encRevision == 4 && !encryptMetadata)
+                //Algorithm 2.A, based on SHA-2 and AES
+                
+                byte[] hash, fileKey_enc;
+                if (isOwnerPassword)
                 {
-                    md.update(new byte[]{(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff});
+                    byte[] o_keySalt = new byte[8];
+                    System.arraycopy(o, 40, o_keySalt, 0, 8);
+                    hash = computeHash2A(password, o_keySalt, u);
+                    fileKey_enc = oe;
+                }
+                else
+                {
+                    byte[] u_keySalt = new byte[8];
+                    System.arraycopy(u, 40, u_keySalt, 0, 8);
+                    hash = computeHash2A(password, u_keySalt, null);
+                    fileKey_enc = ue;
                 }
                 
-                byte[] digest = md.digest();
-
-                //step 6
-                if( encRevision == 3 || encRevision == 4)
+                try
                 {
-                    for( int i=0; i<50; i++ )
-                    {
-                        md.reset();
-                        md.update( digest, 0, length );
-                        digest = md.digest();
-                    }
+                    Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
+                    cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(hash, "AES"), new IvParameterSpec(new byte[16]));
+                    result = cipher.doFinal(fileKey_enc);
                 }
-
-                //step 7
-                if( encRevision == 2 && length != 5 )
+                catch (GeneralSecurityException e)
                 {
-                    throw new CryptographyException(
-                        "Error: length should be 5 when revision is two actual=" + length );
+                    throw new CryptographyException(e);
                 }
-                System.arraycopy( digest, 0, result, 0, length );
             }
-            catch( NoSuchAlgorithmException e )
+            else
             {
-                throw new CryptographyException( e );
+                //Algorithm 2, based on MD5
+                try
+                {
+                    //PDFReference 1.4 pg 78
+                    //step1
+                    byte[] padded = truncateOrPad( password );
+    
+                    //step 2
+                    MessageDigest md = MessageDigest.getInstance("MD5");
+                    md.update( padded );
+    
+                    //step 3
+                    md.update( o );
+    
+                    //step 4
+                    byte zero = (byte)(permissions >>> 0);
+                    byte one = (byte)(permissions >>> 8);
+                    byte two = (byte)(permissions >>> 16);
+                    byte three = (byte)(permissions >>> 24);
+    
+                    md.update( zero );
+                    md.update( one );
+                    md.update( two );
+                    md.update( three );
+    
+                    //step 5
+                    md.update( id );
+                    
+                    //(Security handlers of revision 4 or greater) If document metadata is not being encrypted, 
+                    //pass 4 bytes with the value 0xFFFFFFFF to the MD5 hash function.
+                    //see 7.6.3.3 Algorithm 2 Step f of PDF 32000-1:2008
+                    if( encRevision == 4 && !encryptMetadata)
+                    {
+                        md.update(new byte[]{(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff});
+                    }
+                    
+                    byte[] digest = md.digest();
+    
+                    //step 6
+                    if( encRevision == 3 || encRevision == 4)
+                    {
+                        for( int i=0; i<50; i++ )
+                        {
+                            md.reset();
+                            md.update( digest, 0, length );
+                            digest = md.digest();
+                        }
+                    }
+    
+                    //step 7
+                    if( encRevision == 2 && length != 5 )
+                    {
+                        throw new CryptographyException(
+                            "Error: length should be 5 when revision is two actual=" + length );
+                    }
+                    System.arraycopy( digest, 0, result, 0, length );
+                }
+                catch( NoSuchAlgorithmException e )
+                {
+                    throw new CryptographyException( e );
+                }
             }
             return result;
         }
@@ -608,7 +825,7 @@
         {
             ByteArrayOutputStream result = new ByteArrayOutputStream();
             //STEP 1
-            byte[] encryptionKey = computeEncryptedKey( password, o, permissions, id, encRevision, length, encryptMetadata );
+            byte[] encryptionKey = computeEncryptedKey( password, o, null, null, null, permissions, id, encRevision, length, encryptMetadata, true );
 
             if( encRevision == 2 )
             {
@@ -792,22 +1009,45 @@
             throws CryptographyException, IOException
         {
             boolean matches = false;
-            //STEP 1
-            byte[] computedValue = computeUserPassword( password, o, permissions, id, encRevision, length, 
-                    encryptMetadata );
-            if( encRevision == 2 )
+            
+            if (encRevision == 6)
             {
-                //STEP 2
-                matches = Arrays.equals(u, computedValue);
+                password = truncate127(password);
+                
+                byte[] u_hash = new byte[32];
+                byte[] u_validationSalt = new byte[8];
+                System.arraycopy(u, 0, u_hash, 0, 32);
+                System.arraycopy(u, 32, u_validationSalt, 0, 8);
+                
+                byte[] hash = computeHash2A(password, u_validationSalt, null);
+                
+                if (Arrays.equals(hash, u_hash))
+                    matches = true;                
             }
-            else if( encRevision == 3 || encRevision == 4 )
+            else if (encRevision == 5)
             {
-                //STEP 2
-                matches = arraysEqual( u, computedValue, 16 );
+                // Shall not be used. This value was used by a deprecated Adobe extension.
+                throw new IOException("Unsupported Encryption Revision " + encRevision);
             }
             else
             {
-                throw new IOException( "Unknown Encryption Revision " + encRevision );
+                //STEP 1
+                byte[] computedValue = computeUserPassword( password, o, permissions, id, encRevision, length, 
+                        encryptMetadata );
+                if( encRevision == 2 )
+                {
+                    //STEP 2
+                    matches = Arrays.equals(u, computedValue);
+                }
+                else if( encRevision == 3 || encRevision == 4 )
+                {
+                    //STEP 2
+                    matches = arraysEqual( u, computedValue, 16 );
+                }
+                else
+                {
+                    throw new IOException( "Unknown Encryption Revision " + encRevision );
+                }
             }
             return matches;
         }
@@ -840,8 +1080,12 @@
             boolean encryptMetadata )
             throws CryptographyException, IOException
             {
-                return isUserPassword(password.getBytes("ISO-8859-1"),
-                        u,o,permissions, id, encRevision, length, encryptMetadata);
+                if (encRevision == 6)
+                    return isUserPassword(password.getBytes("UTF-8"),
+                            u,o,permissions, id, encRevision, length, encryptMetadata);
+                else
+                    return isUserPassword(password.getBytes("ISO-8859-1"),
+                            u,o,permissions, id, encRevision, length, encryptMetadata);
             }
 
     /**
@@ -872,8 +1116,12 @@
             boolean encryptMetadata)
             throws CryptographyException, IOException
             {
-                return isOwnerPassword(password.getBytes("ISO-8859-1"),
-                        u,o,permissions, id, encRevision, length, encryptMetadata);
+                if (encRevision == 6)
+                    return isOwnerPassword(password.getBytes("UTF-8"),
+                            u,o,permissions, id, encRevision, length, encryptMetadata);
+                else
+                    return isOwnerPassword(password.getBytes("ISO-8859-1"),
+                            u,o,permissions, id, encRevision, length, encryptMetadata);
             }
 
     private static final boolean arraysEqual( byte[] first, byte[] second, int count )
@@ -892,5 +1140,121 @@
         }
         return true;
     }
-
+    
+    // Algorithm 2.A from ISO 32000-1
+    private static final byte[] computeHash2A(byte[] password, byte[] salt, byte[] u) throws CryptographyException
+    {
+        password = truncate127(password);
+        
+        if (u == null)
+            u = new byte[0];
+        else if (u.length < 48)
+            throw new CryptographyException("Bad U length");
+        else if (u.length > 48)
+        {
+            // must truncate
+            byte[] u_trunc = new byte[48];
+            System.arraycopy(u, 0, u_trunc, 0, 48);
+            u = u_trunc;
+        }
+        
+        byte[] input = concat(password, salt, u);
+        return computeHash2B(input, password, u);
+    }
+    
+    // Algorithm 2.B from ISO 32000-2
+    private static final byte[] computeHash2B(final byte[] input, final byte[] password, final byte[] userKey) throws CryptographyException
+    {
+        try
+        {
+            MessageDigest md = MessageDigest.getInstance("SHA-256");
+            byte[] k = md.digest(input);
+            
+            byte[] e = null;
+            for (int round = 0; round < 64 || ((int)e[e.length-1] & 0xFF) > (round - 32); round++)
+            {                
+                byte[] k1;
+                if (userKey != null && userKey.length >= 48)
+                    k1 = new byte[64*(password.length+k.length+48)];
+                else
+                    k1 = new byte[64*(password.length+k.length)];
+                
+                int pos = 0;
+                for (int i = 0; i < 64; i++)
+                {
+                    System.arraycopy(password, 0, k1, pos, password.length);
+                    pos += password.length;
+                    System.arraycopy(k, 0, k1, pos, k.length);
+                    pos += k.length;
+                    if (userKey != null && userKey.length >= 48)
+                    {
+                        System.arraycopy(userKey, 0, k1, pos, 48);
+                        pos += 48;
+                    }
+                }
+                
+                byte[] k_first = new byte[16];
+                byte[] k_second = new byte[16];
+                System.arraycopy(k, 0, k_first, 0, 16);
+                System.arraycopy(k, 16, k_second, 0, 16);
+                
+                Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
+                SecretKeySpec keyspec = new SecretKeySpec(k_first, "AES");
+                IvParameterSpec ivspec = new IvParameterSpec(k_second);
+                cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
+                e = cipher.doFinal(k1);
+                
+                byte[] e_first = new byte[16];
+                System.arraycopy(e, 0, e_first, 0, 16);
+                BigInteger bi = new BigInteger(1, e_first);
+                BigInteger remainder = bi.mod(new BigInteger("3"));
+                String nextHash = HASHES_2B[remainder.intValue()];
+                
+                md = MessageDigest.getInstance(nextHash);
+                k = md.digest(e);
+            }
+            
+            if (k.length > 32)
+            {
+                byte[] k_trunc = new byte[32];
+                System.arraycopy(k, 0, k_trunc, 0, 32);
+                return k_trunc;
+            }
+            else
+            {
+                return k;
+            }            
+        }
+        catch (GeneralSecurityException e)
+        {
+            throw new CryptographyException(e);
+        }
+    }
+    
+    private static final byte[] concat(byte[] a, byte[] b)
+    {
+        byte[] o = new byte[a.length + b.length];
+        System.arraycopy(a, 0, o, 0, a.length);
+        System.arraycopy(b, 0, o, a.length, b.length);
+        return o;
+    }
+    
+    private static final byte[] concat(byte[] a, byte[] b, byte[] c)
+    {
+        byte[] o = new byte[a.length + b.length + c.length];
+        System.arraycopy(a, 0, o, 0, a.length);
+        System.arraycopy(b, 0, o, a.length, b.length);
+        System.arraycopy(c, 0, o, a.length + b.length, c.length);
+        return o;
+    }
+    
+    private static final byte[] truncate127(byte[] in)
+    {
+        if (in.length <= 127)
+            return in;
+        
+        byte[] trunc = new byte[127];
+        System.arraycopy(in, 0, trunc, 0, 127);
+        return trunc;
+    }
 }
