import io import logging import shutil import tempfile from collections import namedtuple from struct import pack, unpack, unpack_from import olefile from msoffcrypto import exceptions from msoffcrypto.format import base from msoffcrypto.format.common import _parse_header_RC4CryptoAPI from msoffcrypto.method.rc4 import DocumentRC4 from msoffcrypto.method.rc4_cryptoapi import DocumentRC4CryptoAPI logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) FibBase = namedtuple( "FibBase", [ "wIdent", "nFib", "unused", "lid", "pnNext", "fDot", "fGlsy", "fComplex", "fHasPic", "cQuickSaves", "fEncrypted", "fWhichTblStm", "fReadOnlyRecommended", "fWriteReservation", "fExtChar", "fLoadOverride", "fFarEast", "nFibBack", "fObfuscation", "IKey", "envr", "fMac", "fEmptySpecial", "fLoadOverridePage", "reserved1", "reserved2", "fSpare0", "reserved3", "reserved4", "reserved5", "reserved6", ], ) def _parseFibBase(blob): r""" Pasrse FibBase binary blob. >>> blob = io.BytesIO(b'\xec\xa5\xc1\x00G\x00\t\x04\x00\x00\x00\x13\xbf\x004\x00\ ... \x00\x00\x00\x10\x00\x00\x00\x00\x00\x04\x00\x00\x16\x04\x00\x00') >>> fibbase = _parseFibBase(blob) >>> hex(fibbase.wIdent) '0xa5ec' >>> hex(fibbase.nFib) '0xc1' >>> hex(fibbase.fExtChar) '0x1' """ getBit = lambda bits, i: (bits & (1 << i)) >> i getBitSlice = lambda bits, i, w: (bits & (2**w - 1 << i)) >> i # https://msdn.microsoft.com/en-us/library/dd944620(v=office.12).aspx (buf,) = unpack_from(">> with open("tests/inputs/rc4cryptoapi_password.doc", "rb") as f: ... officefile = Doc97File(f) ... officefile.load_key(password="Password1234_") >>> with open("tests/inputs/rc4cryptoapi_password.doc", "rb") as f: ... officefile = Doc97File(f) ... officefile.load_key(password="0000") Traceback (most recent call last): ... msoffcrypto.exceptions.InvalidKeyError: ... """ def __init__(self, file): self.file = file ole = olefile.OleFileIO(file) # do not close this, would close file self.ole = ole self.format = "doc97" self.keyTypes = ["password"] self.key = None self.salt = None # https://msdn.microsoft.com/en-us/library/dd944620(v=office.12).aspx with ole.openstream("wordDocument") as stream: fib = _parseFib(stream) # https://msdn.microsoft.com/en-us/library/dd923367(v=office.12).aspx tablename = "1Table" if fib.base.fWhichTblStm == 1 else "0Table" Info = namedtuple("Info", ["fib", "tablename"]) self.info = Info( fib=fib, tablename=tablename, ) def load_key(self, password=None): fib = self.info.fib logger.debug( "fEncrypted: {}, fObfuscation: {}".format( fib.base.fEncrypted, fib.base.fObfuscation ) ) if fib.base.fEncrypted == 1: if fib.base.fObfuscation == 1: # Using XOR obfuscation xor_obf_password_verifier = fib.base.IKey logger.debug(hex(xor_obf_password_verifier)) else: # elif fib.base.fObfuscation == 0: encryptionHeader_size = fib.base.IKey logger.debug( "encryptionHeader_size: {}".format(hex(encryptionHeader_size)) ) with self.ole.openstream(self.info.tablename) as table: encryptionHeader = ( table # TODO why create a 2nd reference to same stream? ) encryptionVersionInfo = table.read(4) vMajor, vMinor = unpack(">> f = open("tests/inputs/plain.doc", "rb") >>> file = Doc97File(f) >>> file.is_encrypted() False >>> f = open("tests/inputs/rc4cryptoapi_password.doc", "rb") >>> file = Doc97File(f) >>> file.is_encrypted() True """ return True if self.info.fib.base.fEncrypted == 1 else False