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 27KB

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