123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616 |
- /* ====================================================================
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- ==================================================================== */
- package org.apache.poi.poifs.crypt;
-
- import java.nio.charset.StandardCharsets;
- import java.security.DigestException;
- import java.security.GeneralSecurityException;
- import java.security.Key;
- import java.security.MessageDigest;
- import java.security.Provider;
- import java.security.Security;
- import java.security.spec.AlgorithmParameterSpec;
- import java.util.Arrays;
- import java.util.Locale;
-
- import javax.crypto.Cipher;
- import javax.crypto.Mac;
- import javax.crypto.SecretKey;
- import javax.crypto.spec.IvParameterSpec;
- import javax.crypto.spec.RC2ParameterSpec;
-
- import org.apache.poi.EncryptedDocumentException;
- import org.apache.poi.util.IOUtils;
- import org.apache.poi.util.Internal;
- import org.apache.poi.util.LittleEndian;
- import org.apache.poi.util.LittleEndianConsts;
- import org.apache.poi.util.StringUtil;
-
- /**
- * Helper functions used for standard and agile encryption
- */
- @Internal
- public final class CryptoFunctions {
-
- //arbitrarily selected; may need to increase
- private static final int DEFAULT_MAX_RECORD_LENGTH = 100_000;
- static int MAX_RECORD_LENGTH = DEFAULT_MAX_RECORD_LENGTH;
-
- /**
- * @param length the max record length allowed for CryptoFunctions
- */
- public static void setMaxRecordLength(int length) {
- MAX_RECORD_LENGTH = length;
- }
-
- /**
- * @return the max record length allowed for CryptoFunctions
- */
- public static int getMaxRecordLength() {
- return MAX_RECORD_LENGTH;
- }
-
- private CryptoFunctions() {
- }
-
- /**
- * <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>
- *
- * <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>
- *
- * <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>
- *
- *
- * <pre>H_0 = H(salt + password)</pre>
- *
- * <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>
- *
- * <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.</p>
- *
- * <p>For POI, H_final will be calculated by {@link #generateKey(byte[],HashAlgorithm,byte[],int)}</p>
- *
- * @param password the password
- * @param hashAlgorithm the hash algorithm
- * @param salt the initial salt value
- * @param spinCount the repetition count
- * @return the hashed password
- */
- public static byte[] hashPassword(String password, HashAlgorithm hashAlgorithm, byte[] salt, int spinCount) {
- return hashPassword(password, hashAlgorithm, salt, spinCount, true);
- }
-
- /**
- * Generalized method for read and write protection hash generation.
- * The difference is, read protection uses the order iterator then hash in the hash loop, whereas write protection
- * uses first the last hash value and then the current iterator value
- *
- * @param password the pasword
- * @param hashAlgorithm the hash algorighm
- * @param salt the initial salt value
- * @param spinCount the repetition count
- * @param iteratorFirst if true, the iterator is hashed before the n-1 hash value,
- * if false the n-1 hash value is applied first
- * @return the hashed password
- */
- @SuppressWarnings({"squid:S2068"})
- public static byte[] hashPassword(String password, HashAlgorithm hashAlgorithm, byte[] salt, int spinCount, boolean iteratorFirst) {
- // If no password was given, use the default
- if (password == null) {
- password = Decryptor.DEFAULT_PASSWORD;
- }
-
- MessageDigest hashAlg = getMessageDigest(hashAlgorithm);
-
- hashAlg.update(salt);
- byte[] hash = hashAlg.digest(StringUtil.getToUnicodeLE(password));
- byte[] iterator = new byte[LittleEndianConsts.INT_SIZE];
-
- byte[] first = (iteratorFirst ? iterator : hash);
- byte[] second = (iteratorFirst ? hash : iterator);
-
- try {
- for (int i = 0; i < spinCount; i++) {
- LittleEndian.putInt(iterator, 0, i);
- hashAlg.reset();
- hashAlg.update(first);
- hashAlg.update(second);
- hashAlg.digest(hash, 0, hash.length); // don't create hash buffer everytime new
- }
- } catch (DigestException e) {
- throw new EncryptedDocumentException("error in password hashing");
- }
-
- return hash;
- }
-
- /**
- * <p><cite>2.3.4.12 Initialization Vector Generation (Agile Encryption)</cite></p>
- *
- * <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:</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 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;
- if (blockKey != null) {
- MessageDigest hashAlgo = getMessageDigest(hashAlgorithm);
- hashAlgo.update(salt);
- iv = hashAlgo.digest(blockKey);
- }
- return getBlock36(iv, blockSize);
- }
-
- /**
- * <p><cite>2.3.4.11 Encryption Key Generation (Agile Encryption)</cite></p>
- *
- * <p>The final hash data that is used for an encryption key is then generated by using the following
- * method:</p>
- *
- * <pre>H_final = H(H_n + blockKey)</pre>
- *
- * <p>where blockKey represents an array of bytes used to prevent two different blocks from encrypting
- * to the same cipher text.</p>
- *
- * <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.</p>
- *
- * @param passwordHash the hashed password byte
- * @param hashAlgorithm the hash algorithm
- * @param blockKey the block key
- * @param keySize the key size
- * @return intermediate key
- */
- public static byte[] generateKey(byte[] passwordHash, HashAlgorithm hashAlgorithm, byte[] blockKey, int keySize) {
- MessageDigest hashAlgo = getMessageDigest(hashAlgorithm);
- hashAlgo.update(passwordHash);
- byte[] key = hashAlgo.digest(blockKey);
- 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 secret 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 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);
- }
-
- /**
- * Initialize a new cipher object with the given cipher properties
- * If the given algorithm is not implemented in the JCE, it will try to load it from the bouncy castle
- * provider.
- *
- * @param key the secret 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
- * @param padding the padding (null = NOPADDING, ANSIX923Padding, PKCS5Padding, PKCS7Padding, ISO10126Padding, ...)
- * @return the requested cipher
- * @throws EncryptedDocumentException if the initialization failed or if an algorithm was specified,
- * which depends on a missing bouncy castle provider
- */
- public static Cipher getCipher(Key key, CipherAlgorithm cipherAlgorithm, ChainingMode chain, byte[] vec, int cipherMode, String padding) {
- int keySizeInBytes = key.getEncoded().length;
- if (padding == null) padding = "NoPadding";
-
- try {
- // Ensure the JCE policies files allow for this sized key
- if (Cipher.getMaxAllowedKeyLength(cipherAlgorithm.jceId) < keySizeInBytes*8) {
- throw new EncryptedDocumentException("Export Restrictions in place - please install JCE Unlimited Strength Jurisdiction Policy files");
- }
-
- Cipher cipher;
- if (cipherAlgorithm == CipherAlgorithm.rc4) {
- cipher = Cipher.getInstance(cipherAlgorithm.jceId);
- } else if (cipherAlgorithm.needsBouncyCastle) {
- if (chain == null) {
- throw new IllegalArgumentException("Did not have a chain for cipher " + cipherAlgorithm);
- }
- registerBouncyCastle();
- cipher = Cipher.getInstance(cipherAlgorithm.jceId + "/" + chain.jceId + "/" + padding, "BC");
- } else {
- if (chain == null) {
- throw new IllegalArgumentException("Did not have a chain for cipher " + cipherAlgorithm);
- }
- cipher = Cipher.getInstance(cipherAlgorithm.jceId + "/" + chain.jceId + "/" + padding);
- }
-
- if (vec == null) {
- cipher.init(cipherMode, key);
- } else {
- AlgorithmParameterSpec aps;
- if (cipherAlgorithm == CipherAlgorithm.rc2) {
- aps = new RC2ParameterSpec(key.getEncoded().length*8, vec);
- } else {
- aps = new IvParameterSpec(vec);
- }
- cipher.init(cipherMode, key, aps);
- }
- return cipher;
- } catch (GeneralSecurityException e) {
- throw new EncryptedDocumentException(e);
- }
- }
-
- /**
- * Returns a new byte array with a truncated to the given size.
- * If the hash has less than size bytes, it will be filled with 0x36-bytes
- *
- * @param hash the to be truncated/filled hash byte array
- * @param size the size of the returned byte array
- * @return the padded hash
- */
- private static byte[] getBlock36(byte[] hash, int size) {
- return getBlockX(hash, size, (byte)0x36);
- }
-
- /**
- * Returns a new byte array with a truncated to the given size.
- * If the hash has less than size bytes, it will be filled with 0-bytes
- *
- * @param hash the to be truncated/filled hash byte array
- * @param size the size of the returned byte array
- * @return the padded hash
- */
- public static byte[] getBlock0(byte[] hash, int size) {
- return getBlockX(hash, size, (byte)0);
- }
-
- private static byte[] getBlockX(byte[] hash, int size, byte fill) {
- if (hash.length == size) return hash;
-
- byte[] result = IOUtils.safelyAllocate(size, MAX_RECORD_LENGTH);
- Arrays.fill(result, fill);
- System.arraycopy(hash, 0, result, 0, Math.min(result.length, hash.length));
- return result;
- }
-
- public static MessageDigest getMessageDigest(HashAlgorithm hashAlgorithm) {
- try {
- if (hashAlgorithm.needsBouncyCastle) {
- registerBouncyCastle();
- return MessageDigest.getInstance(hashAlgorithm.jceId, "BC");
- } else {
- return MessageDigest.getInstance(hashAlgorithm.jceId);
- }
- } catch (GeneralSecurityException e) {
- throw new EncryptedDocumentException("hash algo not supported", e);
- }
- }
-
- public static Mac getMac(HashAlgorithm hashAlgorithm) {
- try {
- if (hashAlgorithm.needsBouncyCastle) {
- registerBouncyCastle();
- return Mac.getInstance(hashAlgorithm.jceHmacId, "BC");
- } else {
- return Mac.getInstance(hashAlgorithm.jceHmacId);
- }
- } catch (GeneralSecurityException e) {
- throw new EncryptedDocumentException("hmac algo not supported", e);
- }
- }
-
- @SuppressWarnings("unchecked")
- public static void registerBouncyCastle() {
- if (Security.getProvider("BC") != null) {
- return;
- }
-
- try {
- ClassLoader cl = CryptoFunctions.class.getClassLoader();
- String bcProviderName = "org.bouncycastle.jce.provider.BouncyCastleProvider";
- Class<Provider> clazz = (Class<Provider>)cl.loadClass(bcProviderName);
- Security.addProvider(clazz.getDeclaredConstructor().newInstance());
- } catch (Exception e) {
- throw new EncryptedDocumentException("Only the BouncyCastle provider supports your encryption settings - please add it to the classpath.", e);
- }
- }
-
- 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[] 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[][] 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},
- /* char 4 */ {0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40},
- /* char 5 */ {0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5},
- /* char 6 */ {0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A},
- /* char 7 */ {0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9},
- /* char 8 */ {0x47D3, 0x8FA6, 0x0F6D, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0},
- /* char 9 */ {0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC},
- /* char 10 */ {0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10},
- /* char 11 */ {0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168},
- /* char 12 */ {0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C},
- /* char 13 */ {0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD},
- /* char 14 */ {0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC},
- /* char 15 */ {0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4}
- };
-
- /**
- * 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="https://www.ecma-international.org/publications-and-standards/standards/ecma-376/">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) {
- if (password == null) {
- throw new IllegalArgumentException("Password cannot be null");
- }
-
- byte[] arrByteChars = toAnsiPassword(password);
-
- // SET Verifier TO 0x0000
- short verifier = 0;
-
- if (!password.isEmpty()) {
- // 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 < 2007 (method 2).
- * Its output will be used as password input for the newer word generations which
- * utilize a real hashing algorithm like sha1.
- *
- * @param password the password
- * @return the hashed password
- *
- * @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://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>
- * @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>
- */
- public static int createXorVerifier2(String password) {
- if (password == null) {
- throw new IllegalArgumentException("Password cannot be null");
- }
-
- //Array to hold Key Values
- byte[] generatedKey = new byte[4];
-
- //Maximum length of the password is 15 chars.
- final int maxPasswordLength = 15;
-
- if (!password.isEmpty()) {
- // Truncate the password to 15 characters
- password = password.substring(0, Math.min(password.length(), maxPasswordLength));
-
- byte[] arrByteChars = toAnsiPassword(password);
-
- // Compute the high-order word of the new key:
-
- // --> Initialize from the initial code array (see below), depending on the passwords length.
- 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)
- // the most significant, if the bit is set, XOR the keys high-order word with the corresponding word from
- // the Encryption Matrix
- int line = maxPasswordLength - arrByteChars.length;
- for (byte ch : arrByteChars) {
- for (int xor : ENCRYPTION_MATRIX[line++]) {
- if ((ch & 1) == 1) {
- highOrderWord ^= xor;
- }
- ch >>>= 1;
- }
- }
-
- // Compute the low-order word of the new key:
- 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, (short)verifier);
- LittleEndian.putShort(generatedKey, 2, (short)highOrderWord);
- }
-
- return LittleEndian.getInt(generatedKey);
- }
-
- /**
- * This method generates the xored-hashed password for word documents < 2007.
- */
- public static String xorHashPassword(String password) {
- int hashedPassword = createXorVerifier2(password);
- return String.format(Locale.ROOT, "%1$08X", hashedPassword);
- }
-
- /**
- * Convenience function which returns the reversed xored-hashed password for further
- * processing in word documents 2007 and newer, which utilize a real hashing algorithm like sha1.
- */
- public static String xorHashPasswordReversed(String password) {
- int hashedPassword = createXorVerifier2(password);
-
- return String.format(Locale.ROOT, "%1$02X%2$02X%3$02X%4$02X"
- , (hashedPassword) & 0xFF
- , ( hashedPassword >>> 8 ) & 0xFF
- , ( hashedPassword >>> 16 ) & 0xFF
- , ( hashedPassword >>> 24 ) & 0xFF
- );
- }
-
- /**
- * 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>
- * @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 xor key
- */
- public static int createXorKey1(String password) {
- // the xor key for method 1 is part of the verifier for method 2
- // so we simply chop it from there
- return createXorVerifier2(password) >>> 16;
- }
-
- /**
- * Creates an byte array for xor obfuscation (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>
- * @see <a href="http://docs.libreoffice.org/oox/html/binarycodec_8cxx_source.html">Libre Office implementation</a>
- *
- * @param password the password
- * @return the byte array for xor obfuscation
- */
- public static byte[] createXorArray1(String password) {
- if (password.length() > 15) {
- password = password.substring(0, 15);
- }
- byte[] passBytes = password.getBytes(StandardCharsets.US_ASCII);
-
- // this code is based on the libre office implementation.
- // The MS-OFFCRYPTO misses some infos about the various rotation sizes
- byte[] obfuscationArray = new byte[16];
- System.arraycopy(passBytes, 0, obfuscationArray, 0, passBytes.length);
- if (passBytes.length == 0) {
- System.arraycopy(PAD_ARRAY, 0, obfuscationArray, passBytes.length, PAD_ARRAY.length);
- } else {
- 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 - Excel = 2 / Word = 7
- int nRotateSize = 2;
-
- byte[] baseKeyLE = {(byte) (xorKey & 0xFF), (byte) ((xorKey >>> 8) & 0xFF)};
- for (int i=0; i<obfuscationArray.length; i++) {
- obfuscationArray[i] ^= baseKeyLE[i&1];
- obfuscationArray[i] = rotateLeft(obfuscationArray[i], nRotateSize);
- }
-
- return obfuscationArray;
- }
-
- /**
- * The provided Unicode password string is converted to a ANSI string
- *
- * @param password the password
- * @return the ansi bytes
- *
- * @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>
- */
- 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)));
- }
-
- private static short rotateLeftBase15Bit(short verifier) {
- /*
- * IF (Verifier BITWISE AND 0x4000) is 0x0000
- * SET Intermediate1 TO 0
- * ELSE
- * SET Intermediate1 TO 1
- * ENDIF
- */
- short intermediate1 = (short)(((verifier & 0x4000) == 0) ? 0 : 1);
- /*
- * SET Intermediate2 TO Verifier MULTIPLED BY 2
- * SET most significant bit of Intermediate2 TO 0
- */
- short intermediate2 = (short)((verifier<<1) & 0x7FFF);
- /*
- * SET Intermediate3 TO Intermediate1 BITWISE OR Intermediate2
- */
- return (short)(intermediate1 | intermediate2);
- }
- }
|