Author: kiwiwings
Date: Sun Mar 13 19:31:32 2016
New Revision: 1734843

URL: http://svn.apache.org/viewvc?rev=1734843&view=rev
Log:
#59135 - Password gets truncated when using passwords longer than 15 characters 
for the function protectSheet()

Modified:
    poi/site/src/documentation/content/xdocs/status.xml
    poi/trunk/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
    poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java

Modified: poi/site/src/documentation/content/xdocs/status.xml
URL: 
http://svn.apache.org/viewvc/poi/site/src/documentation/content/xdocs/status.xml?rev=1734843&r1=1734842&r2=1734843&view=diff
==============================================================================
--- poi/site/src/documentation/content/xdocs/status.xml (original)
+++ poi/site/src/documentation/content/xdocs/status.xml Sun Mar 13 19:31:32 2016
@@ -40,6 +40,7 @@
     </devs>
 
     <release version="3.15-beta1" date="2016-07-??">
+        <action dev="PD" type="fix" fixes-bug="59135">Password gets truncated 
when using passwords longer than 15 characters for the function 
protectSheet()</action> 
         <action dev="PD" type="add" fixes-bug="56549">Correctly calculate char 
index ranges for HWPF in the TextPieceTable</action>
         <action dev="PD" type="add" fixes-bug="57495">Fix problem with tables 
in documents at pos 0</action>
         <action dev="PD" type="fix">Fix a number of edge-cases where 
file-handles would be leaked</action>

Modified: poi/trunk/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java?rev=1734843&r1=1734842&r2=1734843&view=diff
==============================================================================
--- poi/trunk/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java 
(original)
+++ poi/trunk/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java Sun Mar 
13 19:31:32 2016
@@ -34,6 +34,7 @@ import javax.crypto.spec.IvParameterSpec
 import javax.crypto.spec.RC2ParameterSpec;
 
 import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.util.Internal;
 import org.apache.poi.util.LittleEndian;
 import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.StringUtil;
@@ -41,34 +42,36 @@ import org.apache.poi.util.StringUtil;
 /**
  * Helper functions used for standard and agile encryption
  */
+@Internal
 public class CryptoFunctions {
     /**
-     * 2.3.4.7 ECMA-376 Document Encryption Key Generation (Standard 
Encryption)
-     * 2.3.4.11 Encryption Key Generation (Agile Encryption)
+     * <p><cite>2.3.4.7 ECMA-376 Document Encryption Key Generation (Standard 
Encryption)<br/>
+     * 2.3.4.11 Encryption Key Generation (Agile Encryption)</cite></p>
      * 
-     * The encryption key for ECMA-376 document encryption [ECMA-376] using 
agile encryption MUST be 
-     * generated by using the following method, which is derived from PKCS #5: 
Password-Based
-     * Cryptography Version 2.0 [RFC2898].
+     * <p>The encryption key for ECMA-376 document encryption [ECMA-376] using 
agile
+     * encryption MUST be generated by using the following method, which is 
derived from PKCS #5:
+     * <a href="https://www.ietf.org/rfc/rfc2898.txt";>Password-Based 
Cryptography Version 2.0 [RFC2898]</a>.</p>
      * 
-     * Let H() be a hashing algorithm as determined by the 
PasswordKeyEncryptor.hashAlgorithm
-     * element, H_n be the hash data of the n-th iteration, and a plus sign 
(+) represent concatenation. The
-     * password MUST be provided as an array of Unicode characters. 
Limitations on the length of the
-     * password and the characters used by the password are 
implementation-dependent. The initial
-     * password hash is generated as follows:
+     * <p>Let H() be a hashing algorithm as determined by the 
PasswordKeyEncryptor.hashAlgorithm
+     * element, H_n be the hash data of the n-th iteration, and a plus sign 
(+) represent concatenation.
+     * The password MUST be provided as an array of Unicode characters. 
Limitations on the length of the
+     * password and the characters used by the password are 
implementation-dependent.
+     * The initial password hash is generated as follows:</p>
      * 
-     * - H_0 = H(salt + password)
      * 
-     * The salt used MUST be generated randomly. The salt MUST be stored in the
-     * PasswordKeyEncryptor.saltValue element contained within the 
\EncryptionInfo stream (1) as
-     * specified in section 2.3.4.10. The hash is then iterated by using the 
following approach:
+     * <pre>H_0 = H(salt + password)</pre>
      * 
-     * - H_n = H(iterator + H_n-1)
+     * <p>The salt used MUST be generated randomly. The salt MUST be stored in 
the
+     * PasswordKeyEncryptor.saltValue element contained within the 
\EncryptionInfo stream as
+     * specified in section 2.3.4.10. The hash is then iterated by using the 
following approach:</p>
      * 
-     * where iterator is an unsigned 32-bit value that is initially set to 
0x00000000 and then incremented
+     * <pre>H_n = H(iterator + H_n-1)</pre>
+     * 
+     * <p>where iterator is an unsigned 32-bit value that is initially set to 
0x00000000 and then incremented
      * monotonically on each iteration until PasswordKey.spinCount iterations 
have been performed.
-     * The value of iterator on the last iteration MUST be one less than 
PasswordKey.spinCount.
+     * The value of iterator on the last iteration MUST be one less than 
PasswordKey.spinCount.</p>
      * 
-     * For POI, H_final will be calculated by {@link 
#generateKey(byte[],HashAlgorithm,byte[],int)}
+     * <p>For POI, H_final will be calculated by {@link 
#generateKey(byte[],HashAlgorithm,byte[],int)}</p>
      *
      * @param password
      * @param hashAlgorithm
@@ -124,19 +127,21 @@ public class CryptoFunctions {
     }    
 
     /**
-     * 2.3.4.12 Initialization Vector Generation (Agile Encryption)
+     * <p><cite>2.3.4.12 Initialization Vector Generation (Agile 
Encryption)</cite></p>
      * 
-     * Initialization vectors are used in all cases for agile encryption. An 
initialization vector MUST be
+     * <p>Initialization vectors are used in all cases for agile encryption. 
An initialization vector MUST be
      * generated by using the following method, where H() is a hash function 
that MUST be the same as
-     * specified in section 2.3.4.11 and a plus sign (+) represents 
concatenation:
-     * 1. If a blockKey is provided, let IV be a hash of the KeySalt and the 
following value:
-     *    blockKey: IV = H(KeySalt + blockKey)
-     * 2. If a blockKey is not provided, let IV be equal to the following 
value:
-     *    KeySalt:IV = KeySalt.
-     * 3. If the number of bytes in the value of IV is less than the the value 
of the blockSize attribute
-     *    corresponding to the cipherAlgorithm attribute, pad the array of 
bytes by appending 0x36 until
-     *    the array is blockSize bytes. If the array of bytes is larger than 
blockSize bytes, truncate the
-     *    array to blockSize bytes. 
+     * specified in section 2.3.4.11 and a plus sign (+) represents 
concatenation:</p>
+     * <ul>
+     * <li>If a blockKey is provided, let IV be a hash of the KeySalt and the 
following value:<br/>
+     *     {@code blockKey: IV = H(KeySalt + blockKey)}</li>
+     * <li>If a blockKey is not provided, let IV be equal to the following 
value:<br/>
+     *     {@code KeySalt:IV = KeySalt}</li>
+     * <li>If the number of bytes in the value of IV is less than the the 
value of the blockSize attribute
+     *     corresponding to the cipherAlgorithm attribute, pad the array of 
bytes by appending 0x36 until
+     *     the array is blockSize bytes. If the array of bytes is larger than 
blockSize bytes, truncate the
+     *     array to blockSize bytes.</li>
+     * </ul> 
      **/
     public static byte[] generateIv(HashAlgorithm hashAlgorithm, byte[] salt, 
byte[] blockKey, int blockSize) {
         byte iv[] = salt;
@@ -149,21 +154,19 @@ public class CryptoFunctions {
     }
 
     /**
-     * 2.3.4.11 Encryption Key Generation (Agile Encryption)
-     * 
-     * ... continued ...
+     * <p><cite>2.3.4.11 Encryption Key Generation (Agile 
Encryption)</cite></p>
      * 
-     * The final hash data that is used for an encryption key is then 
generated by using the following
-     * method:
+     * <p>The final hash data that is used for an encryption key is then 
generated by using the following
+     * method:</p>
      * 
-     * - H_final = H(H_n + blockKey)
+     * <pre>H_final = H(H_n + blockKey)</pre>
      * 
-     * where blockKey represents an array of bytes used to prevent two 
different blocks from encrypting
-     * to the same cipher text.
+     * <p>where blockKey represents an array of bytes used to prevent two 
different blocks from encrypting
+     * to the same cipher text.</p>
      * 
-     * If the size of the resulting H_final is smaller than that of 
PasswordKeyEncryptor.keyBits, the key
+     * <p>If the size of the resulting H_final is smaller than that of 
PasswordKeyEncryptor.keyBits, the key
      * MUST be padded by appending bytes with a value of 0x36. If the hash 
value is larger in size than
-     * PasswordKeyEncryptor.keyBits, the key is obtained by truncating the 
hash value. 
+     * PasswordKeyEncryptor.keyBits, the key is obtained by truncating the 
hash value.</p> 
      *
      * @param passwordHash
      * @param hashAlgorithm
@@ -178,6 +181,21 @@ public class CryptoFunctions {
         return getBlock36(key, keySize);
     }
 
+    /**
+     * Initialize a new cipher object with the given cipher properties and no 
padding
+     * If the given algorithm is not implemented in the JCE, it will try to 
load it from the bouncy castle
+     * provider.
+     *
+     * @param key the secrect key
+     * @param cipherAlgorithm the cipher algorithm
+     * @param chain the chaining mode
+     * @param vec the initialization vector (IV), can be null
+     * @param cipherMode Cipher.DECRYPT_MODE or Cipher.ENCRYPT_MODE
+     * @return the requested cipher
+     * @throws GeneralSecurityException
+     * @throws EncryptedDocumentException if the initialization failed or if 
an algorithm was specified,
+     *   which depends on a missing bouncy castle provider 
+     */
     public static Cipher getCipher(SecretKey key, CipherAlgorithm 
cipherAlgorithm, ChainingMode chain, byte[] vec, int cipherMode) {
         return getCipher(key, cipherAlgorithm, chain, vec, cipherMode, null);
     }
@@ -192,7 +210,7 @@ public class CryptoFunctions {
      * @param chain the chaining mode
      * @param vec the initialization vector (IV), can be null
      * @param cipherMode Cipher.DECRYPT_MODE or Cipher.ENCRYPT_MODE
-     * @param padding
+     * @param padding the padding (null = NOPADDING, ANSIX923Padding, 
PKCS5Padding, PKCS7Padding, ISO10126Padding, ...)
      * @return the requested cipher
      * @throws GeneralSecurityException
      * @throws EncryptedDocumentException if the initialization failed or if 
an algorithm was specified,
@@ -243,7 +261,7 @@ public class CryptoFunctions {
      * @param size the size of the returned byte array
      * @return the padded hash
      */
-    public static byte[] getBlock36(byte[] hash, int size) {
+    private static byte[] getBlock36(byte[] hash, int size) {
         return getBlockX(hash, size, (byte)0x36);
     }
 
@@ -296,30 +314,33 @@ public class CryptoFunctions {
 
     @SuppressWarnings("unchecked")
     public static void registerBouncyCastle() {
-        if (Security.getProvider("BC") != null) return;
+        if (Security.getProvider("BC") != null) {
+            return;
+        }
+        
         try {
             ClassLoader cl = Thread.currentThread().getContextClassLoader();
             String bcProviderName = 
"org.bouncycastle.jce.provider.BouncyCastleProvider";
             Class<Provider> clazz = 
(Class<Provider>)cl.loadClass(bcProviderName);
             Security.addProvider(clazz.newInstance());
         } catch (Exception e) {
-            throw new EncryptedDocumentException("Only the BouncyCastle 
provider supports your encryption settings - please add it to the classpath.");
+            throw new EncryptedDocumentException("Only the BouncyCastle 
provider supports your encryption settings - please add it to the classpath.", 
e);
         }
     }
 
-    private static final int InitialCodeArray[] = { 
+    private static final int INITIAL_CODE_ARRAY[] = { 
         0xE1F0, 0x1D0F, 0xCC9C, 0x84C0, 0x110C, 0x0E10, 0xF1CE, 
         0x313E, 0x1872, 0xE139, 0xD40F, 0x84F9, 0x280C, 0xA96A, 
         0x4EC3
     };
 
-    private static final byte PadArray[] = {
+    private static final byte PAD_ARRAY[] = {
         (byte)0xBB, (byte)0xFF, (byte)0xFF, (byte)0xBA, (byte)0xFF,
         (byte)0xFF, (byte)0xB9, (byte)0x80, (byte)0x00, (byte)0xBE,
         (byte)0x0F, (byte)0x00, (byte)0xBF, (byte)0x0F, (byte)0x00
     };
     
-    private static final int EncryptionMatrix[][] = {
+    private static final int ENCRYPTION_MATRIX[][] = {
         /* char 1  */ {0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09},
         /* char 2  */ {0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF},
         /* char 3  */ {0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0},
@@ -338,6 +359,40 @@ public class CryptoFunctions {
     };
 
     /**
+     * Create the verifier for xor obfuscation (method 1)
+     *
+     * @see <a 
href="http://msdn.microsoft.com/en-us/library/dd926947.aspx";>2.3.7.1 Binary 
Document Password Verifier Derivation Method 1</a>
+     * @see <a 
href="http://msdn.microsoft.com/en-us/library/dd905229.aspx";>2.3.7.4 Binary 
Document Password Verifier Derivation Method 2</a>
+     * @see <a 
href="http://www.ecma-international.org/news/TC45_current_work/Office Open XML 
Part 4 - Markup Language Reference.pdf">Part 4 - Markup Language Reference - 
Ecma International - 3.2.12 fileSharing</a>
+     * 
+     * @param password the password
+     * @return the verifier (actually a short value)
+     */
+    public static int createXorVerifier1(String password) {
+        byte[] arrByteChars = toAnsiPassword(password);
+        
+        // SET Verifier TO 0x0000
+        short verifier = 0;
+
+        // FOR EACH PasswordByte IN PasswordArray IN REVERSE ORDER
+        for (int i = arrByteChars.length-1; i >= 0; i--) {
+            // SET Verifier TO Intermediate3 BITWISE XOR PasswordByte
+            verifier = rotateLeftBase15Bit(verifier);
+            verifier ^= arrByteChars[i];
+        }
+
+        // as we haven't prepended the password length into the input array
+        // we need to do it now separately ...
+        verifier = rotateLeftBase15Bit(verifier);
+        verifier ^= arrByteChars.length;
+        
+        // RETURN Verifier BITWISE XOR 0xCE4B
+        verifier ^= 0xCE4B; // (0x8000 | ('N' << 8) | 'K')
+        
+        return verifier & 0xFFFF;
+    }
+ 
+    /**
      * This method generates the xor verifier for word documents &lt; 2007 
(method 2).
      * Its output will be used as password input for the newer word 
generations which
      * utilize a real hashing algorithm like sha1.
@@ -360,22 +415,12 @@ public class CryptoFunctions {
             // Truncate the password to 15 characters
             password = password.substring(0, Math.min(password.length(), 
maxPasswordLength));
 
-            // Construct a new NULL-terminated string consisting of 
single-byte characters:
-            //  -- > Get the single-byte values by iterating through the 
Unicode characters of the truncated Password.
-            //   --> For each character, if the low byte is not equal to 0, 
take it. Otherwise, take the high byte.
-            byte[] arrByteChars = new byte[password.length()];
+            byte[] arrByteChars = toAnsiPassword(password);
             
-            for (int i = 0; i < password.length(); i++) {
-                int intTemp = password.charAt(i);
-                byte lowByte = (byte)(intTemp & 0x00FF);
-                byte highByte = (byte)((intTemp & 0xFF00) >> 8);
-                arrByteChars[i] = (lowByte != 0 ? lowByte : highByte);
-            }
-
             // Compute the high-order word of the new key:
 
             // --> Initialize from the initial code array (see below), 
depending on the passwords length. 
-            int highOrderWord = InitialCodeArray[arrByteChars.length - 1];
+            int highOrderWord = INITIAL_CODE_ARRAY[arrByteChars.length - 1];
 
             // --> For each character in the password:
             //      --> For every bit in the character, starting with the 
least significant and progressing to (but excluding) 
@@ -385,35 +430,18 @@ public class CryptoFunctions {
                 int tmp = maxPasswordLength - arrByteChars.length + i;
                 for (int intBit = 0; intBit < 7; intBit++) {
                     if ((arrByteChars[i] & (0x0001 << intBit)) != 0) {
-                        highOrderWord ^= EncryptionMatrix[tmp][intBit];
+                        highOrderWord ^= ENCRYPTION_MATRIX[tmp][intBit];
                     }
                 }
             }
             
             // Compute the low-order word of the new key:
-            
-            // SET Verifier TO 0x0000
-            short verifier = 0;
-
-            // FOR EACH PasswordByte IN PasswordArray IN REVERSE ORDER
-            for (int i = arrByteChars.length-1; i >= 0; i--) {
-                // SET Verifier TO Intermediate3 BITWISE XOR PasswordByte
-                verifier = rotateLeftBase15Bit(verifier);
-                verifier ^= arrByteChars[i];
-            }
-
-            // as we haven't prepended the password length into the input array
-            // we need to do it now separately ...
-            verifier = rotateLeftBase15Bit(verifier);
-            verifier ^= arrByteChars.length;
-            
-            // RETURN Verifier BITWISE XOR 0xCE4B
-            verifier ^= 0xCE4B; // (0x8000 | ('N' << 8) | 'K')
+            int verifier = createXorVerifier1(password);
 
             // The byte order of the result shall be reversed [password 
"Example": 0x64CEED7E becomes 7EEDCE64],
             // and that value shall be hashed as defined by the attribute 
values.
             
-            LittleEndian.putShort(generatedKey, 0, verifier);
+            LittleEndian.putShort(generatedKey, 0, (short)verifier);
             LittleEndian.putShort(generatedKey, 2, (short)highOrderWord);
         }
         
@@ -444,21 +472,6 @@ public class CryptoFunctions {
     }
 
     /**
-     * Create the verifier for xor obfuscation (method 1)
-     *
-     * @see <a 
href="http://msdn.microsoft.com/en-us/library/dd926947.aspx";>2.3.7.1 Binary 
Document Password Verifier Derivation Method 1</a>
-     * @see <a 
href="http://msdn.microsoft.com/en-us/library/dd905229.aspx";>2.3.7.4 Binary 
Document Password Verifier Derivation Method 2</a>
-     * 
-     * @param password the password
-     * @return the verifier (actually a short value)
-     */
-    public static int createXorVerifier1(String password) {
-        // the verifier for method 1 is part of the verifier for method 2
-        // so we simply chop it from there
-        return createXorVerifier2(password) & 0xFFFF;
-    }
- 
-    /**
      * Create the xor key for xor obfuscation, which is used to create the xor 
array (method 1)
      *
      * @see <a 
href="http://msdn.microsoft.com/en-us/library/dd924704.aspx";>2.3.7.2 Binary 
Document XOR Array Initialization Method 1</a>
@@ -490,12 +503,12 @@ public class CryptoFunctions {
         // The MS-OFFCRYPTO misses some infos about the various rotation sizes 
         byte obfuscationArray[] = new byte[16];
         System.arraycopy(passBytes, 0, obfuscationArray, 0, passBytes.length);
-        System.arraycopy(PadArray, 0, obfuscationArray, passBytes.length, 
PadArray.length-passBytes.length+1);
+        System.arraycopy(PAD_ARRAY, 0, obfuscationArray, passBytes.length, 
PAD_ARRAY.length-passBytes.length+1);
         
         int xorKey = createXorKey1(password);
         
-        // rotation of key values is application dependent
-        int nRotateSize = 2; /* Excel = 2; Word = 7 */
+        // rotation of key values is application dependent - Excel = 2 / Word 
= 7 
+        int nRotateSize = 2;
         
         byte baseKeyLE[] = { (byte)(xorKey & 0xFF), (byte)((xorKey >>> 8) & 
0xFF) };
         for (int i=0; i<obfuscationArray.length; i++) {
@@ -505,7 +518,33 @@ public class CryptoFunctions {
         
         return obfuscationArray;
     }
+    
+    /**
+     * The provided Unicode password string is converted to a ANSI string
+     *
+     * @param password the password
+     * @return the ansi bytes
+     * 
+     * @see <a 
href="http://www.ecma-international.org/news/TC45_current_work/Office Open XML 
Part 4 - Markup Language Reference.pdf">Part 4 - Markup Language Reference - 
Ecma International</a> (3.2.29 workbookProtection)
+     */
+    private static byte[] toAnsiPassword(String password) {
+        // TODO: charset conversion (see ecma spec) 
+        
+        // Get the single-byte values by iterating through the Unicode 
characters.
+        // For each character, if the low byte is not equal to 0, take it.
+        // Otherwise, take the high byte.
+        byte[] arrByteChars = new byte[password.length()];
+        
+        for (int i = 0; i < password.length(); i++) {
+            int intTemp = password.charAt(i);
+            byte lowByte = (byte)(intTemp & 0xFF);
+            byte highByte = (byte)((intTemp >>> 8) & 0xFF);
+            arrByteChars[i] = (lowByte != 0 ? lowByte : highByte);
+        }
 
+        return arrByteChars;
+    }
+    
     private static byte rotateLeft(byte bits, int shift) {
         return (byte)(((bits & 0xff) << shift) | ((bits & 0xff) >>> (8 - 
shift)));
     }

Modified: 
poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java
URL: 
http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java?rev=1734843&r1=1734842&r2=1734843&view=diff
==============================================================================
--- poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java 
(original)
+++ poi/trunk/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java 
Sun Mar 13 19:31:32 2016
@@ -1222,4 +1222,23 @@ public final class TestHSSFSheet extends
         
         wb.close();
     }
+
+    @Test
+    public void bug59135() throws IOException {
+        HSSFWorkbook wb1 = new HSSFWorkbook();
+        wb1.createSheet().protectSheet("1111.2222.3333.1234");
+        HSSFWorkbook wb2 = HSSFTestDataSamples.writeOutAndReadBack(wb1);
+        wb1.close();
+        
+        assertEquals((short)0xb86b, wb2.getSheetAt(0).getPassword());
+        wb2.close();
+
+        HSSFWorkbook wb3 = new HSSFWorkbook();
+        wb3.createSheet().protectSheet("1111.2222.3333.12345");
+        HSSFWorkbook wb4 = HSSFTestDataSamples.writeOutAndReadBack(wb3);
+        wb3.close();
+        
+        assertEquals((short)0xbecc, wb4.getSheetAt(0).getPassword());
+        wb4.close();
+    }
 }



---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to