import io import logging from hashlib import md5 from struct import pack logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) def _makekey(password, salt, block): r""" Return a intermediate key. >>> password = 'password1' >>> salt = b'\xe8w,\x1d\x91\xc5j7\x96Ga\xb2\x80\x182\x17' >>> block = 0 >>> expected = b' \xbf2\xdd\xf5@\x85\x8cQ7D\xaf\x0f$\xe0<' >>> _makekey(password, salt, block) == expected True """ # https://msdn.microsoft.com/en-us/library/dd920360(v=office.12).aspx password = password.encode("UTF-16LE") h0 = md5(password).digest() truncatedHash = h0[:5] intermediateBuffer = (truncatedHash + salt) * 16 h1 = md5(intermediateBuffer).digest() truncatedHash = h1[:5] blockbytes = pack(">> from struct import unpack >>> password = 'VelvetSweatshop' >>> (key,) = unpack('>> DocumentXOR.verifypw(password, key) True """ # https://interoperability.blob.core.windows.net/files/MS-OFFCRYPTO/%5bMS-OFFCRYPTO%5d.pdf verifier = 0 password_array = [] password_array.append(len(password)) password_array.extend([ord(ch) for ch in password]) password_array.reverse() for password_byte in password_array: if verifier & 0x4000 == 0x0000: intermidiate_1 = 0 else: intermidiate_1 = 1 intermidiate_2 = verifier * 2 intermidiate_2 = ( intermidiate_2 & 0x7FFF ) # SET most significant bit of Intermediate2 TO 0 intermidiate_3 = intermidiate_1 ^ intermidiate_2 verifier = intermidiate_3 ^ password_byte return True if (verifier ^ 0xCE4B) == verificationBytes else False @staticmethod def xor_ror(byte1, byte2): return DocumentXOR.ror(byte1 ^ byte2, 1, 8) @staticmethod def create_xor_key_method1(password): xor_key = DocumentXOR.initial_code[len(password) - 1] current_element = 0x00000068 data = [ord(ch) for ch in reversed(password)] for ch in data: for i in range(7): if ch & 0x40 != 0: xor_key = ( xor_key ^ DocumentXOR.xor_matrix[current_element] ) % 65536 ch = (ch << 1) % 256 current_element -= 1 return xor_key @staticmethod def create_xor_array_method1(password): xor_key = DocumentXOR.create_xor_key_method1(password) index = len(password) obfuscation_array = [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ] if index % 2 == 1: temp = ( xor_key & 0xFF00 ) >> 8 # SET Temp TO most significant byte of XorKey obfuscation_array[index] = DocumentXOR.xor_ror( DocumentXOR.pad_array[0], temp ) index -= 1 temp = xor_key & 0x00FF password_last_char = ord(password[-1]) obfuscation_array[index] = DocumentXOR.xor_ror(password_last_char, temp) while index > 0: index -= 1 temp = (xor_key & 0xFF00) >> 8 obfuscation_array[index] = DocumentXOR.xor_ror(ord(password[index]), temp) index -= 1 temp = xor_key & 0x00FF obfuscation_array[index] = DocumentXOR.xor_ror(ord(password[index]), temp) index = 15 pad_index = 15 - len(password) while pad_index > 0: temp = (xor_key & 0xFF00) >> 8 obfuscation_array[index] = DocumentXOR.xor_ror( DocumentXOR.pad_array[pad_index], temp ) index -= 1 pad_index -= 1 temp = xor_key & 0x00FF obfuscation_array[index] = DocumentXOR.xor_ror( DocumentXOR.pad_array[pad_index], temp ) index -= 1 pad_index -= 1 return obfuscation_array @staticmethod def ror(n, rotations, width): return (2**width - 1) & (n >> rotations | n << (width - rotations)) @staticmethod def rol(n, rotations, width): return (2**width - 1) & (n << rotations | n >> (width - rotations)) @staticmethod def decrypt(password, ibuf, plaintext, records, base): r""" Return decrypted data (DecryptData_Method1) """ obuf = io.BytesIO() xor_array = DocumentXOR.create_xor_array_method1(password) data_index = 0 record_index = 0 while data_index < len(plaintext): count = 1 if plaintext[data_index] == -1 or plaintext[data_index] == -2: for j in range(data_index + 1, len(plaintext)): if plaintext[j] >= 0: break count += 1 if plaintext[data_index] == -2: xor_array_index = (data_index + count + 4) % 16 else: xor_array_index = (data_index + count) % 16 temp_res = 0 for item in range(count): data_byte = ibuf.read(1) temp_res = data_byte[0] ^ xor_array[xor_array_index] temp_res = DocumentXOR.ror(temp_res, 5, 8) obuf.write(temp_res.to_bytes(1, "little")) xor_array_index += 1 xor_array_index = xor_array_index % 16 record_index += 1 else: obuf.write(ibuf.read(1)) data_index += count obuf.seek(0) return obuf