import functools
import io
import logging
from hashlib import md5
from struct import pack
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
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("<I", block)
hfinal = md5(truncatedHash + blockbytes).digest()
key = hfinal[: 128 // 8]
return key
[docs]class DocumentXOR:
def __init__(self):
pass
pad_array = [0xBB, 0xFF, 0xFF, 0xBA, 0xFF, 0xFF, 0xB9, 0x80, 0x00, 0xBE, 0x0F, 0x00, 0xBF, 0x0F, 0x00]
initial_code = [0xE1F0, 0x1D0F, 0xCC9C, 0x84C0, 0x110C, 0x0E10, 0xF1CE, 0x313E, 0x1872, 0xE139, 0xD40F, 0x84F9, 0x280C, 0xA96A, 0x4EC3]
xor_matrix = [
0xAEFC,
0x4DD9,
0x9BB2,
0x2745,
0x4E8A,
0x9D14,
0x2A09,
0x7B61,
0xF6C2,
0xFDA5,
0xEB6B,
0xC6F7,
0x9DCF,
0x2BBF,
0x4563,
0x8AC6,
0x05AD,
0x0B5A,
0x16B4,
0x2D68,
0x5AD0,
0x0375,
0x06EA,
0x0DD4,
0x1BA8,
0x3750,
0x6EA0,
0xDD40,
0xD849,
0xA0B3,
0x5147,
0xA28E,
0x553D,
0xAA7A,
0x44D5,
0x6F45,
0xDE8A,
0xAD35,
0x4A4B,
0x9496,
0x390D,
0x721A,
0xEB23,
0xC667,
0x9CEF,
0x29FF,
0x53FE,
0xA7FC,
0x5FD9,
0x47D3,
0x8FA6,
0x0F6D,
0x1EDA,
0x3DB4,
0x7B68,
0xF6D0,
0xB861,
0x60E3,
0xC1C6,
0x93AD,
0x377B,
0x6EF6,
0xDDEC,
0x45A0,
0x8B40,
0x06A1,
0x0D42,
0x1A84,
0x3508,
0x6A10,
0xAA51,
0x4483,
0x8906,
0x022D,
0x045A,
0x08B4,
0x1168,
0x76B4,
0xED68,
0xCAF1,
0x85C3,
0x1BA7,
0x374E,
0x6E9C,
0x3730,
0x6E60,
0xDCC0,
0xA9A1,
0x4363,
0x86C6,
0x1DAD,
0x3331,
0x6662,
0xCCC4,
0x89A9,
0x0373,
0x06E6,
0x0DCC,
0x1021,
0x2042,
0x4084,
0x8108,
0x1231,
0x2462,
0x48C4,
]
[docs] @staticmethod
def verifypw(password, verificationBytes):
r"""
Return True if the given password is valid.
>>> from struct import unpack
>>> password = 'VelvetSweatshop'
>>> (key,) = unpack('<H', b'\x0A\x9A') # 0x9a0a
>>> 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
[docs] @staticmethod
def xor_ror(byte1, byte2):
return DocumentXOR.ror(byte1 ^ byte2, 1, 8)
[docs] @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
[docs] @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
[docs] @staticmethod
def ror(n, rotations, width):
return (2**width - 1) & (n >> rotations | n << (width - rotations))
[docs] @staticmethod
def rol(n, rotations, width):
return (2**width - 1) & (n << rotations | n >> (width - rotations))
[docs] @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