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