You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CryptoFunctions.java 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.poifs.crypt;
  16. import java.nio.charset.StandardCharsets;
  17. import java.security.DigestException;
  18. import java.security.GeneralSecurityException;
  19. import java.security.Key;
  20. import java.security.MessageDigest;
  21. import java.security.Provider;
  22. import java.security.Security;
  23. import java.security.spec.AlgorithmParameterSpec;
  24. import java.util.Arrays;
  25. import java.util.Locale;
  26. import javax.crypto.Cipher;
  27. import javax.crypto.Mac;
  28. import javax.crypto.SecretKey;
  29. import javax.crypto.spec.IvParameterSpec;
  30. import javax.crypto.spec.RC2ParameterSpec;
  31. import org.apache.poi.EncryptedDocumentException;
  32. import org.apache.poi.util.IOUtils;
  33. import org.apache.poi.util.Internal;
  34. import org.apache.poi.util.LittleEndian;
  35. import org.apache.poi.util.LittleEndianConsts;
  36. import org.apache.poi.util.StringUtil;
  37. /**
  38. * Helper functions used for standard and agile encryption
  39. */
  40. @Internal
  41. public final class CryptoFunctions {
  42. //arbitrarily selected; may need to increase
  43. private static final int MAX_RECORD_LENGTH = 100_000;
  44. private CryptoFunctions() {
  45. }
  46. /**
  47. * <p><cite>2.3.4.7 ECMA-376 Document Encryption Key Generation (Standard Encryption)<br>
  48. * 2.3.4.11 Encryption Key Generation (Agile Encryption)</cite></p>
  49. *
  50. * <p>The encryption key for ECMA-376 document encryption [ECMA-376] using agile
  51. * encryption MUST be generated by using the following method, which is derived from PKCS #5:
  52. * <a href="https://www.ietf.org/rfc/rfc2898.txt">Password-Based Cryptography Version 2.0 [RFC2898]</a>.</p>
  53. *
  54. * <p>Let H() be a hashing algorithm as determined by the PasswordKeyEncryptor.hashAlgorithm
  55. * element, H_n be the hash data of the n-th iteration, and a plus sign (+) represent concatenation.
  56. * The password MUST be provided as an array of Unicode characters. Limitations on the length of the
  57. * password and the characters used by the password are implementation-dependent.
  58. * The initial password hash is generated as follows:</p>
  59. *
  60. *
  61. * <pre>H_0 = H(salt + password)</pre>
  62. *
  63. * <p>The salt used MUST be generated randomly. The salt MUST be stored in the
  64. * PasswordKeyEncryptor.saltValue element contained within the \EncryptionInfo stream as
  65. * specified in section 2.3.4.10. The hash is then iterated by using the following approach:</p>
  66. *
  67. * <pre>H_n = H(iterator + H_n-1)</pre>
  68. *
  69. * <p>where iterator is an unsigned 32-bit value that is initially set to 0x00000000 and then incremented
  70. * monotonically on each iteration until PasswordKey.spinCount iterations have been performed.
  71. * The value of iterator on the last iteration MUST be one less than PasswordKey.spinCount.</p>
  72. *
  73. * <p>For POI, H_final will be calculated by {@link #generateKey(byte[],HashAlgorithm,byte[],int)}</p>
  74. *
  75. * @param password the password
  76. * @param hashAlgorithm the hash algorithm
  77. * @param salt the initial salt value
  78. * @param spinCount the repetition count
  79. * @return the hashed password
  80. */
  81. public static byte[] hashPassword(String password, HashAlgorithm hashAlgorithm, byte[] salt, int spinCount) {
  82. return hashPassword(password, hashAlgorithm, salt, spinCount, true);
  83. }
  84. /**
  85. * Generalized method for read and write protection hash generation.
  86. * The difference is, read protection uses the order iterator then hash in the hash loop, whereas write protection
  87. * uses first the last hash value and then the current iterator value
  88. *
  89. * @param password the pasword
  90. * @param hashAlgorithm the hash algorighm
  91. * @param salt the initial salt value
  92. * @param spinCount the repetition count
  93. * @param iteratorFirst if true, the iterator is hashed before the n-1 hash value,
  94. * if false the n-1 hash value is applied first
  95. * @return the hashed password
  96. */
  97. @SuppressWarnings({"squid:S2068"})
  98. public static byte[] hashPassword(String password, HashAlgorithm hashAlgorithm, byte[] salt, int spinCount, boolean iteratorFirst) {
  99. // If no password was given, use the default
  100. if (password == null) {
  101. password = Decryptor.DEFAULT_PASSWORD;
  102. }
  103. MessageDigest hashAlg = getMessageDigest(hashAlgorithm);
  104. hashAlg.update(salt);
  105. byte[] hash = hashAlg.digest(StringUtil.getToUnicodeLE(password));
  106. byte[] iterator = new byte[LittleEndianConsts.INT_SIZE];
  107. byte[] first = (iteratorFirst ? iterator : hash);
  108. byte[] second = (iteratorFirst ? hash : iterator);
  109. try {
  110. for (int i = 0; i < spinCount; i++) {
  111. LittleEndian.putInt(iterator, 0, i);
  112. hashAlg.reset();
  113. hashAlg.update(first);
  114. hashAlg.update(second);
  115. hashAlg.digest(hash, 0, hash.length); // don't create hash buffer everytime new
  116. }
  117. } catch (DigestException e) {
  118. throw new EncryptedDocumentException("error in password hashing");
  119. }
  120. return hash;
  121. }
  122. /**
  123. * <p><cite>2.3.4.12 Initialization Vector Generation (Agile Encryption)</cite></p>
  124. *
  125. * <p>Initialization vectors are used in all cases for agile encryption. An initialization vector MUST be
  126. * generated by using the following method, where H() is a hash function that MUST be the same as
  127. * specified in section 2.3.4.11 and a plus sign (+) represents concatenation:</p>
  128. * <ul>
  129. * <li>If a blockKey is provided, let IV be a hash of the KeySalt and the following value:<br>
  130. * {@code blockKey: IV = H(KeySalt + blockKey)}</li>
  131. * <li>If a blockKey is not provided, let IV be equal to the following value:<br>
  132. * {@code KeySalt:IV = KeySalt}</li>
  133. * <li>If the number of bytes in the value of IV is less than the the value of the blockSize attribute
  134. * corresponding to the cipherAlgorithm attribute, pad the array of bytes by appending 0x36 until
  135. * the array is blockSize bytes. If the array of bytes is larger than blockSize bytes, truncate the
  136. * array to blockSize bytes.</li>
  137. * </ul>
  138. **/
  139. public static byte[] generateIv(HashAlgorithm hashAlgorithm, byte[] salt, byte[] blockKey, int blockSize) {
  140. byte[] iv = salt;
  141. if (blockKey != null) {
  142. MessageDigest hashAlgo = getMessageDigest(hashAlgorithm);
  143. hashAlgo.update(salt);
  144. iv = hashAlgo.digest(blockKey);
  145. }
  146. return getBlock36(iv, blockSize);
  147. }
  148. /**
  149. * <p><cite>2.3.4.11 Encryption Key Generation (Agile Encryption)</cite></p>
  150. *
  151. * <p>The final hash data that is used for an encryption key is then generated by using the following
  152. * method:</p>
  153. *
  154. * <pre>H_final = H(H_n + blockKey)</pre>
  155. *
  156. * <p>where blockKey represents an array of bytes used to prevent two different blocks from encrypting
  157. * to the same cipher text.</p>
  158. *
  159. * <p>If the size of the resulting H_final is smaller than that of PasswordKeyEncryptor.keyBits, the key
  160. * MUST be padded by appending bytes with a value of 0x36. If the hash value is larger in size than
  161. * PasswordKeyEncryptor.keyBits, the key is obtained by truncating the hash value.</p>
  162. *
  163. * @param passwordHash the hashed password byte
  164. * @param hashAlgorithm the hash algorithm
  165. * @param blockKey the block key
  166. * @param keySize the key size
  167. * @return intermediate key
  168. */
  169. public static byte[] generateKey(byte[] passwordHash, HashAlgorithm hashAlgorithm, byte[] blockKey, int keySize) {
  170. MessageDigest hashAlgo = getMessageDigest(hashAlgorithm);
  171. hashAlgo.update(passwordHash);
  172. byte[] key = hashAlgo.digest(blockKey);
  173. return getBlock36(key, keySize);
  174. }
  175. /**
  176. * Initialize a new cipher object with the given cipher properties and no padding
  177. * If the given algorithm is not implemented in the JCE, it will try to load it from the bouncy castle
  178. * provider.
  179. *
  180. * @param key the secret key
  181. * @param cipherAlgorithm the cipher algorithm
  182. * @param chain the chaining mode
  183. * @param vec the initialization vector (IV), can be null
  184. * @param cipherMode Cipher.DECRYPT_MODE or Cipher.ENCRYPT_MODE
  185. * @return the requested cipher
  186. * @throws EncryptedDocumentException if the initialization failed or if an algorithm was specified,
  187. * which depends on a missing bouncy castle provider
  188. */
  189. public static Cipher getCipher(SecretKey key, CipherAlgorithm cipherAlgorithm, ChainingMode chain, byte[] vec, int cipherMode) {
  190. return getCipher(key, cipherAlgorithm, chain, vec, cipherMode, null);
  191. }
  192. /**
  193. * Initialize a new cipher object with the given cipher properties
  194. * If the given algorithm is not implemented in the JCE, it will try to load it from the bouncy castle
  195. * provider.
  196. *
  197. * @param key the secret key
  198. * @param cipherAlgorithm the cipher algorithm
  199. * @param chain the chaining mode
  200. * @param vec the initialization vector (IV), can be null
  201. * @param cipherMode Cipher.DECRYPT_MODE or Cipher.ENCRYPT_MODE
  202. * @param padding the padding (null = NOPADDING, ANSIX923Padding, PKCS5Padding, PKCS7Padding, ISO10126Padding, ...)
  203. * @return the requested cipher
  204. * @throws EncryptedDocumentException if the initialization failed or if an algorithm was specified,
  205. * which depends on a missing bouncy castle provider
  206. */
  207. public static Cipher getCipher(Key key, CipherAlgorithm cipherAlgorithm, ChainingMode chain, byte[] vec, int cipherMode, String padding) {
  208. int keySizeInBytes = key.getEncoded().length;
  209. if (padding == null) padding = "NoPadding";
  210. try {
  211. // Ensure the JCE policies files allow for this sized key
  212. if (Cipher.getMaxAllowedKeyLength(cipherAlgorithm.jceId) < keySizeInBytes*8) {
  213. throw new EncryptedDocumentException("Export Restrictions in place - please install JCE Unlimited Strength Jurisdiction Policy files");
  214. }
  215. Cipher cipher;
  216. if (cipherAlgorithm == CipherAlgorithm.rc4) {
  217. cipher = Cipher.getInstance(cipherAlgorithm.jceId);
  218. } else if (cipherAlgorithm.needsBouncyCastle) {
  219. registerBouncyCastle();
  220. cipher = Cipher.getInstance(cipherAlgorithm.jceId + "/" + chain.jceId + "/" + padding, "BC");
  221. } else {
  222. cipher = Cipher.getInstance(cipherAlgorithm.jceId + "/" + chain.jceId + "/" + padding);
  223. }
  224. if (vec == null) {
  225. cipher.init(cipherMode, key);
  226. } else {
  227. AlgorithmParameterSpec aps;
  228. if (cipherAlgorithm == CipherAlgorithm.rc2) {
  229. aps = new RC2ParameterSpec(key.getEncoded().length*8, vec);
  230. } else {
  231. aps = new IvParameterSpec(vec);
  232. }
  233. cipher.init(cipherMode, key, aps);
  234. }
  235. return cipher;
  236. } catch (GeneralSecurityException e) {
  237. throw new EncryptedDocumentException(e);
  238. }
  239. }
  240. /**
  241. * Returns a new byte array with a truncated to the given size.
  242. * If the hash has less then size bytes, it will be filled with 0x36-bytes
  243. *
  244. * @param hash the to be truncated/filled hash byte array
  245. * @param size the size of the returned byte array
  246. * @return the padded hash
  247. */
  248. private static byte[] getBlock36(byte[] hash, int size) {
  249. return getBlockX(hash, size, (byte)0x36);
  250. }
  251. /**
  252. * Returns a new byte array with a truncated to the given size.
  253. * If the hash has less then size bytes, it will be filled with 0-bytes
  254. *
  255. * @param hash the to be truncated/filled hash byte array
  256. * @param size the size of the returned byte array
  257. * @return the padded hash
  258. */
  259. public static byte[] getBlock0(byte[] hash, int size) {
  260. return getBlockX(hash, size, (byte)0);
  261. }
  262. private static byte[] getBlockX(byte[] hash, int size, byte fill) {
  263. if (hash.length == size) return hash;
  264. byte[] result = IOUtils.safelyAllocate(size, MAX_RECORD_LENGTH);
  265. Arrays.fill(result, fill);
  266. System.arraycopy(hash, 0, result, 0, Math.min(result.length, hash.length));
  267. return result;
  268. }
  269. public static MessageDigest getMessageDigest(HashAlgorithm hashAlgorithm) {
  270. try {
  271. if (hashAlgorithm.needsBouncyCastle) {
  272. registerBouncyCastle();
  273. return MessageDigest.getInstance(hashAlgorithm.jceId, "BC");
  274. } else {
  275. return MessageDigest.getInstance(hashAlgorithm.jceId);
  276. }
  277. } catch (GeneralSecurityException e) {
  278. throw new EncryptedDocumentException("hash algo not supported", e);
  279. }
  280. }
  281. public static Mac getMac(HashAlgorithm hashAlgorithm) {
  282. try {
  283. if (hashAlgorithm.needsBouncyCastle) {
  284. registerBouncyCastle();
  285. return Mac.getInstance(hashAlgorithm.jceHmacId, "BC");
  286. } else {
  287. return Mac.getInstance(hashAlgorithm.jceHmacId);
  288. }
  289. } catch (GeneralSecurityException e) {
  290. throw new EncryptedDocumentException("hmac algo not supported", e);
  291. }
  292. }
  293. @SuppressWarnings("unchecked")
  294. public static void registerBouncyCastle() {
  295. if (Security.getProvider("BC") != null) {
  296. return;
  297. }
  298. try {
  299. ClassLoader cl = CryptoFunctions.class.getClassLoader();
  300. String bcProviderName = "org.bouncycastle.jce.provider.BouncyCastleProvider";
  301. Class<Provider> clazz = (Class<Provider>)cl.loadClass(bcProviderName);
  302. Security.addProvider(clazz.getDeclaredConstructor().newInstance());
  303. } catch (Exception e) {
  304. throw new EncryptedDocumentException("Only the BouncyCastle provider supports your encryption settings - please add it to the classpath.", e);
  305. }
  306. }
  307. private static final int[] INITIAL_CODE_ARRAY = {
  308. 0xE1F0, 0x1D0F, 0xCC9C, 0x84C0, 0x110C, 0x0E10, 0xF1CE,
  309. 0x313E, 0x1872, 0xE139, 0xD40F, 0x84F9, 0x280C, 0xA96A,
  310. 0x4EC3
  311. };
  312. private static final byte[] PAD_ARRAY = {
  313. (byte) 0xBB, (byte) 0xFF, (byte) 0xFF, (byte) 0xBA, (byte) 0xFF,
  314. (byte) 0xFF, (byte) 0xB9, (byte) 0x80, (byte) 0x00, (byte) 0xBE,
  315. (byte) 0x0F, (byte) 0x00, (byte) 0xBF, (byte) 0x0F, (byte) 0x00
  316. };
  317. private static final int[][] ENCRYPTION_MATRIX = {
  318. /* char 1 */ {0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09},
  319. /* char 2 */ {0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF},
  320. /* char 3 */ {0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0},
  321. /* char 4 */ {0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40},
  322. /* char 5 */ {0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5},
  323. /* char 6 */ {0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A},
  324. /* char 7 */ {0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9},
  325. /* char 8 */ {0x47D3, 0x8FA6, 0x0F6D, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0},
  326. /* char 9 */ {0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC},
  327. /* char 10 */ {0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10},
  328. /* char 11 */ {0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168},
  329. /* char 12 */ {0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C},
  330. /* char 13 */ {0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD},
  331. /* char 14 */ {0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC},
  332. /* char 15 */ {0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4}
  333. };
  334. /**
  335. * Create the verifier for xor obfuscation (method 1)
  336. *
  337. * @see <a href="http://msdn.microsoft.com/en-us/library/dd926947.aspx">2.3.7.1 Binary Document Password Verifier Derivation Method 1</a>
  338. * @see <a href="http://msdn.microsoft.com/en-us/library/dd905229.aspx">2.3.7.4 Binary Document Password Verifier Derivation Method 2</a>
  339. * @see <a href="https://www.ecma-international.org/publications-and-standards/standards/ecma-376/">Part 4 - Markup Language Reference - Ecma International - 3.2.12 fileSharing</a>
  340. *
  341. * @param password the password
  342. * @return the verifier (actually a short value)
  343. */
  344. public static int createXorVerifier1(String password) {
  345. if (password == null) {
  346. throw new IllegalArgumentException("Password cannot be null");
  347. }
  348. byte[] arrByteChars = toAnsiPassword(password);
  349. // SET Verifier TO 0x0000
  350. short verifier = 0;
  351. if (!password.isEmpty()) {
  352. // FOR EACH PasswordByte IN PasswordArray IN REVERSE ORDER
  353. for (int i = arrByteChars.length-1; i >= 0; i--) {
  354. // SET Verifier TO Intermediate3 BITWISE XOR PasswordByte
  355. verifier = rotateLeftBase15Bit(verifier);
  356. verifier ^= arrByteChars[i];
  357. }
  358. // as we haven't prepended the password length into the input array
  359. // we need to do it now separately ...
  360. verifier = rotateLeftBase15Bit(verifier);
  361. verifier ^= arrByteChars.length;
  362. // RETURN Verifier BITWISE XOR 0xCE4B
  363. verifier ^= 0xCE4B; // (0x8000 | ('N' << 8) | 'K')
  364. }
  365. return verifier & 0xFFFF;
  366. }
  367. /**
  368. * This method generates the xor verifier for word documents &lt; 2007 (method 2).
  369. * Its output will be used as password input for the newer word generations which
  370. * utilize a real hashing algorithm like sha1.
  371. *
  372. * @param password the password
  373. * @return the hashed password
  374. *
  375. * @see <a href="http://msdn.microsoft.com/en-us/library/dd905229.aspx">2.3.7.4 Binary Document Password Verifier Derivation Method 2</a>
  376. * @see <a href="http://blogs.msdn.com/b/vsod/archive/2010/04/05/how-to-set-the-editing-restrictions-in-word-using-open-xml-sdk-2-0.aspx">How to set the editing restrictions in Word using Open XML SDK 2.0</a>
  377. * @see <a href="http://www.aspose.com/blogs/aspose-blogs/vladimir-averkin/archive/2007/08/20/funny-how-the-new-powerful-cryptography-implemented-in-word-2007-turns-it-into-a-perfect-tool-for-document-password-removal.html">Funny: How the new powerful cryptography implemented in Word 2007 turns it into a perfect tool for document password removal.</a>
  378. */
  379. public static int createXorVerifier2(String password) {
  380. if (password == null) {
  381. throw new IllegalArgumentException("Password cannot be null");
  382. }
  383. //Array to hold Key Values
  384. byte[] generatedKey = new byte[4];
  385. //Maximum length of the password is 15 chars.
  386. final int maxPasswordLength = 15;
  387. if (!password.isEmpty()) {
  388. // Truncate the password to 15 characters
  389. password = password.substring(0, Math.min(password.length(), maxPasswordLength));
  390. byte[] arrByteChars = toAnsiPassword(password);
  391. // Compute the high-order word of the new key:
  392. // --> Initialize from the initial code array (see below), depending on the passwords length.
  393. int highOrderWord = INITIAL_CODE_ARRAY[arrByteChars.length - 1];
  394. // --> For each character in the password:
  395. // --> For every bit in the character, starting with the least significant and progressing to (but excluding)
  396. // the most significant, if the bit is set, XOR the keys high-order word with the corresponding word from
  397. // the Encryption Matrix
  398. int line = maxPasswordLength - arrByteChars.length;
  399. for (byte ch : arrByteChars) {
  400. for (int xor : ENCRYPTION_MATRIX[line++]) {
  401. if ((ch & 1) == 1) {
  402. highOrderWord ^= xor;
  403. }
  404. ch >>>= 1;
  405. }
  406. }
  407. // Compute the low-order word of the new key:
  408. int verifier = createXorVerifier1(password);
  409. // The byte order of the result shall be reversed [password "Example": 0x64CEED7E becomes 7EEDCE64],
  410. // and that value shall be hashed as defined by the attribute values.
  411. LittleEndian.putShort(generatedKey, 0, (short)verifier);
  412. LittleEndian.putShort(generatedKey, 2, (short)highOrderWord);
  413. }
  414. return LittleEndian.getInt(generatedKey);
  415. }
  416. /**
  417. * This method generates the xored-hashed password for word documents &lt; 2007.
  418. */
  419. public static String xorHashPassword(String password) {
  420. int hashedPassword = createXorVerifier2(password);
  421. return String.format(Locale.ROOT, "%1$08X", hashedPassword);
  422. }
  423. /**
  424. * Convenience function which returns the reversed xored-hashed password for further
  425. * processing in word documents 2007 and newer, which utilize a real hashing algorithm like sha1.
  426. */
  427. public static String xorHashPasswordReversed(String password) {
  428. int hashedPassword = createXorVerifier2(password);
  429. return String.format(Locale.ROOT, "%1$02X%2$02X%3$02X%4$02X"
  430. , (hashedPassword) & 0xFF
  431. , ( hashedPassword >>> 8 ) & 0xFF
  432. , ( hashedPassword >>> 16 ) & 0xFF
  433. , ( hashedPassword >>> 24 ) & 0xFF
  434. );
  435. }
  436. /**
  437. * Create the xor key for xor obfuscation, which is used to create the xor array (method 1)
  438. *
  439. * @see <a href="http://msdn.microsoft.com/en-us/library/dd924704.aspx">2.3.7.2 Binary Document XOR Array Initialization Method 1</a>
  440. * @see <a href="http://msdn.microsoft.com/en-us/library/dd905229.aspx">2.3.7.4 Binary Document Password Verifier Derivation Method 2</a>
  441. *
  442. * @param password the password
  443. * @return the xor key
  444. */
  445. public static int createXorKey1(String password) {
  446. // the xor key for method 1 is part of the verifier for method 2
  447. // so we simply chop it from there
  448. return createXorVerifier2(password) >>> 16;
  449. }
  450. /**
  451. * Creates an byte array for xor obfuscation (method 1)
  452. *
  453. * @see <a href="http://msdn.microsoft.com/en-us/library/dd924704.aspx">2.3.7.2 Binary Document XOR Array Initialization Method 1</a>
  454. * @see <a href="http://docs.libreoffice.org/oox/html/binarycodec_8cxx_source.html">Libre Office implementation</a>
  455. *
  456. * @param password the password
  457. * @return the byte array for xor obfuscation
  458. */
  459. public static byte[] createXorArray1(String password) {
  460. if (password.length() > 15) {
  461. password = password.substring(0, 15);
  462. }
  463. byte[] passBytes = password.getBytes(StandardCharsets.US_ASCII);
  464. // this code is based on the libre office implementation.
  465. // The MS-OFFCRYPTO misses some infos about the various rotation sizes
  466. byte[] obfuscationArray = new byte[16];
  467. System.arraycopy(passBytes, 0, obfuscationArray, 0, passBytes.length);
  468. System.arraycopy(PAD_ARRAY, 0, obfuscationArray, passBytes.length, PAD_ARRAY.length-passBytes.length+1);
  469. int xorKey = createXorKey1(password);
  470. // rotation of key values is application dependent - Excel = 2 / Word = 7
  471. int nRotateSize = 2;
  472. byte[] baseKeyLE = {(byte) (xorKey & 0xFF), (byte) ((xorKey >>> 8) & 0xFF)};
  473. for (int i=0; i<obfuscationArray.length; i++) {
  474. obfuscationArray[i] ^= baseKeyLE[i&1];
  475. obfuscationArray[i] = rotateLeft(obfuscationArray[i], nRotateSize);
  476. }
  477. return obfuscationArray;
  478. }
  479. /**
  480. * The provided Unicode password string is converted to a ANSI string
  481. *
  482. * @param password the password
  483. * @return the ansi bytes
  484. *
  485. * @see <a href="http://www.ecma-international.org/news/TC45_current_work/Office%20Open%20XML%20Part%204%20-%20Markup%20Language%20Reference.pdf">Part 4 - Markup Language Reference - Ecma International - section 3.2.29 (workbookProtection)</a>
  486. */
  487. private static byte[] toAnsiPassword(String password) {
  488. // TODO: charset conversion (see ecma spec)
  489. // Get the single-byte values by iterating through the Unicode characters.
  490. // For each character, if the low byte is not equal to 0, take it.
  491. // Otherwise, take the high byte.
  492. byte[] arrByteChars = new byte[password.length()];
  493. for (int i = 0; i < password.length(); i++) {
  494. int intTemp = password.charAt(i);
  495. byte lowByte = (byte)(intTemp & 0xFF);
  496. byte highByte = (byte)((intTemp >>> 8) & 0xFF);
  497. arrByteChars[i] = (lowByte != 0 ? lowByte : highByte);
  498. }
  499. return arrByteChars;
  500. }
  501. private static byte rotateLeft(byte bits, int shift) {
  502. return (byte)(((bits & 0xff) << shift) | ((bits & 0xff) >>> (8 - shift)));
  503. }
  504. private static short rotateLeftBase15Bit(short verifier) {
  505. /*
  506. * IF (Verifier BITWISE AND 0x4000) is 0x0000
  507. * SET Intermediate1 TO 0
  508. * ELSE
  509. * SET Intermediate1 TO 1
  510. * ENDIF
  511. */
  512. short intermediate1 = (short)(((verifier & 0x4000) == 0) ? 0 : 1);
  513. /*
  514. * SET Intermediate2 TO Verifier MULTIPLED BY 2
  515. * SET most significant bit of Intermediate2 TO 0
  516. */
  517. short intermediate2 = (short)((verifier<<1) & 0x7FFF);
  518. /*
  519. * SET Intermediate3 TO Intermediate1 BITWISE OR Intermediate2
  520. */
  521. return (short)(intermediate1 | intermediate2);
  522. }
  523. }